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/optics-core/contracts/governance/GovernanceRouter.sol

493 lines
15 KiB

// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
pragma experimental ABIEncoderV2;
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 {Home} from "../Home.sol";
import {XAppConnectionManager, TypeCasts} from "../XAppConnectionManager.sol";
import {IMessageRecipient} from "../../interfaces/IMessageRecipient.sol";
import {GovernanceMessage} from "./GovernanceMessage.sol";
contract GovernanceRouter is Initializable, IMessageRecipient {
using SafeMath for uint256;
using TypedMemView for bytes;
using TypedMemView for bytes29;
using GovernanceMessage for bytes29;
uint32 public immutable localDomain;
uint256 public immutable recoveryTimelock; // number of seconds before recovery can be activated
uint256 public recoveryActiveAt; // timestamp when recovery timelock expires; 0 if timelock has not been initiated
address public recoveryManager; // the address of the recovery manager multisig
address public governor; // the local entity empowered to call governance functions, set to 0x0 on non-Governor chains
uint32 public governorDomain; // domain of Governor chain -- for accepting incoming messages from Governor
XAppConnectionManager public xAppConnectionManager;
mapping(uint32 => bytes32) public routers; // registry of domain -> remote GovernanceRouter contract address
uint32[] public domains; // array of all domains registered
event SetRouter(
uint32 indexed domain,
bytes32 previousRouter,
bytes32 newRouter
);
event TransferGovernor(
uint32 previousGovernorDomain,
uint32 newGovernorDomain,
address indexed previousGovernor,
address indexed newGovernor
);
event TransferRecoveryManager(
address indexed previousRecoveryManager,
address indexed newRecoveryManager
);
event InitiateRecovery(address indexed recoveryManager, uint256 endBlock);
event ExitRecovery(address recoveryManager);
constructor(uint32 _localDomain, uint256 _recoveryTimelock) {
localDomain = _localDomain;
recoveryTimelock = _recoveryTimelock;
}
function initialize(
address _xAppConnectionManager,
address _recoveryManager
) public initializer {
// initialize governor
address _governorAddr = msg.sender;
bool _isLocalGovernor = true;
_transferGovernor(localDomain, _governorAddr, _isLocalGovernor);
recoveryManager = _recoveryManager;
// initialize XAppConnectionManager
setXAppConnectionManager(_xAppConnectionManager);
require(
xAppConnectionManager.localDomain() == localDomain,
"XAppConnectionManager bad domain"
);
}
modifier typeAssert(bytes29 _view, GovernanceMessage.Types _type) {
_view.assertType(uint40(_type));
_;
}
modifier onlyReplica() {
require(xAppConnectionManager.isReplica(msg.sender), "!replica");
_;
}
modifier onlyGovernorRouter(uint32 _domain, bytes32 _address) {
require(_isGovernorRouter(_domain, _address), "!governorRouter");
_;
}
modifier onlyGovernor() {
require(msg.sender == governor, "! called by governor");
_;
}
modifier onlyRecoveryManager() {
require(msg.sender == recoveryManager, "! called by recovery manager");
_;
}
modifier onlyInRecovery() {
require(inRecovery(), "! in recovery");
_;
}
modifier onlyNotInRecovery() {
require(!inRecovery(), "in recovery");
_;
}
modifier onlyGovernorOrRecoveryManager() {
if (!inRecovery()) {
require(msg.sender == governor, "! called by governor");
} else {
require(
msg.sender == recoveryManager,
"! called by recovery manager"
);
}
_;
}
/**
* @notice Handle Optics messages
*
* For all non-Governor chains to handle messages
* sent from the Governor chain via Optics.
*
* 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 onlyReplica onlyGovernorRouter(_origin, _sender) {
bytes29 _msg = _message.ref(0);
if (_msg.isValidCall()) {
_handleCall(_msg.tryAsCall());
} else if (_msg.isValidTransferGovernor()) {
_handleTransferGovernor(_msg.tryAsTransferGovernor());
} else if (_msg.isValidSetRouter()) {
_handleSetRouter(_msg.tryAsSetRouter());
} else {
require(false, "!valid message type");
}
}
/**
* @notice Dispatch calls locally
* @param _calls The calls
*/
function callLocal(GovernanceMessage.Call[] calldata _calls)
external
onlyGovernorOrRecoveryManager
{
for (uint256 i = 0; i < _calls.length; i++) {
_dispatchCall(_calls[i]);
}
}
/**
* @notice Dispatch calls on a remote chain via the remote GovernanceRouter
* @param _destination The domain of the remote chain
* @param _calls The calls
*/
function callRemote(
uint32 _destination,
GovernanceMessage.Call[] calldata _calls
) external onlyGovernor onlyNotInRecovery {
bytes32 _router = _mustHaveRouter(_destination);
bytes memory _msg = GovernanceMessage.formatCalls(_calls);
Home(xAppConnectionManager.home()).enqueue(_destination, _router, _msg);
}
/**
* @notice Transfer governorship
* @param _newDomain The domain of the new governor
* @param _newGovernor The address of the new governor
*/
function transferGovernor(uint32 _newDomain, address _newGovernor)
external
onlyGovernor
onlyNotInRecovery
{
bool _isLocalGovernor = _isLocalDomain(_newDomain);
_transferGovernor(_newDomain, _newGovernor, _isLocalGovernor); // transfer the governor locally
if (_isLocalGovernor) {
// if the governor domain is local, we only need to change the governor address locally
// no need to message remote routers; they should already have the same domain set and governor = bytes32(0)
return;
}
bytes memory _transferGovernorMessage = GovernanceMessage
.formatTransferGovernor(
_newDomain,
TypeCasts.addressToBytes32(_newGovernor)
);
_sendToAllRemoteRouters(_transferGovernorMessage);
}
/**
* @notice Transfer recovery manager role
* @dev callable by the recoveryManager at any time to transfer the role
* @param _newRecoveryManager The address of the new recovery manager
*/
function transferRecoveryManager(address _newRecoveryManager)
external
onlyRecoveryManager
{
emit TransferRecoveryManager(recoveryManager, _newRecoveryManager);
recoveryManager = _newRecoveryManager;
}
/**
* @notice Set the router address for a given domain and
* dispatch the change to all remote routers
* @param _domain The domain
* @param _router The address of the new router
*/
function setRouter(uint32 _domain, bytes32 _router)
external
onlyGovernor
onlyNotInRecovery
{
_setRouter(_domain, _router); // set the router locally
bytes memory _setRouterMessage = GovernanceMessage.formatSetRouter(
_domain,
_router
);
_sendToAllRemoteRouters(_setRouterMessage);
}
/**
* @notice Set the router address *locally only*
* for the deployer to setup the router mapping locally
* before transferring governorship to the "true" governor
* @dev External helper for contract setup
* @param _domain The domain
* @param _router The new router
*/
function setRouterLocal(uint32 _domain, bytes32 _router)
external
onlyGovernorOrRecoveryManager
{
_setRouter(_domain, _router); // set the router locally
}
/**
* @notice Set the address of the XAppConnectionManager
* @dev Domain/address validation helper
* @param _xAppConnectionManager The address of the new xAppConnectionManager
*/
function setXAppConnectionManager(address _xAppConnectionManager)
public
onlyGovernorOrRecoveryManager
{
xAppConnectionManager = XAppConnectionManager(_xAppConnectionManager);
}
/**
* @notice Initiate the recovery timelock
* @dev callable by the recovery manager
*/
function initiateRecoveryTimelock()
external
onlyNotInRecovery
onlyRecoveryManager
{
require(recoveryActiveAt == 0, "recovery already initiated");
recoveryActiveAt = block.timestamp.add(recoveryTimelock);
emit InitiateRecovery(recoveryManager, recoveryActiveAt);
}
/**
* @notice Exit recovery mode
* @dev callable by the recovery manager to end recovery mode
*/
function exitRecovery() external onlyRecoveryManager {
require(recoveryActiveAt != 0, "recovery not initiated");
delete recoveryActiveAt;
emit ExitRecovery(recoveryManager);
}
function inRecovery() public view returns (bool) {
uint256 _recoveryActiveAt = recoveryActiveAt;
bool _recoveryInitiated = _recoveryActiveAt != 0;
bool _recoveryActive = _recoveryActiveAt <= block.timestamp;
return _recoveryInitiated && _recoveryActive;
}
/**
* @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++) {
_dispatchCall(_calls[i]);
}
}
/**
* @notice Handle message transferring governorship to a new Governor
* @param _msg The message
*/
function _handleTransferGovernor(bytes29 _msg)
internal
typeAssert(_msg, GovernanceMessage.Types.TransferGovernor)
{
uint32 _newDomain = _msg.domain();
address _newGovernor = TypeCasts.bytes32ToAddress(_msg.governor());
bool _isLocalGovernor = _isLocalDomain(_newDomain);
_transferGovernor(_newDomain, _newGovernor, _isLocalGovernor);
}
/**
* @notice Handle message setting the router address for a given domain
* @param _msg The message
*/
function _handleSetRouter(bytes29 _msg)
internal
typeAssert(_msg, GovernanceMessage.Types.SetRouter)
{
uint32 _domain = _msg.domain();
bytes32 _router = _msg.router();
_setRouter(_domain, _router);
}
/**
* @notice Dispatch message to all remote routers
* @param _msg The message
*/
function _sendToAllRemoteRouters(bytes memory _msg) internal {
Home _home = Home(xAppConnectionManager.home());
for (uint256 i = 0; i < domains.length; i++) {
if (domains[i] != uint32(0)) {
_home.enqueue(domains[i], routers[domains[i]], _msg);
}
}
}
/**
* @notice Dispatch call locally
* @param _call The call
* @return _ret
*/
function _dispatchCall(GovernanceMessage.Call memory _call)
internal
returns (bytes memory _ret)
{
address _toContract = TypeCasts.bytes32ToAddress(_call.to);
bool _success;
(_success, _ret) = _toContract.call(_call.data);
require(_success, "call failed");
}
/**
* @notice Transfer governorship within this contract's state
* @param _newDomain The domain of the new governor
* @param _newGovernor The address of the new governor
* @param _isLocalGovernor True if the newDomain is the localDomain
*/
function _transferGovernor(
uint32 _newDomain,
address _newGovernor,
bool _isLocalGovernor
) internal {
// require that the governor domain has a valid router
if (!_isLocalGovernor) {
_mustHaveRouter(_newDomain);
}
// Governor is 0x0 unless the governor is local
address _newGov = _isLocalGovernor ? _newGovernor : address(0);
governorDomain = _newDomain;
governor = _newGov;
emit TransferGovernor(governorDomain, _newDomain, governor, _newGov);
}
/**
* @notice Set the router for a given domain
* @param _domain The domain
* @param _newRouter The new router
*/
function _setRouter(uint32 _domain, bytes32 _newRouter) internal {
bytes32 _previousRouter = routers[_domain];
emit SetRouter(_domain, _previousRouter, _newRouter);
if (_newRouter == bytes32(0)) {
_removeDomain(_domain);
return;
}
if (_previousRouter == bytes32(0)) {
_addDomain(_domain);
}
routers[_domain] = _newRouter;
}
/**
* @notice Add a domain that has a router
* @param _domain The domain
*/
function _addDomain(uint32 _domain) internal {
domains.push(_domain);
}
/**
* @notice Remove a domain and its associated router
* @param _domain The domain
*/
function _removeDomain(uint32 _domain) internal {
delete routers[_domain];
// find the index of the domain to remove & delete it from domains[]
for (uint256 i = 0; i < domains.length; i++) {
if (domains[i] == _domain) {
delete domains[i];
return;
}
}
}
/**
* @notice Determine if a given domain and address is the Governor Router
* @param _domain The domain
* @param _address The address of the domain's router
* @return _ret True if the given domain/address is the
* Governor Router.
*/
function _isGovernorRouter(uint32 _domain, bytes32 _address)
internal
view
returns (bool)
{
return _domain == governorDomain && _address == routers[_domain];
}
/**
* @notice Determine if a given domain is the local domain
* @param _domain The domain
* @return _ret - True if the given domain is the local domain
*/
function _isLocalDomain(uint32 _domain) internal view returns (bool) {
return _domain == localDomain;
}
/**
* @notice Require that a domain has a router and returns the router
* @param _domain The domain
* @return _router - The domain's router
*/
function _mustHaveRouter(uint32 _domain)
internal
view
returns (bytes32 _router)
{
_router = routers[_domain];
require(_router != bytes32(0), "!router");
}
}