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

346 lines
13 KiB

// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
// ============ Internal Imports ============
import {Version0} from "./Version0.sol";
import {Common} from "./Common.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 {IUpdaterManager} from "../interfaces/IUpdaterManager.sol";
// ============ External Imports ============
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
/**
* @title Home
* @author Celo Labs 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, MerkleTreeManager, Common, OwnableUpgradeable {
// ============ 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 Optics
* @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 messageHash Hash of message; the leaf inserted to the Merkle tree for the message
* @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 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 Updater is rotated by the UpdaterManager
* @param updater The address of the new updater
*/
event NewUpdater(address updater);
/**
* @notice Emitted when the UpdaterManager contract is changed
* @param updaterManager The address of the new updaterManager
*/
event NewUpdaterManager(address updaterManager);
// ============ Constructor ============
constructor(uint32 _localDomain) Common(_localDomain) {} // solhint-disable-line no-empty-blocks
// ============ Initializer ============
function initialize(IUpdaterManager _updaterManager) public initializer {
// initialize owner
__Ownable_init();
// set Updater Manager contract and initialize Updater
_setUpdaterManager(_updaterManager);
address _updater = updaterManager.updater();
__Common_initialize(_updater);
emit NewUpdater(_updater);
}
// ============ Modifiers ============
/**
* @notice Ensures that function is called by the UpdaterManager contract
*/
modifier onlyUpdaterManager() {
require(msg.sender == address(updaterManager), "!updaterManager");
_;
}
// ============ External: Updater & UpdaterManager Configuration ============
/**
* @notice Set a new Updater
* @param _updater the new Updater
*/
function setUpdater(address _updater) external onlyUpdaterManager {
_setUpdater(_updater);
}
/**
* @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));
}
// ============ External Functions ============
/**
* @notice Dispatch the message it 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();
}
}
// ============ Public Functions ============
/**
* @notice Hash of Home domain concatenated with "OPTICS"
*/
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 Set the Updater
* @param _updater Address of the Updater
*/
function _setUpdater(address _updater) internal {
updater = _updater;
emit NewUpdater(_updater);
}
/**
* @notice Slash the Updater and set contract state to FAILED
* @dev Called when fraud is proven (Improper Update or Double Update)
*/
function _fail() internal override {
// set contract to FAILED
_setFailed();
// 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;
}
}