The home for Hyperlane core contracts, sdk packages, and other infrastructure
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
hyperlane-monorepo/solidity/apps/contracts/governance/GovernanceRouter.sol

390 lines
13 KiB

// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
pragma experimental ABIEncoderV2;
// ============ Internal Imports ============
import {GovernanceMessage} from "./GovernanceMessage.sol";
// ============ External Imports ============
import {Version0} from "@abacus-network/core/contracts/Version0.sol";
import {Router} from "@abacus-network/core/contracts/router/Router.sol";
import {TypeCasts} from "@abacus-network/core/contracts/XAppConnectionManager.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/Initializable.sol";
import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";
import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
/**
* @dev GovernanceRouter has two modes of operation, normal and recovery.
* During normal mode, `owner()` returns the `governor`, giving it permission
* to call `onlyOwner` functions.
* During recovery mode, `owner()` returns the `_owner`, giving it permission
* to call `onlyOwner` functions.
*/
contract GovernanceRouter is Version0, Router {
// ============ Libraries ============
using SafeMath for uint256;
using TypedMemView for bytes;
using TypedMemView for bytes29;
using GovernanceMessage for bytes29;
// ============ Immutables ============
// number of seconds before recovery can be activated
uint256 public immutable recoveryTimelock;
// ============ Public Storage ============
// timestamp when recovery timelock expires; 0 if timelock has not been initiated
uint256 public recoveryActiveAt;
// the local entity empowered to call governance functions during normal
// operation, typically set to 0x0 on all chains but one
address public governor;
// ============ Upgrade Gap ============
// gap for upgrade safety
uint256[48] private __GAP;
// ============ Events ============
/**
* @notice Emitted when the Governor role is set
* @param governor the address of the new Governor
*/
event SetGovernor(address indexed governor);
/**
* @notice Emitted when recovery state is initiated by the Owner
* @param owner the address of the current owner who initiated the transition
* @param recoveryActiveAt the block at which recovery state will be active
*/
event InitiateRecovery(address indexed owner, uint256 recoveryActiveAt);
/**
* @notice Emitted when recovery state is exited by the Owner
* @param owner the address of the current Owner who initiated the transition
*/
event ExitRecovery(address owner);
// ============ Modifiers ============
modifier typeAssert(bytes29 _view, GovernanceMessage.Types _type) {
_view.assertType(uint40(_type));
_;
}
modifier onlyInRecovery() {
require(inRecovery(), "!recovery");
_;
}
modifier onlyNotInRecovery() {
require(!inRecovery(), "recovery");
_;
}
modifier onlyGovernor() {
require(msg.sender == governor, "!governor");
_;
}
modifier onlyRecoveryManager() {
require(msg.sender == recoveryManager(), "!recoveryManager");
_;
}
// ============ Constructor ============
constructor(uint256 _recoveryTimelock) {
recoveryTimelock = _recoveryTimelock;
}
// ============ Initializer ============
function initialize(address _xAppConnectionManager) public initializer {
__XAppConnectionClient_initialize(_xAppConnectionManager);
governor = msg.sender;
}
// ============ External Functions ============
/**
* @notice Handle Abacus messages
* For all non-Governor chains to handle messages
* sent from the Governor chain via Abacus.
* Governor chain should never receive messages,
* because non-Governor chains are not able to send them
* @param _origin The domain (of the Governor Router)
* @param _sender The message sender (must be the Governor Router)
* @param _message The message
*/
function handle(
uint32 _origin,
bytes32 _sender,
bytes memory _message
) external override onlyInbox onlyRemoteRouter(_origin, _sender) {
bytes29 _msg = _message.ref(0);
if (_msg.isValidCall()) {
_handleCall(_msg.tryAsCall());
} else if (_msg.isValidSetGovernor()) {
_handleSetGovernor(_msg.tryAsSetGovernor());
} else if (_msg.isValidEnrollRemoteRouter()) {
_handleEnrollRemoteRouter(_msg.tryAsEnrollRemoteRouter());
} else if (_msg.isValidSetXAppConnectionManager()) {
_handleSetXAppConnectionManager(
_msg.tryAsSetXAppConnectionManager()
);
} else {
require(false, "!valid message type");
}
}
// ============ External Local Functions ============
/**
* @notice Make local calls.
* @param _calls The calls
*/
function call(GovernanceMessage.Call[] calldata _calls) external onlyOwner {
for (uint256 i = 0; i < _calls.length; i++) {
_makeCall(_calls[i]);
}
}
/**
* @notice Sets the governor of the router.
* @param _governor The address of the new governor
*/
function setGovernor(address _governor) external onlyOwner {
_setGovernor(_governor);
}
/**
* @notice Initiate the recovery timelock
* @dev callable by the recovery manager iff not in recovery
*/
function initiateRecoveryTimelock()
external
onlyNotInRecovery
onlyRecoveryManager
{
require(recoveryActiveAt == 0, "recovery already initiated");
// set the time that recovery will be active
recoveryActiveAt = block.timestamp.add(recoveryTimelock);
emit InitiateRecovery(recoveryManager(), recoveryActiveAt);
}
/**
* @notice Exit recovery mode
* @dev callable by the recovery manager iff in recovery
*/
function exitRecovery() external onlyInRecovery onlyRecoveryManager {
delete recoveryActiveAt;
emit ExitRecovery(recoveryManager());
}
// ============ External Remote Functions ============
/**
* @notice Dispatch calls on a remote chain via the remote GovernanceRouter.
* Any value paid to this function is used to pay for message processing on the remote chain.
* @param _destination The domain of the remote chain
* @param _calls The calls
*/
function callRemote(
uint32 _destination,
GovernanceMessage.Call[] calldata _calls
) external payable onlyGovernor onlyNotInRecovery {
bytes memory _msg = GovernanceMessage.formatCalls(_calls);
_dispatchToRemoteRouterWithGas(_destination, _msg, msg.value);
}
/**
* @notice Enroll a remote router on a remote router. Any value paid to this
* function is used to pay for message processing on the remote chain.
* @param _destination The domain of the enroller
* @param _domain The domain of the enrollee
* @param _router The address of the enrollee
*/
function enrollRemoteRouterRemote(
uint32 _destination,
uint32 _domain,
bytes32 _router
) external payable onlyGovernor onlyNotInRecovery {
bytes memory _msg = GovernanceMessage.formatEnrollRemoteRouter(
_domain,
_router
);
_dispatchToRemoteRouterWithGas(_destination, _msg, msg.value);
}
/**
* @notice Sets the xAppConnectionManager of a remote router. Any value paid to this
* function is used to pay for message processing on the remote chain.
* @param _destination The domain of router on which to set the xAppConnectionManager
* @param _xAppConnectionManager The address of the xAppConnectionManager contract
*/
function setXAppConnectionManagerRemote(
uint32 _destination,
address _xAppConnectionManager
) external payable onlyGovernor onlyNotInRecovery {
bytes memory _msg = GovernanceMessage.formatSetXAppConnectionManager(
TypeCasts.addressToBytes32(_xAppConnectionManager)
);
_dispatchToRemoteRouterWithGas(_destination, _msg, msg.value);
}
/**
* @notice Sets the governor of a remote router. Any value paid to this
* function is used to pay for message processing on the remote chain.
* @param _destination The domain of router on which to set the governor
* @param _governor The address of the new governor
*/
function setGovernorRemote(uint32 _destination, address _governor)
external
payable
onlyGovernor
onlyNotInRecovery
{
bytes memory _msg = GovernanceMessage.formatSetGovernor(
TypeCasts.addressToBytes32(_governor)
);
_dispatchToRemoteRouterWithGas(_destination, _msg, msg.value);
}
// ============ Public Functions ============
/**
* @notice Transfers the recovery manager to a new address.
* @dev Callable by the governor when not in recovery mode or the
* recoveryManager at any time.
* @param _recoveryManager The address of the new recovery manager
*/
function transferOwnership(address _recoveryManager)
public
virtual
override
{
// If we are not in recovery, temporarily enter recovery so that the
// recoveryManager can call transferOwnership.
if (msg.sender == recoveryManager() && !inRecovery()) {
uint256 _recoveryActiveAt = recoveryActiveAt;
recoveryActiveAt = 1;
OwnableUpgradeable.transferOwnership(_recoveryManager);
recoveryActiveAt = _recoveryActiveAt;
} else {
OwnableUpgradeable.transferOwnership(_recoveryManager);
}
}
/**
* @notice Returns the address of the current owner.
* @dev When not in recovery mode, the governor owns the contract.
*/
function owner() public view virtual override returns (address) {
return inRecovery() ? recoveryManager() : governor;
}
/**
* @notice Returns the address of the recovery manager.
* @dev Exposing via this funciton is necessary because we overload
* `owner()` in order to make `onlyOwner()` work as intended, and because
* `OwnableUpgradeable` does not expose the private `_owner`.
*/
function recoveryManager() public view returns (address) {
return OwnableUpgradeable.owner();
}
/**
* @notice Check if the contract is in recovery mode currently
* @return TRUE iff the contract is actively in recovery mode currently
*/
function inRecovery() public view returns (bool) {
uint256 _recoveryActiveAt = recoveryActiveAt;
bool _recoveryInitiated = _recoveryActiveAt != 0;
bool _recoveryActive = _recoveryActiveAt <= block.timestamp;
return _recoveryInitiated && _recoveryActive;
}
// ============ Internal Functions ============
/**
* @notice Handle message dispatching calls locally
* @param _msg The message
*/
function _handleCall(bytes29 _msg)
internal
typeAssert(_msg, GovernanceMessage.Types.Call)
{
GovernanceMessage.Call[] memory _calls = _msg.getCalls();
for (uint256 i = 0; i < _calls.length; i++) {
_makeCall(_calls[i]);
}
}
/**
* @notice Handle message transferring governorship to a new Governor
* @param _msg The message
*/
function _handleSetGovernor(bytes29 _msg)
internal
typeAssert(_msg, GovernanceMessage.Types.SetGovernor)
{
address _governor = TypeCasts.bytes32ToAddress(_msg.governor());
_setGovernor(_governor);
}
/**
* @notice Handle message setting the router address for a given domain
* @param _msg The message
*/
function _handleEnrollRemoteRouter(bytes29 _msg)
internal
typeAssert(_msg, GovernanceMessage.Types.EnrollRemoteRouter)
{
uint32 _domain = _msg.domain();
bytes32 _router = _msg.router();
_enrollRemoteRouter(_domain, _router);
}
/**
* @notice Handle message setting the xAppConnectionManager address
* @param _msg The message
*/
function _handleSetXAppConnectionManager(bytes29 _msg)
internal
typeAssert(_msg, GovernanceMessage.Types.SetXAppConnectionManager)
{
address _xAppConnectionManager = TypeCasts.bytes32ToAddress(
_msg.xAppConnectionManager()
);
_setXAppConnectionManager(_xAppConnectionManager);
}
/**
* @notice Call local contract.
* @param _call The call
* @return _ret
*/
function _makeCall(GovernanceMessage.Call memory _call)
internal
returns (bytes memory _ret)
{
address _to = TypeCasts.bytes32ToAddress(_call.to);
// attempt to dispatch using low-level call
bool _success;
(_success, _ret) = _to.call(_call.data);
// revert if the call failed
require(_success, "call failed");
}
/**
* @notice Set the governor.
* @param _governor The address of the new governor
*/
function _setGovernor(address _governor) internal {
governor = _governor;
emit SetGovernor(_governor);
}
}