// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2 <0.8.0;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(account) }
return size > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain`call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.call{ value: value }(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.staticcall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.delegatecall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2 <0.8.0;
/**
* @dev Collection of functions related to the address type
*/
library AddressUpgradeable {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(account) }
return size > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain`call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.call{ value: value }(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.staticcall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/*
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address payable) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
import "../proxy/Initializable.sol";
/*
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal initializer {
__Context_init_unchained();
}
function __Context_init_unchained() internal initializer {
}
function _msgSender() internal view virtual returns (address payable) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
uint256[50] private __gap;
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
// Check the signature length
if (signature.length != 65) {
revert("ECDSA: invalid signature length");
}
// Divide the signature in r, s and v variables
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
// solhint-disable-next-line no-inline-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return recover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover-bytes32-bytes-} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "ECDSA: invalid signature 's' value");
require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value");
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
require(signer != address(0), "ECDSA: invalid signature");
return signer;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* replicates the behavior of the
* https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`]
* JSON-RPC method.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.7.6;
pragma experimental ABIEncoderV2;
// ============ External Imports ============
import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol";
library GovernanceMessage {
using TypedMemView for bytes;
using TypedMemView for bytes29;
// Batch message characteristics
// * 1 item - the type
uint256 private constant BATCH_PREFIX_ITEMS = 1;
// * type is 1 byte long
uint256 private constant BATCH_PREFIX_LEN = 1;
// * Length of a Batch message
// * type + batch hash
uint256 private constant BATCH_MESSAGE_LEN = 1 + 32;
// Serialized Call[] characteristics
// * 1 item - the type
uint256 private constant CALLS_PREFIX_ITEMS = 1;
// * type is 1 byte long
uint256 private constant CALLS_PREFIX_LEN = 1;
// Serialized Call characteristics
// * Location of the data blob in a serialized call
// * address + length
uint256 private constant CALL_DATA_OFFSET = 32 + 4;
// Transfer Governance message characteristics
// * Length of a Transfer Governance message
// * type + domain + address
uint256 private constant TRANSFER_GOV_MESSAGE_LEN = 1 + 4 + 32;
struct Call {
bytes32 to;
bytes data;
}
enum Types {
Invalid, // 0
Batch, // 1 - A Batch message
TransferGovernor // 2 - A TransferGovernor message
}
// Read the type of a message
function messageType(bytes29 _view) internal pure returns (Types) {
return Types(uint8(_view.typeOf()));
}
// Read the message identifer (first byte) of a message
function identifier(bytes29 _view) internal pure returns (uint8) {
return uint8(_view.indexUint(0, 1));
}
/*
* Message Type: BATCH
* struct Call {
* identifier, // message ID -- 1 byte
* batchHash // Hash of serialized calls (see below) -- 32 bytes
* }
*
* struct Call {
* to, // address to call -- 32 bytes
* dataLen, // call data length -- 4 bytes,
* data // call data -- 0+ bytes (variable)
* }
*
* struct Calls
* numCalls, // number of calls -- 1 byte
* calls[] // serialized Call -- 0+ bytes
* }
*/
// create a Batch message from a list of calls
function formatBatch(Call[] memory _calls)
internal
view
returns (bytes memory)
{
return abi.encodePacked(Types.Batch, getBatchHash(_calls));
}
// serialize a call to memory and return a reference
function serializeCall(Call memory _call) internal pure returns (bytes29) {
return
abi
.encodePacked(_call.to, uint32(_call.data.length), _call.data)
.ref(0);
}
function getBatchHash(Call[] memory _calls)
internal
view
returns (bytes32)
{
// length prefix + 1 entry for each
bytes29[] memory _encodedCalls = new bytes29[](
_calls.length + CALLS_PREFIX_ITEMS
);
_encodedCalls[0] = abi.encodePacked(uint8(_calls.length)).ref(0);
for (uint256 i = 0; i < _calls.length; i++) {
_encodedCalls[i + CALLS_PREFIX_ITEMS] = serializeCall(_calls[i]);
}
return keccak256(TypedMemView.join(_encodedCalls));
}
function isValidBatch(bytes29 _view) internal pure returns (bool) {
return
identifier(_view) == uint8(Types.Batch) &&
_view.len() == BATCH_MESSAGE_LEN;
}
function isBatch(bytes29 _view) internal pure returns (bool) {
return isValidBatch(_view) && messageType(_view) == Types.Batch;
}
function tryAsBatch(bytes29 _view) internal pure returns (bytes29) {
if (isValidBatch(_view)) {
return _view.castTo(uint40(Types.Batch));
}
return TypedMemView.nullView();
}
function mustBeBatch(bytes29 _view) internal pure returns (bytes29) {
return tryAsBatch(_view).assertValid();
}
// Types.Batch
function batchHash(bytes29 _view) internal pure returns (bytes32) {
return _view.index(BATCH_PREFIX_LEN, 32);
}
/*
* Message Type: TRANSFER GOVERNOR
* struct TransferGovernor {
* identifier, // message ID -- 1 byte
* domain, // domain of new governor -- 4 bytes
* addr // address of new governor -- 32 bytes
* }
*/
function formatTransferGovernor(uint32 _domain, bytes32 _governor)
internal
view
returns (bytes memory _msg)
{
_msg = TypedMemView.clone(
mustBeTransferGovernor(
abi
.encodePacked(Types.TransferGovernor, _domain, _governor)
.ref(0)
)
);
}
function isValidTransferGovernor(bytes29 _view)
internal
pure
returns (bool)
{
return
identifier(_view) == uint8(Types.TransferGovernor) &&
_view.len() == TRANSFER_GOV_MESSAGE_LEN;
}
function isTransferGovernor(bytes29 _view) internal pure returns (bool) {
return
isValidTransferGovernor(_view) &&
messageType(_view) == Types.TransferGovernor;
}
function tryAsTransferGovernor(bytes29 _view)
internal
pure
returns (bytes29)
{
if (isValidTransferGovernor(_view)) {
return _view.castTo(uint40(Types.TransferGovernor));
}
return TypedMemView.nullView();
}
function mustBeTransferGovernor(bytes29 _view)
internal
pure
returns (bytes29)
{
return tryAsTransferGovernor(_view).assertValid();
}
// Types.TransferGovernor
function domain(bytes29 _view) internal pure returns (uint32) {
return uint32(_view.indexUint(1, 4));
}
// Types.TransferGovernor
function governor(bytes29 _view) internal pure returns (bytes32) {
return _view.index(5, 32);
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.7.6;
pragma experimental ABIEncoderV2;
// ============ Internal Imports ============
import {Home} from "../Home.sol";
import {Version0} from "../Version0.sol";
import {XAppConnectionManager, TypeCasts} from "../XAppConnectionManager.sol";
import {IMessageRecipient} from "../interfaces/IMessageRecipient.sol";
import {GovernanceMessage} from "./GovernanceMessage.sol";
// ============ External Imports ============
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";
contract GovernanceRouter is Version0, Initializable, IMessageRecipient {
// ============ Libraries ============
using SafeMath for uint256;
using TypedMemView for bytes;
using TypedMemView for bytes29;
using GovernanceMessage for bytes29;
// ============== Enums ==============
// The status of a batch of governance calls
enum BatchStatus {
Unknown, // 0
Pending, // 1
Complete // 2
}
// ============ Immutables ============
uint32 public immutable localDomain;
// 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 address of the recovery manager multisig
address public recoveryManager;
// the local entity empowered to call governance functions, set to 0x0 on non-Governor chains
address public governor;
// domain of Governor chain -- for accepting incoming messages from Governor
uint32 public governorDomain;
// xAppConnectionManager contract which stores Replica addresses
XAppConnectionManager public xAppConnectionManager;
// domain -> remote GovernanceRouter contract address
mapping(uint32 => bytes32) public routers;
// array of all domains with registered GovernanceRouter
uint32[] public domains;
// call hash -> call status
mapping(bytes32 => BatchStatus) public inboundCallBatches;
// ============ Upgrade Gap ============
// gap for upgrade safety
uint256[42] private __GAP;
// ============ Events ============
/**
* @notice Emitted a remote GovernanceRouter address is added, removed, or changed
* @param domain the domain of the remote Router
* @param previousRouter the previously registered router; 0 if router is being added
* @param newRouter the new registered router; 0 if router is being removed
*/
event SetRouter(
uint32 indexed domain,
bytes32 previousRouter,
bytes32 newRouter
);
/**
* @notice Emitted when the Governor role is transferred
* @param previousGovernorDomain the domain of the previous Governor
* @param newGovernorDomain the domain of the new Governor
* @param previousGovernor the address of the previous Governor; 0 if the governor was remote
* @param newGovernor the address of the new Governor; 0 if the governor is remote
*/
event TransferGovernor(
uint32 previousGovernorDomain,
uint32 newGovernorDomain,
address indexed previousGovernor,
address indexed newGovernor
);
/**
* @notice Emitted when the RecoveryManager role is transferred
* @param previousRecoveryManager the address of the previous RecoveryManager
* @param newRecoveryManager the address of the new RecoveryManager
*/
event TransferRecoveryManager(
address indexed previousRecoveryManager,
address indexed newRecoveryManager
);
/**
* @notice Emitted when recovery state is initiated by the RecoveryManager
* @param recoveryManager the address of the current RecoveryManager who
* initiated the transition
* @param recoveryActiveAt the block at which recovery state will be active
*/
event InitiateRecovery(
address indexed recoveryManager,
uint256 recoveryActiveAt
);
/**
* @notice Emitted when recovery state is exited by the RecoveryManager
* @param recoveryManager the address of the current RecoveryManager who
* initiated the transition
*/
event ExitRecovery(address recoveryManager);
/**
* @notice Emitted when a batch of governance instructions from the
* governing remote router is received and ready for execution
* @param batchHash A hash committing to the batch of calls to be executed
*/
event BatchReceived(bytes32 indexed batchHash);
/**
* @notice Emitted when a batch of governance instructions from the
* governing remote router is executed
* @param batchHash A hash committing to the batch of calls to be executed
*/
event BatchExecuted(bytes32 indexed batchHash);
modifier typeAssert(bytes29 _view, GovernanceMessage.Types _type) {
_view.assertType(uint40(_type));
_;
}
// ============ Modifiers ============
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 || msg.sender == address(this),
"! called by governor"
);
_;
}
modifier onlyRecoveryManager() {
require(msg.sender == recoveryManager, "! called by recovery manager");
_;
}
modifier onlyNotInRecovery() {
require(!inRecovery(), "in recovery");
_;
}
modifier onlyGovernorOrRecoveryManager() {
if (!inRecovery()) {
require(
msg.sender == governor || msg.sender == address(this),
"! called by governor"
);
} else {
require(
msg.sender == recoveryManager || msg.sender == address(this),
"! called by recovery manager"
);
}
_;
}
// ============ Constructor ============
constructor(uint32 _localDomain, uint256 _recoveryTimelock) {
localDomain = _localDomain;
recoveryTimelock = _recoveryTimelock;
}
// ============ Initializer ============
function initialize(
address _xAppConnectionManager,
address _recoveryManager
) public initializer {
// initialize governor
address _governorAddr = msg.sender;
bool _isLocalGovernor = true;
_transferGovernor(localDomain, _governorAddr, _isLocalGovernor);
// initialize recovery manager
recoveryManager = _recoveryManager;
// initialize XAppConnectionManager
setXAppConnectionManager(_xAppConnectionManager);
}
// ============ External Functions ============
/**
* @notice Handle Nomad messages
* For all non-Governor chains to handle messages
* sent from the Governor chain via Nomad.
* 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,
uint32, // _nonce (unused)
bytes32 _sender,
bytes memory _message
) external override onlyReplica onlyGovernorRouter(_origin, _sender) {
bytes29 _msg = _message.ref(0);
bytes29 _view = _msg.tryAsBatch();
if (_view.notNull()) {
_handleBatch(_view);
return;
}
_view = _msg.tryAsTransferGovernor();
if (_view.notNull()) {
_handleTransferGovernor(_view);
return;
}
require(false, "!valid message type");
}
/**
* @notice Dispatch a set of local and remote calls
* Local calls are executed immediately.
* Remote calls are dispatched to the remote domain for processing and
* execution.
* @dev The contents of the _domains array at the same index
* will determine the destination of messages in that _remoteCalls array.
* As such, all messages in an array MUST have the same destination.
* Missing destinations or too many will result in reverts.
* @param _localCalls An array of local calls
* @param _remoteCalls An array of arrays of remote calls
*/
function executeGovernanceActions(
GovernanceMessage.Call[] calldata _localCalls,
uint32[] calldata _domains,
GovernanceMessage.Call[][] calldata _remoteCalls
) external onlyGovernorOrRecoveryManager {
// extend the access modifier onlyGovernor, limiting the call to
// external actors. This is to protect against governanceRouter calling
// the function through executeCallBatch.
// executeGovernanceActions is intended to be executed ONLY by external actors,
// namely either the governor or the recovery manager
require(msg.sender != address(this), "!sender is an external address");
require(
_domains.length == _remoteCalls.length,
"!domains length matches calls length"
);
// remote calls are disallowed while in recovery
require(
_remoteCalls.length == 0 || !inRecovery(),
"!remote calls in recovery mode"
);
// _localCall loop
for (uint256 i = 0; i < _localCalls.length; i++) {
_callLocal(_localCalls[i]);
}
// remote calls loop
for (uint256 i = 0; i < _remoteCalls.length; i++) {
uint32 destination = _domains[i];
_callRemote(destination, _remoteCalls[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
) internal onlyGovernor onlyNotInRecovery {
// ensure that destination chain has enrolled router
bytes32 _router = _mustHaveRouter(_destination);
// format batch message
bytes memory _msg = GovernanceMessage.formatBatch(_calls);
// dispatch call message using Nomad
Home(xAppConnectionManager.home()).dispatch(
_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);
// transfer the governor locally
_transferGovernor(_newDomain, _newGovernor, _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)
if (_isLocalGovernor) {
return;
}
// format transfer governor message
bytes memory _transferGovernorMessage = GovernanceMessage
.formatTransferGovernor(
_newDomain,
TypeCasts.addressToBytes32(_newGovernor)
);
// send transfer governor message to all remote routers
// note: this assumes that the Router is on the global GovernorDomain;
// this causes a process error when relinquishing governorship
// on a newly deployed domain which is not the GovernorDomain
_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 setRouterGlobal(uint32 _domain, bytes32 _router)
external
onlyGovernor
onlyNotInRecovery
{
_setRouterGlobal(_domain, _router);
}
function _setRouterGlobal(uint32 _domain, bytes32 _router) internal {
Home _home = Home(xAppConnectionManager.home());
// Set up the call for use in the loop.
// Because each domain's governance router may be different, we cannot
// serialize the `Call` once and then reuse it. We have to re-serialize
// the call, adjusting its `to` value on each step of the loop.
GovernanceMessage.Call[] memory _calls = new GovernanceMessage.Call[](
1
);
_calls[0].data = abi.encodeWithSignature(
"setRouterLocal(uint32,bytes32)",
_domain,
_router
);
for (uint256 i = 0; i < domains.length; i++) {
uint32 _destination = domains[i];
if (_destination != uint32(0)) {
// set to, and dispatch
bytes32 _recipient = routers[_destination];
_calls[0].to = _recipient;
bytes memory _msg = GovernanceMessage.formatBatch(_calls);
_home.dispatch(_destination, _recipient, _msg);
}
}
// set the router locally
_setRouter(_domain, _router);
}
/**
* @notice Set the router address *locally only*
* @dev For use in deploy to setup the router mapping locally
* @param _domain The domain
* @param _router The new router
*/
function setRouterLocal(uint32 _domain, bytes32 _router)
external
onlyGovernorOrRecoveryManager
{
// set the router locally
_setRouter(_domain, _router);
}
/**
* @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);
require(
xAppConnectionManager.localDomain() == localDomain,
"XAppConnectionManager bad domain"
);
}
/**
* @notice Initiate the recovery timelock
* @dev callable by the recovery manager
*/
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 to end recovery mode
*/
function exitRecovery() external onlyRecoveryManager {
require(recoveryActiveAt != 0, "recovery not initiated");
delete recoveryActiveAt;
emit ExitRecovery(recoveryManager);
}
// ============ Public Functions ============
/**
* @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 storing batch of calls to be executed locally
* @dev If a second, identical batch is attempted to deliver while
* the first is still pending, the second delivery will revert,
* allowing the second batch to be delivered later after the first is executed.
* @param _msg The message
*/
function _handleBatch(bytes29 _msg)
internal
typeAssert(_msg, GovernanceMessage.Types.Batch)
{
bytes32 _batchHash = _msg.batchHash();
// prevent delivery of identical batches while one is still pending
require(
inboundCallBatches[_batchHash] != BatchStatus.Pending,
"BatchStatus is Pending"
);
// set batch to pending & emit
inboundCallBatches[_batchHash] = BatchStatus.Pending;
emit BatchReceived(_batchHash);
}
/**
* @notice execute a pending batch of messages
*/
function executeCallBatch(GovernanceMessage.Call[] calldata _calls)
external
{
bytes32 _batchHash = GovernanceMessage.getBatchHash(_calls);
require(
inboundCallBatches[_batchHash] == BatchStatus.Pending,
"!batch pending"
);
inboundCallBatches[_batchHash] = BatchStatus.Complete;
for (uint256 i = 0; i < _calls.length; i++) {
_callLocal(_calls[i]);
}
emit BatchExecuted(_batchHash);
}
/**
* @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 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.dispatch(domains[i], routers[domains[i]], _msg);
}
}
}
/**
* @notice Dispatch call locally
* @param _call The call
* @return _ret
*/
function _callLocal(GovernanceMessage.Call memory _call)
internal
returns (bytes memory _ret)
{
address _toContract = TypeCasts.bytes32ToAddress(_call.to);
// attempt to dispatch using low-level call
bool _success;
(_success, _ret) = _toContract.call(_call.data);
// revert if the call failed
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 new governor is not the zero address
require(_newGovernor != address(0), "cannot renounce governor");
// 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);
// emit event before updating state variables
emit TransferGovernor(governorDomain, _newDomain, governor, _newGov);
// update state
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 {
// ignore local domain in router mapping
require(!_isLocalDomain(_domain), "can't set local router");
// store previous router in memory
bytes32 _previousRouter = routers[_domain];
// if router is being removed,
if (_newRouter == bytes32(0)) {
// remove domain from array
_removeDomain(_domain);
// remove router from mapping
delete routers[_domain];
} else {
// if router was not previously added,
if (_previousRouter == bytes32(0)) {
// add domain to array
_addDomain(_domain);
}
// set router in mapping (add or change)
routers[_domain] = _newRouter;
}
// emit event
emit SetRouter(_domain, _previousRouter, _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 from array
* @param _domain The domain
*/
function _removeDomain(uint32 _domain) internal {
// 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] &&
_address != 0;
}
/**
* @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");
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.7.6;
// ============ Internal Imports ============
import {Version0} from "./Version0.sol";
import {NomadBase} from "./NomadBase.sol";
import {QueueLib} from "./libs/Queue.sol";
import {MerkleLib} from "./libs/Merkle.sol";
import {Message} from "./libs/Message.sol";
import {MerkleTreeManager} from "./Merkle.sol";
import {QueueManager} from "./Queue.sol";
import {IUpdaterManager} from "./interfaces/IUpdaterManager.sol";
// ============ External Imports ============
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
/**
* @title Home
* @author Illusory Systems Inc.
* @notice Accepts messages to be dispatched to remote chains,
* constructs a Merkle tree of the messages,
* and accepts signatures from a bonded Updater
* which notarize the Merkle tree roots.
* Accepts submissions of fraudulent signatures
* by the Updater and slashes the Updater in this case.
*/
contract Home is Version0, QueueManager, MerkleTreeManager, NomadBase {
// ============ Libraries ============
using QueueLib for QueueLib.Queue;
using MerkleLib for MerkleLib.Tree;
// ============ Constants ============
// Maximum bytes per message = 2 KiB
// (somewhat arbitrarily set to begin)
uint256 public constant MAX_MESSAGE_BODY_BYTES = 2 * 2**10;
// ============ Public Storage Variables ============
// domain => next available nonce for the domain
mapping(uint32 => uint32) public nonces;
// contract responsible for Updater bonding, slashing and rotation
IUpdaterManager public updaterManager;
// ============ Upgrade Gap ============
// gap for upgrade safety
uint256[48] private __GAP;
// ============ Events ============
/**
* @notice Emitted when a new message is dispatched via Nomad
* @param messageHash Hash of message; the leaf inserted to the Merkle tree
* for the message
* @param leafIndex Index of message's leaf in merkle tree
* @param destinationAndNonce Destination and destination-specific
* nonce combined in single field ((destination << 32) & nonce)
* @param committedRoot the latest notarized root submitted in the last
* signed Update
* @param message Raw bytes of message
*/
event Dispatch(
bytes32 indexed messageHash,
uint256 indexed leafIndex,
uint64 indexed destinationAndNonce,
bytes32 committedRoot,
bytes message
);
/**
* @notice Emitted when proof of an improper update is submitted,
* which sets the contract to FAILED state
* @param oldRoot Old root of the improper update
* @param newRoot New root of the improper update
* @param signature Signature on `oldRoot` and `newRoot
*/
event ImproperUpdate(bytes32 oldRoot, bytes32 newRoot, bytes signature);
/**
* @notice Emitted when proof of a double update is submitted,
* which sets the contract to FAILED state
* @param oldRoot Old root shared between two conflicting updates
* @param newRoot Array containing two conflicting new roots
* @param signature Signature on `oldRoot` and `newRoot`[0]
* @param signature2 Signature on `oldRoot` and `newRoot`[1]
*/
event DoubleUpdate(
bytes32 oldRoot,
bytes32[2] newRoot,
bytes signature,
bytes signature2
);
/**
* @notice Emitted when the Updater is slashed
* (should be paired with ImproperUpdater or DoubleUpdate event)
* @param updater The address of the updater
* @param reporter The address of the entity that reported the updater misbehavior
*/
event UpdaterSlashed(address indexed updater, address indexed reporter);
/**
* @notice Emitted when the UpdaterManager contract is changed
* @param updaterManager The address of the new updaterManager
*/
event NewUpdaterManager(address updaterManager);
// ============ Constructor ============
constructor(uint32 _localDomain) NomadBase(_localDomain) {} // solhint-disable-line no-empty-blocks
// ============ Initializer ============
function initialize(IUpdaterManager _updaterManager) public initializer {
// initialize queue, set Updater Manager, and initialize
__QueueManager_initialize();
_setUpdaterManager(_updaterManager);
__NomadBase_initialize(updaterManager.updater());
}
// ============ Modifiers ============
/**
* @notice Ensures that function is called by the UpdaterManager contract
*/
modifier onlyUpdaterManager() {
require(msg.sender == address(updaterManager), "!updaterManager");
_;
}
/**
* @notice Ensures that contract state != FAILED when the function is called
*/
modifier notFailed() {
require(state != States.Failed, "failed state");
_;
}
// ============ External: Updater & UpdaterManager Configuration ============
/**
* @notice Set a new Updater
* @dev To be set when rotating Updater after Fraud
* @param _updater the new Updater
*/
function setUpdater(address _updater) external onlyUpdaterManager {
_setUpdater(_updater);
// set the Home state to Active
// now that Updater has been rotated
state = States.Active;
}
/**
* @notice Set a new UpdaterManager contract
* @dev Home(s) will initially be initialized using a trusted UpdaterManager contract;
* we will progressively decentralize by swapping the trusted contract with a new implementation
* that implements Updater bonding & slashing, and rules for Updater selection & rotation
* @param _updaterManager the new UpdaterManager contract
*/
function setUpdaterManager(address _updaterManager) external onlyOwner {
_setUpdaterManager(IUpdaterManager(_updaterManager));
_setUpdater(IUpdaterManager(_updaterManager).updater());
}
// ============ External Functions ============
/**
* @notice Dispatch the message to the destination domain & recipient
* @dev Format the message, insert its hash into Merkle tree,
* enqueue the new Merkle root, and emit `Dispatch` event with message information.
* @param _destinationDomain Domain of destination chain
* @param _recipientAddress Address of recipient on destination chain as bytes32
* @param _messageBody Raw bytes content of message
*/
function dispatch(
uint32 _destinationDomain,
bytes32 _recipientAddress,
bytes memory _messageBody
) external notFailed {
require(_messageBody.length <= MAX_MESSAGE_BODY_BYTES, "msg too long");
// get the next nonce for the destination domain, then increment it
uint32 _nonce = nonces[_destinationDomain];
nonces[_destinationDomain] = _nonce + 1;
// format the message into packed bytes
bytes memory _message = Message.formatMessage(
localDomain,
bytes32(uint256(uint160(msg.sender))),
_nonce,
_destinationDomain,
_recipientAddress,
_messageBody
);
// insert the hashed message into the Merkle tree
bytes32 _messageHash = keccak256(_message);
tree.insert(_messageHash);
// enqueue the new Merkle root after inserting the message
queue.enqueue(root());
// Emit Dispatch event with message information
// note: leafIndex is count() - 1 since new leaf has already been inserted
emit Dispatch(
_messageHash,
count() - 1,
_destinationAndNonce(_destinationDomain, _nonce),
committedRoot,
_message
);
}
/**
* @notice Submit a signature from the Updater "notarizing" a root,
* which updates the Home contract's `committedRoot`,
* and publishes the signature which will be relayed to Replica contracts
* @dev emits Update event
* @dev If _newRoot is not contained in the queue,
* the Update is a fraudulent Improper Update, so
* the Updater is slashed & Home is set to FAILED state
* @param _committedRoot Current updated merkle root which the update is building off of
* @param _newRoot New merkle root to update the contract state to
* @param _signature Updater signature on `_committedRoot` and `_newRoot`
*/
function update(
bytes32 _committedRoot,
bytes32 _newRoot,
bytes memory _signature
) external notFailed {
// check that the update is not fraudulent;
// if fraud is detected, Updater is slashed & Home is set to FAILED state
if (improperUpdate(_committedRoot, _newRoot, _signature)) return;
// clear all of the intermediate roots contained in this update from the queue
while (true) {
bytes32 _next = queue.dequeue();
if (_next == _newRoot) break;
}
// update the Home state with the latest signed root & emit event
committedRoot = _newRoot;
emit Update(localDomain, _committedRoot, _newRoot, _signature);
}
/**
* @notice Suggest an update for the Updater to sign and submit.
* @dev If queue is empty, null bytes returned for both
* (No update is necessary because no messages have been dispatched since the last update)
* @return _committedRoot Latest root signed by the Updater
* @return _new Latest enqueued Merkle root
*/
function suggestUpdate()
external
view
returns (bytes32 _committedRoot, bytes32 _new)
{
if (queue.length() != 0) {
_committedRoot = committedRoot;
_new = queue.lastItem();
}
}
/**
* @notice Called by external agent. Checks that signatures on two sets of
* roots are valid and that the new roots conflict with each other. If both
* cases hold true, the contract is failed and a `DoubleUpdate` event is
* emitted.
* @dev When `fail()` is called on Home, updater is slashed.
* @param _oldRoot Old root shared between two conflicting updates
* @param _newRoot Array containing two conflicting new roots
* @param _signature Signature on `_oldRoot` and `_newRoot`[0]
* @param _signature2 Signature on `_oldRoot` and `_newRoot`[1]
*/
function doubleUpdate(
bytes32 _oldRoot,
bytes32[2] calldata _newRoot,
bytes calldata _signature,
bytes calldata _signature2
) external notFailed {
if (
NomadBase._isUpdaterSignature(_oldRoot, _newRoot[0], _signature) &&
NomadBase._isUpdaterSignature(_oldRoot, _newRoot[1], _signature2) &&
_newRoot[0] != _newRoot[1]
) {
_fail();
emit DoubleUpdate(_oldRoot, _newRoot, _signature, _signature2);
}
}
// ============ Public Functions ============
/**
* @notice Hash of Home domain concatenated with "NOMAD"
*/
function homeDomainHash() public view override returns (bytes32) {
return _homeDomainHash(localDomain);
}
/**
* @notice Check if an Update is an Improper Update;
* if so, slash the Updater and set the contract to FAILED state.
*
* An Improper Update is an update building off of the Home's `committedRoot`
* for which the `_newRoot` does not currently exist in the Home's queue.
* This would mean that message(s) that were not truly
* dispatched on Home were falsely included in the signed root.
*
* An Improper Update will only be accepted as valid by the Replica
* If an Improper Update is attempted on Home,
* the Updater will be slashed immediately.
* If an Improper Update is submitted to the Replica,
* it should be relayed to the Home contract using this function
* in order to slash the Updater with an Improper Update.
*
* An Improper Update submitted to the Replica is only valid
* while the `_oldRoot` is still equal to the `committedRoot` on Home;
* if the `committedRoot` on Home has already been updated with a valid Update,
* then the Updater should be slashed with a Double Update.
* @dev Reverts (and doesn't slash updater) if signature is invalid or
* update not current
* @param _oldRoot Old merkle tree root (should equal home's committedRoot)
* @param _newRoot New merkle tree root
* @param _signature Updater signature on `_oldRoot` and `_newRoot`
* @return TRUE if update was an Improper Update (implying Updater was slashed)
*/
function improperUpdate(
bytes32 _oldRoot,
bytes32 _newRoot,
bytes memory _signature
) public notFailed returns (bool) {
require(
_isUpdaterSignature(_oldRoot, _newRoot, _signature),
"!updater sig"
);
require(_oldRoot == committedRoot, "not a current update");
// if the _newRoot is not currently contained in the queue,
// slash the Updater and set the contract to FAILED state
if (!queue.contains(_newRoot)) {
_fail();
emit ImproperUpdate(_oldRoot, _newRoot, _signature);
return true;
}
// if the _newRoot is contained in the queue,
// this is not an improper update
return false;
}
// ============ Internal Functions ============
/**
* @notice Set the UpdaterManager
* @param _updaterManager Address of the UpdaterManager
*/
function _setUpdaterManager(IUpdaterManager _updaterManager) internal {
require(
Address.isContract(address(_updaterManager)),
"!contract updaterManager"
);
updaterManager = IUpdaterManager(_updaterManager);
emit NewUpdaterManager(address(_updaterManager));
}
/**
* @notice Slash the Updater and set contract state to FAILED
* @dev Called when fraud is proven (Improper Update or Double Update)
*/
function _fail() internal {
// set contract to FAILED
state = States.Failed;
// slash Updater
updaterManager.slashUpdater(msg.sender);
emit UpdaterSlashed(updater, msg.sender);
}
/**
* @notice Internal utility function that combines
* `_destination` and `_nonce`.
* @dev Both destination and nonce should be less than 2^32 - 1
* @param _destination Domain of destination chain
* @param _nonce Current nonce for given destination chain
* @return Returns (`_destination` << 32) & `_nonce`
*/
function _destinationAndNonce(uint32 _destination, uint32 _nonce)
internal
pure
returns (uint64)
{
return (uint64(_destination) << 32) | _nonce;
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.7.6;
interface IMessageRecipient {
function handle(
uint32 _origin,
uint32 _nonce,
bytes32 _sender,
bytes memory _message
) external;
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.7.6;
interface IUpdaterManager {
function slashUpdater(address payable _reporter) external;
function updater() external view returns (address);
}
// SPDX-License-Identifier: MIT
// solhint-disable-next-line compiler-version
pragma solidity >=0.4.24 <0.8.0;
import "../utils/Address.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {UpgradeableProxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
*/
bool private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Modifier to protect an initializer function from being invoked twice.
*/
modifier initializer() {
require(_initializing || _isConstructor() || !_initialized, "Initializable: contract is already initialized");
bool isTopLevelCall = !_initializing;
if (isTopLevelCall) {
_initializing = true;
_initialized = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
}
}
/// @dev Returns true if and only if the function is running in the constructor
function _isConstructor() private view returns (bool) {
return !Address.isContract(address(this));
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.7.6;
// work based on eth2 deposit contract, which is used under CC0-1.0
/**
* @title MerkleLib
* @author Illusory Systems Inc.
* @notice An incremental merkle tree modeled on the eth2 deposit contract.
**/
library MerkleLib {
uint256 internal constant TREE_DEPTH = 32;
uint256 internal constant MAX_LEAVES = 2**TREE_DEPTH - 1;
/**
* @notice Struct representing incremental merkle tree. Contains current
* branch and the number of inserted leaves in the tree.
**/
struct Tree {
bytes32[TREE_DEPTH] branch;
uint256 count;
}
/**
* @notice Inserts `_node` into merkle tree
* @dev Reverts if tree is full
* @param _node Element to insert into tree
**/
function insert(Tree storage _tree, bytes32 _node) internal {
require(_tree.count < MAX_LEAVES, "merkle tree full");
_tree.count += 1;
uint256 size = _tree.count;
for (uint256 i = 0; i < TREE_DEPTH; i++) {
if ((size & 1) == 1) {
_tree.branch[i] = _node;
return;
}
_node = keccak256(abi.encodePacked(_tree.branch[i], _node));
size /= 2;
}
// As the loop should always end prematurely with the `return` statement,
// this code should be unreachable. We assert `false` just to be safe.
assert(false);
}
/**
* @notice Calculates and returns`_tree`'s current root given array of zero
* hashes
* @param _zeroes Array of zero hashes
* @return _current Calculated root of `_tree`
**/
function rootWithCtx(Tree storage _tree, bytes32[TREE_DEPTH] memory _zeroes)
internal
view
returns (bytes32 _current)
{
uint256 _index = _tree.count;
for (uint256 i = 0; i < TREE_DEPTH; i++) {
uint256 _ithBit = (_index >> i) & 0x01;
bytes32 _next = _tree.branch[i];
if (_ithBit == 1) {
_current = keccak256(abi.encodePacked(_next, _current));
} else {
_current = keccak256(abi.encodePacked(_current, _zeroes[i]));
}
}
}
/// @notice Calculates and returns`_tree`'s current root
function root(Tree storage _tree) internal view returns (bytes32) {
return rootWithCtx(_tree, zeroHashes());
}
/// @notice Returns array of TREE_DEPTH zero hashes
/// @return _zeroes Array of TREE_DEPTH zero hashes
function zeroHashes()
internal
pure
returns (bytes32[TREE_DEPTH] memory _zeroes)
{
_zeroes[0] = Z_0;
_zeroes[1] = Z_1;
_zeroes[2] = Z_2;
_zeroes[3] = Z_3;
_zeroes[4] = Z_4;
_zeroes[5] = Z_5;
_zeroes[6] = Z_6;
_zeroes[7] = Z_7;
_zeroes[8] = Z_8;
_zeroes[9] = Z_9;
_zeroes[10] = Z_10;
_zeroes[11] = Z_11;
_zeroes[12] = Z_12;
_zeroes[13] = Z_13;
_zeroes[14] = Z_14;
_zeroes[15] = Z_15;
_zeroes[16] = Z_16;
_zeroes[17] = Z_17;
_zeroes[18] = Z_18;
_zeroes[19] = Z_19;
_zeroes[20] = Z_20;
_zeroes[21] = Z_21;
_zeroes[22] = Z_22;
_zeroes[23] = Z_23;
_zeroes[24] = Z_24;
_zeroes[25] = Z_25;
_zeroes[26] = Z_26;
_zeroes[27] = Z_27;
_zeroes[28] = Z_28;
_zeroes[29] = Z_29;
_zeroes[30] = Z_30;
_zeroes[31] = Z_31;
}
/**
* @notice Calculates and returns the merkle root for the given leaf
* `_item`, a merkle branch, and the index of `_item` in the tree.
* @param _item Merkle leaf
* @param _branch Merkle proof
* @param _index Index of `_item` in tree
* @return _current Calculated merkle root
**/
function branchRoot(
bytes32 _item,
bytes32[TREE_DEPTH] memory _branch,
uint256 _index
) internal pure returns (bytes32 _current) {
_current = _item;
for (uint256 i = 0; i < TREE_DEPTH; i++) {
uint256 _ithBit = (_index >> i) & 0x01;
bytes32 _next = _branch[i];
if (_ithBit == 1) {
_current = keccak256(abi.encodePacked(_next, _current));
} else {
_current = keccak256(abi.encodePacked(_current, _next));
}
}
}
// keccak256 zero hashes
bytes32 internal constant Z_0 =
hex"0000000000000000000000000000000000000000000000000000000000000000";
bytes32 internal constant Z_1 =
hex"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5";
bytes32 internal constant Z_2 =
hex"b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30";
bytes32 internal constant Z_3 =
hex"21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85";
bytes32 internal constant Z_4 =
hex"e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344";
bytes32 internal constant Z_5 =
hex"0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d";
bytes32 internal constant Z_6 =
hex"887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968";
bytes32 internal constant Z_7 =
hex"ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83";
bytes32 internal constant Z_8 =
hex"9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af";
bytes32 internal constant Z_9 =
hex"cefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0";
bytes32 internal constant Z_10 =
hex"f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5";
bytes32 internal constant Z_11 =
hex"f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892";
bytes32 internal constant Z_12 =
hex"3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c";
bytes32 internal constant Z_13 =
hex"c1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb";
bytes32 internal constant Z_14 =
hex"5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc";
bytes32 internal constant Z_15 =
hex"da7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2";
bytes32 internal constant Z_16 =
hex"2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f";
bytes32 internal constant Z_17 =
hex"e1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a";
bytes32 internal constant Z_18 =
hex"5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0";
bytes32 internal constant Z_19 =
hex"b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0";
bytes32 internal constant Z_20 =
hex"c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2";
bytes32 internal constant Z_21 =
hex"f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9";
bytes32 internal constant Z_22 =
hex"5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377";
bytes32 internal constant Z_23 =
hex"4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652";
bytes32 internal constant Z_24 =
hex"cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef";
bytes32 internal constant Z_25 =
hex"0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d";
bytes32 internal constant Z_26 =
hex"b8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0";
bytes32 internal constant Z_27 =
hex"838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e";
bytes32 internal constant Z_28 =
hex"662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e";
bytes32 internal constant Z_29 =
hex"388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322";
bytes32 internal constant Z_30 =
hex"93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735";
bytes32 internal constant Z_31 =
hex"8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9";
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.7.6;
import "@summa-tx/memview-sol/contracts/TypedMemView.sol";
import {TypeCasts} from "./TypeCasts.sol";
/**
* @title Message Library
* @author Illusory Systems Inc.
* @notice Library for formatted messages used by Home and Replica.
**/
library Message {
using TypedMemView for bytes;
using TypedMemView for bytes29;
// Number of bytes in formatted message before `body` field
uint256 internal constant PREFIX_LENGTH = 76;
/**
* @notice Returns formatted (packed) message with provided fields
* @param _originDomain Domain of home chain
* @param _sender Address of sender as bytes32
* @param _nonce Destination-specific nonce
* @param _destinationDomain Domain of destination chain
* @param _recipient Address of recipient on destination chain as bytes32
* @param _messageBody Raw bytes of message body
* @return Formatted message
**/
function formatMessage(
uint32 _originDomain,
bytes32 _sender,
uint32 _nonce,
uint32 _destinationDomain,
bytes32 _recipient,
bytes memory _messageBody
) internal pure returns (bytes memory) {
return
abi.encodePacked(
_originDomain,
_sender,
_nonce,
_destinationDomain,
_recipient,
_messageBody
);
}
/**
* @notice Returns leaf of formatted message with provided fields.
* @param _origin Domain of home chain
* @param _sender Address of sender as bytes32
* @param _nonce Destination-specific nonce number
* @param _destination Domain of destination chain
* @param _recipient Address of recipient on destination chain as bytes32
* @param _body Raw bytes of message body
* @return Leaf (hash) of formatted message
**/
function messageHash(
uint32 _origin,
bytes32 _sender,
uint32 _nonce,
uint32 _destination,
bytes32 _recipient,
bytes memory _body
) internal pure returns (bytes32) {
return
keccak256(
formatMessage(
_origin,
_sender,
_nonce,
_destination,
_recipient,
_body
)
);
}
/// @notice Returns message's origin field
function origin(bytes29 _message) internal pure returns (uint32) {
return uint32(_message.indexUint(0, 4));
}
/// @notice Returns message's sender field
function sender(bytes29 _message) internal pure returns (bytes32) {
return _message.index(4, 32);
}
/// @notice Returns message's nonce field
function nonce(bytes29 _message) internal pure returns (uint32) {
return uint32(_message.indexUint(36, 4));
}
/// @notice Returns message's destination field
function destination(bytes29 _message) internal pure returns (uint32) {
return uint32(_message.indexUint(40, 4));
}
/// @notice Returns message's recipient field as bytes32
function recipient(bytes29 _message) internal pure returns (bytes32) {
return _message.index(44, 32);
}
/// @notice Returns message's recipient field as an address
function recipientAddress(bytes29 _message)
internal
pure
returns (address)
{
return TypeCasts.bytes32ToAddress(recipient(_message));
}
/// @notice Returns message's body field as bytes29 (refer to TypedMemView library for details on bytes29 type)
function body(bytes29 _message) internal pure returns (bytes29) {
return _message.slice(PREFIX_LENGTH, _message.len() - PREFIX_LENGTH, 0);
}
function leaf(bytes29 _message) internal view returns (bytes32) {
return
messageHash(
origin(_message),
sender(_message),
nonce(_message),
destination(_message),
recipient(_message),
TypedMemView.clone(body(_message))
);
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.7.6;
// ============ Internal Imports ============
import {Message} from "./libs/Message.sol";
// ============ External Imports ============
import {ECDSA} from "@openzeppelin/contracts/cryptography/ECDSA.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
/**
* @title NomadBase
* @author Illusory Systems Inc.
* @notice Shared utilities between Home and Replica.
*/
abstract contract NomadBase is Initializable, OwnableUpgradeable {
// ============ Enums ============
// States:
// 0 - UnInitialized - before initialize function is called
// note: the contract is initialized at deploy time, so it should never be in this state
// 1 - Active - as long as the contract has not become fraudulent
// 2 - Failed - after a valid fraud proof has been submitted;
// contract will no longer accept updates or new messages
enum States {
UnInitialized,
Active,
Failed
}
// ============ Immutable Variables ============
// Domain of chain on which the contract is deployed
uint32 public immutable localDomain;
// ============ Public Variables ============
// Address of bonded Updater
address public updater;
// Current state of contract
States public state;
// The latest root that has been signed by the Updater
bytes32 public committedRoot;
// ============ Upgrade Gap ============
// gap for upgrade safety
uint256[47] private __GAP;
// ============ Events ============
/**
* @notice Emitted when update is made on Home
* or unconfirmed update root is submitted on Replica
* @param homeDomain Domain of home contract
* @param oldRoot Old merkle root
* @param newRoot New merkle root
* @param signature Updater's signature on `oldRoot` and `newRoot`
*/
event Update(
uint32 indexed homeDomain,
bytes32 indexed oldRoot,
bytes32 indexed newRoot,
bytes signature
);
/**
* @notice Emitted when Updater is rotated
* @param oldUpdater The address of the old updater
* @param newUpdater The address of the new updater
*/
event NewUpdater(address oldUpdater, address newUpdater);
// ============ Constructor ============
constructor(uint32 _localDomain) {
localDomain = _localDomain;
}
// ============ Initializer ============
function __NomadBase_initialize(address _updater) internal initializer {
__Ownable_init();
_setUpdater(_updater);
state = States.Active;
}
// ============ Public Functions ============
/**
* @notice Hash of Home domain concatenated with "NOMAD"
*/
function homeDomainHash() public view virtual returns (bytes32);
// ============ Internal Functions ============
/**
* @notice Hash of Home domain concatenated with "NOMAD"
* @param _homeDomain the Home domain to hash
*/
function _homeDomainHash(uint32 _homeDomain)
internal
pure
returns (bytes32)
{
return keccak256(abi.encodePacked(_homeDomain, "NOMAD"));
}
/**
* @notice Set the Updater
* @param _newUpdater Address of the new Updater
*/
function _setUpdater(address _newUpdater) internal {
address _oldUpdater = updater;
updater = _newUpdater;
emit NewUpdater(_oldUpdater, _newUpdater);
}
/**
* @notice Checks that signature was signed by Updater
* @param _oldRoot Old merkle root
* @param _newRoot New merkle root
* @param _signature Signature on `_oldRoot` and `_newRoot`
* @return TRUE iff signature is valid signed by updater
**/
function _isUpdaterSignature(
bytes32 _oldRoot,
bytes32 _newRoot,
bytes memory _signature
) internal view returns (bool) {
bytes32 _digest = keccak256(
abi.encodePacked(homeDomainHash(), _oldRoot, _newRoot)
);
_digest = ECDSA.toEthSignedMessageHash(_digest);
return (ECDSA.recover(_digest, _signature) == updater);
}
/**
* @dev should be impossible to renounce ownership;
* we override OpenZeppelin OwnableUpgradeable's
* implementation of renounceOwnership to make it a no-op
*/
function renounceOwnership() public override onlyOwner {
// do nothing
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor () internal {
address msgSender = _msgSender();
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
import "../utils/ContextUpgradeable.sol";
import "../proxy/Initializable.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
function __Ownable_init() internal initializer {
__Context_init_unchained();
__Ownable_init_unchained();
}
function __Ownable_init_unchained() internal initializer {
address msgSender = _msgSender();
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
uint256[49] private __gap;
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.7.6;
// ============ Internal Imports ============
import {QueueLib} from "./libs/Queue.sol";
// ============ External Imports ============
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol";
/**
* @title QueueManager
* @author Illusory Systems Inc.
* @notice Contains a queue instance and
* exposes view functions for the queue.
**/
contract QueueManager is Initializable {
// ============ Libraries ============
using QueueLib for QueueLib.Queue;
QueueLib.Queue internal queue;
// ============ Upgrade Gap ============
// gap for upgrade safety
uint256[49] private __GAP;
// ============ Initializer ============
function __QueueManager_initialize() internal initializer {
queue.initialize();
}
// ============ Public Functions ============
/**
* @notice Returns number of elements in queue
*/
function queueLength() external view returns (uint256) {
return queue.length();
}
/**
* @notice Returns TRUE iff `_item` is in the queue
*/
function queueContains(bytes32 _item) external view returns (bool) {
return queue.contains(_item);
}
/**
* @notice Returns last item enqueued to the queue
*/
function queueEnd() external view returns (bytes32) {
return queue.lastItem();
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.7.6;
// ============ Internal Imports ============
import {Version0} from "./Version0.sol";
import {NomadBase} from "./NomadBase.sol";
import {MerkleLib} from "./libs/Merkle.sol";
import {Message} from "./libs/Message.sol";
import {IMessageRecipient} from "./interfaces/IMessageRecipient.sol";
// ============ External Imports ============
import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol";
/**
* @title Replica
* @author Illusory Systems Inc.
* @notice Track root updates on Home,
* prove and dispatch messages to end recipients.
*/
contract Replica is Version0, NomadBase {
// ============ Libraries ============
using MerkleLib for MerkleLib.Tree;
using TypedMemView for bytes;
using TypedMemView for bytes29;
using Message for bytes29;
// ============ Constants ============
bytes32 public constant LEGACY_STATUS_NONE = bytes32(0);
bytes32 public constant LEGACY_STATUS_PROVEN = bytes32(uint256(1));
bytes32 public constant LEGACY_STATUS_PROCESSED = bytes32(uint256(2));
// ============ Public Storage ============
// Domain of home chain
uint32 public remoteDomain;
// Number of seconds to wait before root becomes confirmable
uint256 public optimisticSeconds;
// re-entrancy guard
uint8 private entered;
// Mapping of roots to allowable confirmation times
mapping(bytes32 => uint256) public confirmAt;
// Mapping of message leaves to MessageStatus
mapping(bytes32 => bytes32) public messages;
// ============ Upgrade Gap ============
// gap for upgrade safety
uint256[45] private __GAP;
// ============ Events ============
/**
* @notice Emitted when message is processed
* @param messageHash The keccak256 hash of the message that was processed
* @param success TRUE if the call was executed successfully,
* FALSE if the call reverted or threw
* @param returnData the return data from the external call
*/
event Process(
bytes32 indexed messageHash,
bool indexed success,
bytes indexed returnData
);
/**
* @notice Emitted when the value for optimisticTimeout is set
* @param timeout The new value for optimistic timeout
*/
event SetOptimisticTimeout(uint256 timeout);
/**
* @notice Emitted when a root's confirmation is modified by governance
* @param root The root for which confirmAt has been set
* @param previousConfirmAt The previous value of confirmAt
* @param newConfirmAt The new value of confirmAt
*/
event SetConfirmation(
bytes32 indexed root,
uint256 previousConfirmAt,
uint256 newConfirmAt
);
// ============ Constructor ============
constructor(uint32 _localDomain) NomadBase(_localDomain) {}
// ============ Initializer ============
/**
* @notice Initialize the replica
* @dev Performs the following action:
* - initializes inherited contracts
* - initializes re-entrancy guard
* - sets remote domain
* - sets a trusted root, and pre-approves messages under it
* - sets the optimistic timer
* @param _remoteDomain The domain of the Home contract this follows
* @param _updater The EVM id of the updater
* @param _committedRoot A trusted root from which to start the Replica
* @param _optimisticSeconds The time a new root must wait to be confirmed
*/
function initialize(
uint32 _remoteDomain,
address _updater,
bytes32 _committedRoot,
uint256 _optimisticSeconds
) public initializer {
__NomadBase_initialize(_updater);
// set storage variables
entered = 1;
remoteDomain = _remoteDomain;
committedRoot = _committedRoot;
// pre-approve the committed root.
if (_committedRoot != bytes32(0)) confirmAt[_committedRoot] = 1;
_setOptimisticTimeout(_optimisticSeconds);
}
// ============ External Functions ============
/**
* @notice Called by external agent. Submits the signed update's new root,
* marks root's allowable confirmation time, and emits an `Update` event.
* @dev Reverts if update doesn't build off latest committedRoot
* or if signature is invalid.
* @param _oldRoot Old merkle root
* @param _newRoot New merkle root
* @param _signature Updater's signature on `_oldRoot` and `_newRoot`
*/
function update(
bytes32 _oldRoot,
bytes32 _newRoot,
bytes memory _signature
) external {
// ensure that update is building off the last submitted root
require(_oldRoot == committedRoot, "not current update");
// validate updater signature
require(
_isUpdaterSignature(_oldRoot, _newRoot, _signature),
"!updater sig"
);
// Hook for future use
_beforeUpdate();
// set the new root's confirmation timer
confirmAt[_newRoot] = block.timestamp + optimisticSeconds;
// update committedRoot
committedRoot = _newRoot;
emit Update(remoteDomain, _oldRoot, _newRoot, _signature);
}
/**
* @notice If necessary, attempts to prove the validity of provided
* `_message`. If the message is successfully proven, then tries to
* process the message
* @dev Reverts if `prove` call returns false
* @param _message A Nomad message coming from another chain :)
* @param _proof Merkle proof of inclusion for message's leaf (optional if
* the message has already been proven).
* @param _index Index of leaf in home's merkle tree (optional if the
* message has already been proven).
*/
function proveAndProcess(
bytes memory _message,
bytes32[32] calldata _proof,
uint256 _index
) external {
bytes32 _messageHash = keccak256(_message);
require(
acceptableRoot(messages[_messageHash]) ||
prove(_messageHash, _proof, _index),
"!prove"
);
process(_message);
}
/**
* @notice Given formatted message, attempts to dispatch
* message payload to end recipient.
* @dev Recipient must implement a `handle` method (refer to IMessageRecipient.sol)
* Reverts if formatted message's destination domain is not the Replica's domain,
* if message has not been proven,
* or if not enough gas is provided for the dispatch transaction.
* @param _message Formatted message
* @return _success TRUE iff dispatch transaction succeeded
*/
function process(bytes memory _message) public returns (bool _success) {
// ensure message was meant for this domain
bytes29 _m = _message.ref(0);
require(_m.destination() == localDomain, "!destination");
// ensure message has been proven
bytes32 _messageHash = _m.keccak();
require(acceptableRoot(messages[_messageHash]), "!proven");
// check re-entrancy guard
require(entered == 1, "!reentrant");
entered = 0;
// update message status as processed
messages[_messageHash] = LEGACY_STATUS_PROCESSED;
// call handle function
IMessageRecipient(_m.recipientAddress()).handle(
_m.origin(),
_m.nonce(),
_m.sender(),
_m.body().clone()
);
// emit process results
emit Process(_messageHash, true, "");
// reset re-entrancy guard
entered = 1;
// return true
return true;
}
// ============ External Owner Functions ============
/**
* @notice Set optimistic timeout period for new roots
* @dev Only callable by owner (Governance)
* @param _optimisticSeconds New optimistic timeout period
*/
function setOptimisticTimeout(uint256 _optimisticSeconds)
external
onlyOwner
{
_setOptimisticTimeout(_optimisticSeconds);
}
/**
* @notice Set Updater role
* @dev MUST ensure that all roots signed by previous Updater have
* been relayed before calling. Only callable by owner (Governance)
* @param _updater New Updater
*/
function setUpdater(address _updater) external onlyOwner {
_setUpdater(_updater);
}
/**
* @notice Set confirmAt for a given root
* @dev To be used if in the case that fraud is proven
* and roots need to be deleted / added. Only callable by owner (Governance)
* @param _root The root for which to modify confirm time
* @param _confirmAt The new confirmation time. Set to 0 to "delete" a root.
*/
function setConfirmation(bytes32 _root, uint256 _confirmAt)
external
onlyOwner
{
require(_root != bytes32(0) || _confirmAt == 0, "can't set zero root");
uint256 _previousConfirmAt = confirmAt[_root];
confirmAt[_root] = _confirmAt;
emit SetConfirmation(_root, _previousConfirmAt, _confirmAt);
}
// ============ Public Functions ============
/**
* @notice Check that the root has been submitted
* and that the optimistic timeout period has expired,
* meaning the root can be processed
* @param _root the Merkle root, submitted in an update, to check
* @return TRUE iff root has been submitted & timeout has expired
*/
function acceptableRoot(bytes32 _root) public view returns (bool) {
// this is backwards-compatibility for messages proven/processed
// under previous versions
if (_root == LEGACY_STATUS_PROVEN) return true;
if (_root == LEGACY_STATUS_PROCESSED || _root == LEGACY_STATUS_NONE)
return false;
uint256 _time = confirmAt[_root];
if (_time == 0) {
return false;
}
return block.timestamp >= _time;
}
/**
* @notice Attempts to prove the validity of message given its leaf, the
* merkle proof of inclusion for the leaf, and the index of the leaf.
* @dev Reverts if message's MessageStatus != None (i.e. if message was
* already proven or processed)
* @dev For convenience, we allow proving against any previous root.
* This means that witnesses never need to be updated for the new root
* @param _leaf Leaf of message to prove
* @param _proof Merkle proof of inclusion for leaf
* @param _index Index of leaf in home's merkle tree
* @return Returns true if proof was valid and `prove` call succeeded
**/
function prove(
bytes32 _leaf,
bytes32[32] calldata _proof,
uint256 _index
) public returns (bool) {
// ensure that message has not been processed
// Note that this allows re-proving under a new root.
require(
messages[_leaf] != LEGACY_STATUS_PROCESSED,
"already processed"
);
// calculate the expected root based on the proof
bytes32 _calculatedRoot = MerkleLib.branchRoot(_leaf, _proof, _index);
// if the root is valid, change status to Proven
if (acceptableRoot(_calculatedRoot)) {
messages[_leaf] = _calculatedRoot;
return true;
}
return false;
}
/**
* @notice Hash of Home domain concatenated with "NOMAD"
*/
function homeDomainHash() public view override returns (bytes32) {
return _homeDomainHash(remoteDomain);
}
// ============ Internal Functions ============
/**
* @notice Set optimistic timeout period for new roots
* @dev Called by owner (Governance) or at initialization
* @param _optimisticSeconds New optimistic timeout period
*/
function _setOptimisticTimeout(uint256 _optimisticSeconds) internal {
// This allows us to initialize the value to be very low in test envs,
// but does not allow governance action to lower a production env below
// the safe value
uint256 _current = optimisticSeconds;
if (_current != 0 && _current > 1500)
require(_optimisticSeconds >= 1500, "optimistic timeout too low");
// ensure the optimistic timeout is less than 1 year
// (prevents overflow when adding block.timestamp)
require(_optimisticSeconds < 31536000, "optimistic timeout too high");
// set the optimistic timeout
optimisticSeconds = _optimisticSeconds;
emit SetOptimisticTimeout(_optimisticSeconds);
}
/// @notice Hook for potential future use
// solhint-disable-next-line no-empty-blocks
function _beforeUpdate() internal {}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
/**
* @dev Returns the substraction of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b > a) return (false, 0);
return (true, a - b);
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b == 0) return (false, 0);
return (true, a / b);
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b == 0) return (false, 0);
return (true, a % b);
}
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
return a - b;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) return 0;
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: division by zero");
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: modulo by zero");
return a % b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {trySub}.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
return a - b;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting with custom message on
* division by zero. The result is rounded towards zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryDiv}.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting with custom message when dividing by zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryMod}.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
return a % b;
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.7.6;
import "@summa-tx/memview-sol/contracts/TypedMemView.sol";
library TypeCasts {
using TypedMemView for bytes;
using TypedMemView for bytes29;
function coerceBytes32(string memory _s)
internal
pure
returns (bytes32 _b)
{
_b = bytes(_s).ref(0).index(0, uint8(bytes(_s).length));
}
// treat it as a null-terminated string of max 32 bytes
function coerceString(bytes32 _buf)
internal
pure
returns (string memory _newStr)
{
uint8 _slen = 0;
while (_slen < 32 && _buf[_slen] != 0) {
_slen++;
}
// solhint-disable-next-line no-inline-assembly
assembly {
_newStr := mload(0x40)
mstore(0x40, add(_newStr, 0x40)) // may end up with extra
mstore(_newStr, _slen)
mstore(add(_newStr, 0x20), _buf)
}
}
// alignment preserving cast
function addressToBytes32(address _addr) internal pure returns (bytes32) {
return bytes32(uint256(uint160(_addr)));
}
// alignment preserving cast
function bytes32ToAddress(bytes32 _buf) internal pure returns (address) {
return address(uint160(uint256(_buf)));
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.5.10 <0.8.0;
import {SafeMath} from "./SafeMath.sol";
library TypedMemView {
using SafeMath for uint256;
// Why does this exist?
// the solidity `bytes memory` type has a few weaknesses.
// 1. You can't index ranges effectively
// 2. You can't slice without copying
// 3. The underlying data may represent any type
// 4. Solidity never deallocates memory, and memory costs grow
// superlinearly
// By using a memory view instead of a `bytes memory` we get the following
// advantages:
// 1. Slices are done on the stack, by manipulating the pointer
// 2. We can index arbitrary ranges and quickly convert them to stack types
// 3. We can insert type info into the pointer, and typecheck at runtime
// This makes `TypedMemView` a useful tool for efficient zero-copy
// algorithms.
// Why bytes29?
// We want to avoid confusion between views, digests, and other common
// types so we chose a large and uncommonly used odd number of bytes
//
// Note that while bytes are left-aligned in a word, integers and addresses
// are right-aligned. This means when working in assembly we have to
// account for the 3 unused bytes on the righthand side
//
// First 5 bytes are a type flag.
// - ff_ffff_fffe is reserved for unknown type.
// - ff_ffff_ffff is reserved for invalid types/errors.
// next 12 are memory address
// next 12 are len
// bottom 3 bytes are empty
// Assumptions:
// - non-modification of memory.
// - No Solidity updates
// - - wrt free mem point
// - - wrt bytes representation in memory
// - - wrt memory addressing in general
// Usage:
// - create type constants
// - use `assertType` for runtime type assertions
// - - unfortunately we can't do this at compile time yet :(
// - recommended: implement modifiers that perform type checking
// - - e.g.
// - - `uint40 constant MY_TYPE = 3;`
// - - ` modifer onlyMyType(bytes29 myView) { myView.assertType(MY_TYPE); }`
// - instantiate a typed view from a bytearray using `ref`
// - use `index` to inspect the contents of the view
// - use `slice` to create smaller views into the same memory
// - - `slice` can increase the offset
// - - `slice can decrease the length`
// - - must specify the output type of `slice`
// - - `slice` will return a null view if you try to overrun
// - - make sure to explicitly check for this with `notNull` or `assertType`
// - use `equal` for typed comparisons.
// The null view
bytes29 public constant NULL = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
uint256 constant LOW_12_MASK = 0xffffffffffffffffffffffff;
uint8 constant TWELVE_BYTES = 96;
/**
* @notice Returns the encoded hex character that represents the lower 4 bits of the argument.
* @param _b The byte
* @return char - The encoded hex character
*/
function nibbleHex(uint8 _b) internal pure returns (uint8 char) {
// This can probably be done more efficiently, but it's only in error
// paths, so we don't really care :)
uint8 _nibble = _b | 0xf0; // set top 4, keep bottom 4
if (_nibble == 0xf0) {return 0x30;} // 0
if (_nibble == 0xf1) {return 0x31;} // 1
if (_nibble == 0xf2) {return 0x32;} // 2
if (_nibble == 0xf3) {return 0x33;} // 3
if (_nibble == 0xf4) {return 0x34;} // 4
if (_nibble == 0xf5) {return 0x35;} // 5
if (_nibble == 0xf6) {return 0x36;} // 6
if (_nibble == 0xf7) {return 0x37;} // 7
if (_nibble == 0xf8) {return 0x38;} // 8
if (_nibble == 0xf9) {return 0x39;} // 9
if (_nibble == 0xfa) {return 0x61;} // a
if (_nibble == 0xfb) {return 0x62;} // b
if (_nibble == 0xfc) {return 0x63;} // c
if (_nibble == 0xfd) {return 0x64;} // d
if (_nibble == 0xfe) {return 0x65;} // e
if (_nibble == 0xff) {return 0x66;} // f
}
/**
* @notice Returns a uint16 containing the hex-encoded byte.
* @param _b The byte
* @return encoded - The hex-encoded byte
*/
function byteHex(uint8 _b) internal pure returns (uint16 encoded) {
encoded |= nibbleHex(_b >> 4); // top 4 bits
encoded <<= 8;
encoded |= nibbleHex(_b); // lower 4 bits
}
/**
* @notice Encodes the uint256 to hex. `first` contains the encoded top 16 bytes.
* `second` contains the encoded lower 16 bytes.
*
* @param _b The 32 bytes as uint256
* @return first - The top 16 bytes
* @return second - The bottom 16 bytes
*/
function encodeHex(uint256 _b) internal pure returns (uint256 first, uint256 second) {
for (uint8 i = 31; i > 15; i -= 1) {
uint8 _byte = uint8(_b >> (i * 8));
first |= byteHex(_byte);
if (i != 16) {
first <<= 16;
}
}
// abusing underflow here =_=
for (uint8 i = 15; i < 255 ; i -= 1) {
uint8 _byte = uint8(_b >> (i * 8));
second |= byteHex(_byte);
if (i != 0) {
second <<= 16;
}
}
}
/**
* @notice Changes the endianness of a uint256.
* @dev https://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel
* @param _b The unsigned integer to reverse
* @return v - The reversed value
*/
function reverseUint256(uint256 _b) internal pure returns (uint256 v) {
v = _b;
// swap bytes
v = ((v >> 8) & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) |
((v & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) << 8);
// swap 2-byte long pairs
v = ((v >> 16) & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) |
((v & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) << 16);
// swap 4-byte long pairs
v = ((v >> 32) & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) |
((v & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) << 32);
// swap 8-byte long pairs
v = ((v >> 64) & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) |
((v & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) << 64);
// swap 16-byte long pairs
v = (v >> 128) | (v << 128);
}
/**
* @notice Create a mask with the highest `_len` bits set.
* @param _len The length
* @return mask - The mask
*/
function leftMask(uint8 _len) private pure returns (uint256 mask) {
// ugly. redo without assembly?
assembly {
// solium-disable-previous-line security/no-inline-assembly
mask := sar(
sub(_len, 1),
0x8000000000000000000000000000000000000000000000000000000000000000
)
}
}
/**
* @notice Return the null view.
* @return bytes29 - The null view
*/
function nullView() internal pure returns (bytes29) {
return NULL;
}
/**
* @notice Check if the view is null.
* @return bool - True if the view is null
*/
function isNull(bytes29 memView) internal pure returns (bool) {
return memView == NULL;
}
/**
* @notice Check if the view is not null.
* @return bool - True if the view is not null
*/
function notNull(bytes29 memView) internal pure returns (bool) {
return !isNull(memView);
}
/**
* @notice Check if the view is of a valid type and points to a valid location
* in memory.
* @dev We perform this check by examining solidity's unallocated memory
* pointer and ensuring that the view's upper bound is less than that.
* @param memView The view
* @return ret - True if the view is valid
*/
function isValid(bytes29 memView) internal pure returns (bool ret) {
if (typeOf(memView) == 0xffffffffff) {return false;}
uint256 _end = end(memView);
assembly {
// solium-disable-previous-line security/no-inline-assembly
ret := not(gt(_end, mload(0x40)))
}
}
/**
* @notice Require that a typed memory view be valid.
* @dev Returns the view for easy chaining.
* @param memView The view
* @return bytes29 - The validated view
*/
function assertValid(bytes29 memView) internal pure returns (bytes29) {
require(isValid(memView), "Validity assertion failed");
return memView;
}
/**
* @notice Return true if the memview is of the expected type. Otherwise false.
* @param memView The view
* @param _expected The expected type
* @return bool - True if the memview is of the expected type
*/
function isType(bytes29 memView, uint40 _expected) internal pure returns (bool) {
return typeOf(memView) == _expected;
}
/**
* @notice Require that a typed memory view has a specific type.
* @dev Returns the view for easy chaining.
* @param memView The view
* @param _expected The expected type
* @return bytes29 - The view with validated type
*/
function assertType(bytes29 memView, uint40 _expected) internal pure returns (bytes29) {
if (!isType(memView, _expected)) {
(, uint256 g) = encodeHex(uint256(typeOf(memView)));
(, uint256 e) = encodeHex(uint256(_expected));
string memory err = string(
abi.encodePacked(
"Type assertion failed. Got 0x",
uint80(g),
". Expected 0x",
uint80(e)
)
);
revert(err);
}
return memView;
}
/**
* @notice Return an identical view with a different type.
* @param memView The view
* @param _newType The new type
* @return newView - The new view with the specified type
*/
function castTo(bytes29 memView, uint40 _newType) internal pure returns (bytes29 newView) {
// then | in the new type
assembly {
// solium-disable-previous-line security/no-inline-assembly
// shift off the top 5 bytes
newView := or(newView, shr(40, shl(40, memView)))
newView := or(newView, shl(216, _newType))
}
}
/**
* @notice Unsafe raw pointer construction. This should generally not be called
* directly. Prefer `ref` wherever possible.
* @dev Unsafe raw pointer construction. This should generally not be called
* directly. Prefer `ref` wherever possible.
* @param _type The type
* @param _loc The memory address
* @param _len The length
* @return newView - The new view with the specified type, location and length
*/
function unsafeBuildUnchecked(uint256 _type, uint256 _loc, uint256 _len) private pure returns (bytes29 newView) {
assembly {
// solium-disable-previous-line security/no-inline-assembly
newView := shl(96, or(newView, _type)) // insert type
newView := shl(96, or(newView, _loc)) // insert loc
newView := shl(24, or(newView, _len)) // empty bottom 3 bytes
}
}
/**
* @notice Instantiate a new memory view. This should generally not be called
* directly. Prefer `ref` wherever possible.
* @dev Instantiate a new memory view. This should generally not be called
* directly. Prefer `ref` wherever possible.
* @param _type The type
* @param _loc The memory address
* @param _len The length
* @return newView - The new view with the specified type, location and length
*/
function build(uint256 _type, uint256 _loc, uint256 _len) internal pure returns (bytes29 newView) {
uint256 _end = _loc.add(_len);
assembly {
// solium-disable-previous-line security/no-inline-assembly
if gt(_end, mload(0x40)) {
_end := 0
}
}
if (_end == 0) {
return NULL;
}
newView = unsafeBuildUnchecked(_type, _loc, _len);
}
/**
* @notice Instantiate a memory view from a byte array.
* @dev Note that due to Solidity memory representation, it is not possible to
* implement a deref, as the `bytes` type stores its len in memory.
* @param arr The byte array
* @param newType The type
* @return bytes29 - The memory view
*/
function ref(bytes memory arr, uint40 newType) internal pure returns (bytes29) {
uint256 _len = arr.length;
uint256 _loc;
assembly {
// solium-disable-previous-line security/no-inline-assembly
_loc := add(arr, 0x20) // our view is of the data, not the struct
}
return build(newType, _loc, _len);
}
/**
* @notice Return the associated type information.
* @param memView The memory view
* @return _type - The type associated with the view
*/
function typeOf(bytes29 memView) internal pure returns (uint40 _type) {
assembly {
// solium-disable-previous-line security/no-inline-assembly
// 216 == 256 - 40
_type := shr(216, memView) // shift out lower 24 bytes
}
}
/**
* @notice Optimized type comparison. Checks that the 5-byte type flag is equal.
* @param left The first view
* @param right The second view
* @return bool - True if the 5-byte type flag is equal
*/
function sameType(bytes29 left, bytes29 right) internal pure returns (bool) {
return (left ^ right) >> (2 * TWELVE_BYTES) == 0;
}
/**
* @notice Return the memory address of the underlying bytes.
* @param memView The view
* @return _loc - The memory address
*/
function loc(bytes29 memView) internal pure returns (uint96 _loc) {
uint256 _mask = LOW_12_MASK; // assembly can't use globals
assembly {
// solium-disable-previous-line security/no-inline-assembly
// 120 bits = 12 bytes (the encoded loc) + 3 bytes (empty low space)
_loc := and(shr(120, memView), _mask)
}
}
/**
* @notice The number of memory words this memory view occupies, rounded up.
* @param memView The view
* @return uint256 - The number of memory words
*/
function words(bytes29 memView) internal pure returns (uint256) {
return uint256(len(memView)).add(32) / 32;
}
/**
* @notice The in-memory footprint of a fresh copy of the view.
* @param memView The view
* @return uint256 - The in-memory footprint of a fresh copy of the view.
*/
function footprint(bytes29 memView) internal pure returns (uint256) {
return words(memView) * 32;
}
/**
* @notice The number of bytes of the view.
* @param memView The view
* @return _len - The length of the view
*/
function len(bytes29 memView) internal pure returns (uint96 _len) {
uint256 _mask = LOW_12_MASK; // assembly can't use globals
assembly {
// solium-disable-previous-line security/no-inline-assembly
_len := and(shr(24, memView), _mask)
}
}
/**
* @notice Returns the endpoint of `memView`.
* @param memView The view
* @return uint256 - The endpoint of `memView`
*/
function end(bytes29 memView) internal pure returns (uint256) {
return loc(memView) + len(memView);
}
/**
* @notice Safe slicing without memory modification.
* @param memView The view
* @param _index The start index
* @param _len The length
* @param newType The new type
* @return bytes29 - The new view
*/
function slice(bytes29 memView, uint256 _index, uint256 _len, uint40 newType) internal pure returns (bytes29) {
uint256 _loc = loc(memView);
// Ensure it doesn't overrun the view
if (_loc.add(_index).add(_len) > end(memView)) {
return NULL;
}
_loc = _loc.add(_index);
return build(newType, _loc, _len);
}
/**
* @notice Shortcut to `slice`. Gets a view representing the first `_len` bytes.
* @param memView The view
* @param _len The length
* @param newType The new type
* @return bytes29 - The new view
*/
function prefix(bytes29 memView, uint256 _len, uint40 newType) internal pure returns (bytes29) {
return slice(memView, 0, _len, newType);
}
/**
* @notice Shortcut to `slice`. Gets a view representing the last `_len` byte.
* @param memView The view
* @param _len The length
* @param newType The new type
* @return bytes29 - The new view
*/
function postfix(bytes29 memView, uint256 _len, uint40 newType) internal pure returns (bytes29) {
return slice(memView, uint256(len(memView)).sub(_len), _len, newType);
}
/**
* @notice Construct an error message for an indexing overrun.
* @param _loc The memory address
* @param _len The length
* @param _index The index
* @param _slice The slice where the overrun occurred
* @return err - The err
*/
function indexErrOverrun(
uint256 _loc,
uint256 _len,
uint256 _index,
uint256 _slice
) internal pure returns (string memory err) {
(, uint256 a) = encodeHex(_loc);
(, uint256 b) = encodeHex(_len);
(, uint256 c) = encodeHex(_index);
(, uint256 d) = encodeHex(_slice);
err = string(
abi.encodePacked(
"TypedMemView/index - Overran the view. Slice is at 0x",
uint48(a),
" with length 0x",
uint48(b),
". Attempted to index at offset 0x",
uint48(c),
" with length 0x",
uint48(d),
"."
)
);
}
/**
* @notice Load up to 32 bytes from the view onto the stack.
* @dev Returns a bytes32 with only the `_bytes` highest bytes set.
* This can be immediately cast to a smaller fixed-length byte array.
* To automatically cast to an integer, use `indexUint`.
* @param memView The view
* @param _index The index
* @param _bytes The bytes
* @return result - The 32 byte result
*/
function index(bytes29 memView, uint256 _index, uint8 _bytes) internal pure returns (bytes32 result) {
if (_bytes == 0) {return bytes32(0);}
if (_index.add(_bytes) > len(memView)) {
revert(indexErrOverrun(loc(memView), len(memView), _index, uint256(_bytes)));
}
require(_bytes <= 32, "TypedMemView/index - Attempted to index more than 32 bytes");
uint8 bitLength = _bytes * 8;
uint256 _loc = loc(memView);
uint256 _mask = leftMask(bitLength);
assembly {
// solium-disable-previous-line security/no-inline-assembly
result := and(mload(add(_loc, _index)), _mask)
}
}
/**
* @notice Parse an unsigned integer from the view at `_index`.
* @dev Requires that the view have >= `_bytes` bytes following that index.
* @param memView The view
* @param _index The index
* @param _bytes The bytes
* @return result - The unsigned integer
*/
function indexUint(bytes29 memView, uint256 _index, uint8 _bytes) internal pure returns (uint256 result) {
return uint256(index(memView, _index, _bytes)) >> ((32 - _bytes) * 8);
}
/**
* @notice Parse an unsigned integer from LE bytes.
* @param memView The view
* @param _index The index
* @param _bytes The bytes
* @return result - The unsigned integer
*/
function indexLEUint(bytes29 memView, uint256 _index, uint8 _bytes) internal pure returns (uint256 result) {
return reverseUint256(uint256(index(memView, _index, _bytes)));
}
/**
* @notice Parse an address from the view at `_index`. Requires that the view have >= 20 bytes
* following that index.
* @param memView The view
* @param _index The index
* @return address - The address
*/
function indexAddress(bytes29 memView, uint256 _index) internal pure returns (address) {
return address(uint160(indexUint(memView, _index, 20)));
}
/**
* @notice Return the keccak256 hash of the underlying memory
* @param memView The view
* @return digest - The keccak256 hash of the underlying memory
*/
function keccak(bytes29 memView) internal pure returns (bytes32 digest) {
uint256 _loc = loc(memView);
uint256 _len = len(memView);
assembly {
// solium-disable-previous-line security/no-inline-assembly
digest := keccak256(_loc, _len)
}
}
/**
* @notice Return the sha2 digest of the underlying memory.
* @dev We explicitly deallocate memory afterwards.
* @param memView The view
* @return digest - The sha2 hash of the underlying memory
*/
function sha2(bytes29 memView) internal view returns (bytes32 digest) {
uint256 _loc = loc(memView);
uint256 _len = len(memView);
assembly {
// solium-disable-previous-line security/no-inline-assembly
let ptr := mload(0x40)
pop(staticcall(gas(), 2, _loc, _len, ptr, 0x20)) // sha2 #1
digest := mload(ptr)
}
}
/**
* @notice Implements bitcoin's hash160 (rmd160(sha2()))
* @param memView The pre-image
* @return digest - the Digest
*/
function hash160(bytes29 memView) internal view returns (bytes20 digest) {
uint256 _loc = loc(memView);
uint256 _len = len(memView);
assembly {
// solium-disable-previous-line security/no-inline-assembly
let ptr := mload(0x40)
pop(staticcall(gas(), 2, _loc, _len, ptr, 0x20)) // sha2
pop(staticcall(gas(), 3, ptr, 0x20, ptr, 0x20)) // rmd160
digest := mload(add(ptr, 0xc)) // return value is 0-prefixed.
}
}
/**
* @notice Implements bitcoin's hash256 (double sha2)
* @param memView A view of the preimage
* @return digest - the Digest
*/
function hash256(bytes29 memView) internal view returns (bytes32 digest) {
uint256 _loc = loc(memView);
uint256 _len = len(memView);
assembly {
// solium-disable-previous-line security/no-inline-assembly
let ptr := mload(0x40)
pop(staticcall(gas(), 2, _loc, _len, ptr, 0x20)) // sha2 #1
pop(staticcall(gas(), 2, ptr, 0x20, ptr, 0x20)) // sha2 #2
digest := mload(ptr)
}
}
/**
* @notice Return true if the underlying memory is equal. Else false.
* @param left The first view
* @param right The second view
* @return bool - True if the underlying memory is equal
*/
function untypedEqual(bytes29 left, bytes29 right) internal pure returns (bool) {
return (loc(left) == loc(right) && len(left) == len(right)) || keccak(left) == keccak(right);
}
/**
* @notice Return false if the underlying memory is equal. Else true.
* @param left The first view
* @param right The second view
* @return bool - False if the underlying memory is equal
*/
function untypedNotEqual(bytes29 left, bytes29 right) internal pure returns (bool) {
return !untypedEqual(left, right);
}
/**
* @notice Compares type equality.
* @dev Shortcuts if the pointers are identical, otherwise compares type and digest.
* @param left The first view
* @param right The second view
* @return bool - True if the types are the same
*/
function equal(bytes29 left, bytes29 right) internal pure returns (bool) {
return left == right || (typeOf(left) == typeOf(right) && keccak(left) == keccak(right));
}
/**
* @notice Compares type inequality.
* @dev Shortcuts if the pointers are identical, otherwise compares type and digest.
* @param left The first view
* @param right The second view
* @return bool - True if the types are not the same
*/
function notEqual(bytes29 left, bytes29 right) internal pure returns (bool) {
return !equal(left, right);
}
/**
* @notice Copy the view to a location, return an unsafe memory reference
* @dev Super Dangerous direct memory access.
*
* This reference can be overwritten if anything else modifies memory (!!!).
* As such it MUST be consumed IMMEDIATELY.
* This function is private to prevent unsafe usage by callers.
* @param memView The view
* @param _newLoc The new location
* @return written - the unsafe memory reference
*/
function unsafeCopyTo(bytes29 memView, uint256 _newLoc) private view returns (bytes29 written) {
require(notNull(memView), "TypedMemView/copyTo - Null pointer deref");
require(isValid(memView), "TypedMemView/copyTo - Invalid pointer deref");
uint256 _len = len(memView);
uint256 _oldLoc = loc(memView);
uint256 ptr;
assembly {
// solium-disable-previous-line security/no-inline-assembly
ptr := mload(0x40)
// revert if we're writing in occupied memory
if gt(ptr, _newLoc) {
revert(0x60, 0x20) // empty revert message
}
// use the identity precompile to copy
// guaranteed not to fail, so pop the success
pop(staticcall(gas(), 4, _oldLoc, _len, _newLoc, _len))
}
written = unsafeBuildUnchecked(typeOf(memView), _newLoc, _len);
}
/**
* @notice Copies the referenced memory to a new loc in memory, returning a `bytes` pointing to
* the new memory
* @dev Shortcuts if the pointers are identical, otherwise compares type and digest.
* @param memView The view
* @return ret - The view pointing to the new memory
*/
function clone(bytes29 memView) internal view returns (bytes memory ret) {
uint256 ptr;
uint256 _len = len(memView);
assembly {
// solium-disable-previous-line security/no-inline-assembly
ptr := mload(0x40) // load unused memory pointer
ret := ptr
}
unsafeCopyTo(memView, ptr + 0x20);
assembly {
// solium-disable-previous-line security/no-inline-assembly
mstore(0x40, add(add(ptr, _len), 0x20)) // write new unused pointer
mstore(ptr, _len) // write len of new array (in bytes)
}
}
/**
* @notice Join the views in memory, return an unsafe reference to the memory.
* @dev Super Dangerous direct memory access.
*
* This reference can be overwritten if anything else modifies memory (!!!).
* As such it MUST be consumed IMMEDIATELY.
* This function is private to prevent unsafe usage by callers.
* @param memViews The views
* @return unsafeView - The conjoined view pointing to the new memory
*/
function unsafeJoin(bytes29[] memory memViews, uint256 _location) private view returns (bytes29 unsafeView) {
assembly {
// solium-disable-previous-line security/no-inline-assembly
let ptr := mload(0x40)
// revert if we're writing in occupied memory
if gt(ptr, _location) {
revert(0x60, 0x20) // empty revert message
}
}
uint256 _offset = 0;
for (uint256 i = 0; i < memViews.length; i ++) {
bytes29 memView = memViews[i];
unsafeCopyTo(memView, _location + _offset);
_offset += len(memView);
}
unsafeView = unsafeBuildUnchecked(0, _location, _offset);
}
/**
* @notice Produce the keccak256 digest of the concatenated contents of multiple views.
* @param memViews The views
* @return bytes32 - The keccak256 digest
*/
function joinKeccak(bytes29[] memory memViews) internal view returns (bytes32) {
uint256 ptr;
assembly {
// solium-disable-previous-line security/no-inline-assembly
ptr := mload(0x40) // load unused memory pointer
}
return keccak(unsafeJoin(memViews, ptr));
}
/**
* @notice Produce the sha256 digest of the concatenated contents of multiple views.
* @param memViews The views
* @return bytes32 - The sha256 digest
*/
function joinSha2(bytes29[] memory memViews) internal view returns (bytes32) {
uint256 ptr;
assembly {
// solium-disable-previous-line security/no-inline-assembly
ptr := mload(0x40) // load unused memory pointer
}
return sha2(unsafeJoin(memViews, ptr));
}
/**
* @notice copies all views, joins them into a new bytearray.
* @param memViews The views
* @return ret - The new byte array
*/
function join(bytes29[] memory memViews) internal view returns (bytes memory ret) {
uint256 ptr;
assembly {
// solium-disable-previous-line security/no-inline-assembly
ptr := mload(0x40) // load unused memory pointer
}
bytes29 _newView = unsafeJoin(memViews, ptr + 0x20);
uint256 _written = len(_newView);
uint256 _footprint = footprint(_newView);
assembly {
// solium-disable-previous-line security/no-inline-assembly
// store the legnth
mstore(ptr, _written)
// new pointer is old + 0x20 + the footprint of the body
mstore(0x40, add(add(ptr, _footprint), 0x20))
ret := ptr
}
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.7.6;
/**
* @title Version0
* @notice Version getter for contracts
**/
abstract contract Version0 {
uint8 public constant VERSION = 0;
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.7.6;
// ============ Internal Imports ============
import {Home} from "./Home.sol";
import {Replica} from "./Replica.sol";
import {TypeCasts} from "./libs/TypeCasts.sol";
// ============ External Imports ============
import {ECDSA} from "@openzeppelin/contracts/cryptography/ECDSA.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title XAppConnectionManager
* @author Illusory Systems Inc.
* @notice Manages a registry of local Replica contracts
* for remote Home domains. Accepts Watcher signatures
* to un-enroll Replicas attached to fraudulent remote Homes
*/
contract XAppConnectionManager is Ownable {
// ============ Public Storage ============
// Home contract
Home public home;
// local Replica address => remote Home domain
mapping(address => uint32) public replicaToDomain;
// remote Home domain => local Replica address
mapping(uint32 => address) public domainToReplica;
// watcher address => replica remote domain => has/doesn't have permission
mapping(address => mapping(uint32 => bool)) private watcherPermissions;
// ============ Events ============
/**
* @notice Emitted when a new Replica is enrolled / added
* @param domain the remote domain of the Home contract for the Replica
* @param replica the address of the Replica
*/
event ReplicaEnrolled(uint32 indexed domain, address replica);
/**
* @notice Emitted when a new Replica is un-enrolled / removed
* @param domain the remote domain of the Home contract for the Replica
* @param replica the address of the Replica
*/
event ReplicaUnenrolled(uint32 indexed domain, address replica);
/**
* @notice Emitted when Watcher permissions are changed
* @param domain the remote domain of the Home contract for the Replica
* @param watcher the address of the Watcher
* @param access TRUE if the Watcher was given permissions, FALSE if permissions were removed
*/
event WatcherPermissionSet(
uint32 indexed domain,
address watcher,
bool access
);
// ============ Constructor ============
// solhint-disable-next-line no-empty-blocks
constructor() Ownable() {}
// ============ External Functions ============
/**
* @notice Un-Enroll a replica contract
* in the case that fraud was detected on the Home
* @dev in the future, if fraud occurs on the Home contract,
* the Watcher will submit their signature directly to the Home
* and it can be relayed to all remote chains to un-enroll the Replicas
* @param _domain the remote domain of the Home contract for the Replica
* @param _updater the address of the Updater for the Home contract (also stored on Replica)
* @param _signature signature of watcher on (domain, replica address, updater address)
*/
function unenrollReplica(
uint32 _domain,
bytes32 _updater,
bytes memory _signature
) external {
// ensure that the replica is currently set
address _replica = domainToReplica[_domain];
require(_replica != address(0), "!replica exists");
// ensure that the signature is on the proper updater
require(
Replica(_replica).updater() == TypeCasts.bytes32ToAddress(_updater),
"!current updater"
);
// get the watcher address from the signature
// and ensure that the watcher has permission to un-enroll this replica
address _watcher = _recoverWatcherFromSig(
_domain,
TypeCasts.addressToBytes32(_replica),
_updater,
_signature
);
require(watcherPermissions[_watcher][_domain], "!valid watcher");
// remove the replica from mappings
_clearReplica(_replica);
}
/**
* @notice Set the address of the local Home contract
* @param _home the address of the local Home contract
*/
function setHome(address _home) external onlyOwner {
home = Home(_home);
}
/**
* @notice Allow Owner to enroll Replica contract
* @param _replica the address of the Replica
* @param _domain the remote domain of the Home contract for the Replica
*/
function ownerEnrollReplica(address _replica, uint32 _domain)
external
onlyOwner
{
// un-enroll any existing replica or domain
_clearReplica(_replica);
_clearDomain(_domain);
// add replica and domain to two-way mapping
replicaToDomain[_replica] = _domain;
domainToReplica[_domain] = _replica;
emit ReplicaEnrolled(_domain, _replica);
}
/**
* @notice Allow Owner to un-enroll Replica contract
* @param _replica the address of the Replica
*/
function ownerUnenrollReplica(address _replica) external onlyOwner {
_clearReplica(_replica);
}
/**
* @notice Allow Owner to set Watcher permissions for a Replica
* @param _watcher the address of the Watcher
* @param _domain the remote domain of the Home contract for the Replica
* @param _access TRUE to give the Watcher permissions, FALSE to remove permissions
*/
function setWatcherPermission(
address _watcher,
uint32 _domain,
bool _access
) external onlyOwner {
watcherPermissions[_watcher][_domain] = _access;
emit WatcherPermissionSet(_domain, _watcher, _access);
}
/**
* @notice Query local domain from Home
* @return local domain
*/
function localDomain() external view returns (uint32) {
return home.localDomain();
}
/**
* @notice Get access permissions for the watcher on the domain
* @param _watcher the address of the watcher
* @param _domain the domain to check for watcher permissions
* @return TRUE iff _watcher has permission to un-enroll replicas on _domain
*/
function watcherPermission(address _watcher, uint32 _domain)
external
view
returns (bool)
{
return watcherPermissions[_watcher][_domain];
}
// ============ Public Functions ============
/**
* @notice Check whether _replica is enrolled
* @param _replica the replica to check for enrollment
* @return TRUE iff _replica is enrolled
*/
function isReplica(address _replica) public view returns (bool) {
return replicaToDomain[_replica] != 0;
}
// ============ Internal Functions ============
/**
* @notice Remove the replica from the two-way mappings
* @param _replica replica to un-enroll
*/
function _clearReplica(address _replica) internal {
uint32 _currentDomain = replicaToDomain[_replica];
if (_currentDomain != 0) {
domainToReplica[_currentDomain] = address(0);
replicaToDomain[_replica] = 0;
emit ReplicaUnenrolled(_currentDomain, _replica);
}
}
/**
* @notice remove the domain from the two-way mapping
* @param _domain domain to un-enroll
*/
function _clearDomain(uint32 _domain) internal {
address _currentReplica = domainToReplica[_domain];
if (_currentReplica != address(0)) {
domainToReplica[_domain] = address(0);
replicaToDomain[_currentReplica] = 0;
emit ReplicaUnenrolled(_domain, _currentReplica);
}
}
/**
* @notice Get the Watcher address from the provided signature
* @return address of watcher that signed
*/
function _recoverWatcherFromSig(
uint32 _domain,
bytes32 _replica,
bytes32 _updater,
bytes memory _signature
) internal view returns (address) {
bytes32 _homeDomainHash = Replica(TypeCasts.bytes32ToAddress(_replica))
.homeDomainHash();
bytes32 _digest = keccak256(
abi.encodePacked(_homeDomainHash, _domain, _updater)
);
_digest = ECDSA.toEthSignedMessageHash(_digest);
return ECDSA.recover(_digest, _signature);
}
/**
* @dev should be impossible to renounce ownership;
* we override OpenZeppelin Ownable implementation
* of renounceOwnership to make it a no-op
*/
function renounceOwnership() public override onlyOwner {
// do nothing
}
}
{
"compilationTarget": {
"packages/contracts-core/contracts/governance/GovernanceRouter.sol": "GovernanceRouter"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "none"
},
"optimizer": {
"enabled": true,
"runs": 999999
},
"remappings": [
":@ensdomains/=node_modules/@ensdomains/",
":@nomad-xyz/=packages/",
":@openzeppelin/=node_modules/@openzeppelin/",
":@summa-tx/=node_modules/@summa-tx/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":eth-gas-reporter/=node_modules/eth-gas-reporter/",
":forge-std/=lib/forge-std/src/",
":hardhat/=node_modules/hardhat/"
]
}
[{"inputs":[{"internalType":"uint32","name":"_localDomain","type":"uint32"},{"internalType":"uint256","name":"_recoveryTimelock","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"batchHash","type":"bytes32"}],"name":"BatchExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"batchHash","type":"bytes32"}],"name":"BatchReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"recoveryManager","type":"address"}],"name":"ExitRecovery","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recoveryManager","type":"address"},{"indexed":false,"internalType":"uint256","name":"recoveryActiveAt","type":"uint256"}],"name":"InitiateRecovery","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint32","name":"domain","type":"uint32"},{"indexed":false,"internalType":"bytes32","name":"previousRouter","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"newRouter","type":"bytes32"}],"name":"SetRouter","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"previousGovernorDomain","type":"uint32"},{"indexed":false,"internalType":"uint32","name":"newGovernorDomain","type":"uint32"},{"indexed":true,"internalType":"address","name":"previousGovernor","type":"address"},{"indexed":true,"internalType":"address","name":"newGovernor","type":"address"}],"name":"TransferGovernor","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousRecoveryManager","type":"address"},{"indexed":true,"internalType":"address","name":"newRecoveryManager","type":"address"}],"name":"TransferRecoveryManager","type":"event"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"domains","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"to","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct GovernanceMessage.Call[]","name":"_calls","type":"tuple[]"}],"name":"executeCallBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"to","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct GovernanceMessage.Call[]","name":"_localCalls","type":"tuple[]"},{"internalType":"uint32[]","name":"_domains","type":"uint32[]"},{"components":[{"internalType":"bytes32","name":"to","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct GovernanceMessage.Call[][]","name":"_remoteCalls","type":"tuple[][]"}],"name":"executeGovernanceActions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exitRecovery","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"governor","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"governorDomain","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"_origin","type":"uint32"},{"internalType":"uint32","name":"","type":"uint32"},{"internalType":"bytes32","name":"_sender","type":"bytes32"},{"internalType":"bytes","name":"_message","type":"bytes"}],"name":"handle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"inRecovery","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"inboundCallBatches","outputs":[{"internalType":"enum GovernanceRouter.BatchStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_xAppConnectionManager","type":"address"},{"internalType":"address","name":"_recoveryManager","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initiateRecoveryTimelock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"localDomain","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"recoveryActiveAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"recoveryManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"recoveryTimelock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"","type":"uint32"}],"name":"routers","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"_domain","type":"uint32"},{"internalType":"bytes32","name":"_router","type":"bytes32"}],"name":"setRouterGlobal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"_domain","type":"uint32"},{"internalType":"bytes32","name":"_router","type":"bytes32"}],"name":"setRouterLocal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_xAppConnectionManager","type":"address"}],"name":"setXAppConnectionManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"_newDomain","type":"uint32"},{"internalType":"address","name":"_newGovernor","type":"address"}],"name":"transferGovernor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newRecoveryManager","type":"address"}],"name":"transferRecoveryManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"xAppConnectionManager","outputs":[{"internalType":"contract XAppConnectionManager","name":"","type":"address"}],"stateMutability":"view","type":"function"}]