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

266 lines
8.7 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 Contract responsible for managing production of the message tree and
* holding custody of the updater bond.
*/
contract Home is Version0, MerkleTreeManager, Common, OwnableUpgradeable {
using QueueLib for QueueLib.Queue;
using MerkleLib for MerkleLib.Tree;
/// @notice Set arbitrarily to 2 KiB
uint256 public constant MAX_MESSAGE_BODY_BYTES = 2 * 2**10;
/// @notice Mapping of sequence numbers for each destination
mapping(uint32 => uint32) public sequences;
IUpdaterManager public updaterManager;
uint256[48] private __GAP; // gap for upgrade safety
4 years ago
/**
* @notice Event emitted when new message is enqueued
* @param leafIndex Index of message's leaf in merkle tree
* @param destinationAndSequence Destination and destination-specific
* sequence combined in single field ((destination << 32) & sequence)
* @param leaf Hash of formatted message
* @param message Raw bytes of enqueued message
*/
event Dispatch(
feature: adds untested ProverSync struct to optics-core (#78) * feature: basic ProverSync setup * prog: basic setup for linking enqueuings to roots * refactor: modifies Dispatch event to contain treeSize field and condenses destination and sequence into single field * feature: adds leaf_by_tree_size methods to Home abi and logic for updating local tree in prover_sync * format: runs cargo fmt * prog: comments out unused code from previous design * fix: replaces slip44 references with domain * refactor: moves interval to parameter instead of ProverSync struct field * fix: deletes commented out code from NewLeaf design * refactor: modifies ProverSync error handling to bubble up ProverSyncError type * fix: removes slip44 reference * refactor: adds incremental merkle tree to ProverSync and batch updates prover on successful incremental update * fmt: runs cargo fmt * fix: fixes raw_message_by_sequence filtering after destination and sequence event fields were combined to single field in Home * fix: simplifies error handling, adds retry counter for failed api calls, and clones incremental merkle * fix: renames leaves_by_tree_size to leaves_by_tree_index * fix/docs: fixes Home Dispatch event to use leafIndex instead of treeSize and fixes docs to reflect change * fix: changes num_retries to u32 * refactor: cleans up error handling * docs: adds comment about why leafIndex is count() - 1 * fix: deletes old NewLeaf event from Home * fmt: runs cargo fmt * fix: removes num_retries field * refactor: simplifies destination_and_sequence util * fix: fixes typo in warning message * refactor: derives transparent errors * docs: improves comments for prover-sync/incremental merkle behavior * fix: restarts poll_updates loop if prover root was updated while we were building leaf vector from incremental merkle * feature: adds ProverSync::new function * fix: ProverSync stops polling updates on invalid local_root * fmt: runs cargo fmt
4 years ago
uint256 indexed leafIndex,
uint64 indexed destinationAndSequence,
bytes32 indexed leaf,
bytes32 current,
bytes message
);
/// @notice Event emitted when improper update detected
event ImproperUpdate();
/**
* @notice Event emitted when the UpdaterManager sets a new updater on Home
* @param updater The address of the new updater
*/
event NewUpdater(address updater);
/**
* @notice Event emitted when a new UpdaterManager is set
* @param updaterManager The address of the new updaterManager
*/
event NewUpdaterManager(address updaterManager);
/**
* @notice Event emitted when an updater is slashed
* @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);
constructor(uint32 _localDomain) Common(_localDomain) {} // solhint-disable-line no-empty-blocks
function initialize(IUpdaterManager _updaterManager) public initializer {
__Ownable_init();
_setUpdaterManager(_updaterManager);
address _updater = updaterManager.updater();
__Common_initialize(_updater);
emit NewUpdater(_updater);
}
modifier onlyUpdaterManager() {
require(msg.sender == address(updaterManager), "!updaterManager");
_;
}
/// @notice Sets updater
function setUpdater(address _updater) external onlyUpdaterManager {
_setUpdater(_updater);
}
/// @notice sets a new updaterManager
function setUpdaterManager(address _updaterManager) external onlyOwner {
_setUpdaterManager(IUpdaterManager(_updaterManager));
}
/**
* @notice Formats message, adds its leaf into merkle tree, enqueues new
* merkle root, and emits `Dispatch` event with data regarding message.
* @param _destination Domain of destination chain
* @param _recipient Address or recipient on destination chain
* @param _body Raw bytes of message
*/
function enqueue(
uint32 _destination,
bytes32 _recipient,
bytes memory _body
) external notFailed {
require(_body.length <= MAX_MESSAGE_BODY_BYTES, "!too big");
uint32 _sequence = sequences[_destination];
bytes memory _message = Message.formatMessage(
localDomain,
bytes32(uint256(uint160(msg.sender))),
_sequence,
_destination,
_recipient,
_body
);
bytes32 _leaf = keccak256(_message);
tree.insert(_leaf);
queue.enqueue(root());
feature: adds untested ProverSync struct to optics-core (#78) * feature: basic ProverSync setup * prog: basic setup for linking enqueuings to roots * refactor: modifies Dispatch event to contain treeSize field and condenses destination and sequence into single field * feature: adds leaf_by_tree_size methods to Home abi and logic for updating local tree in prover_sync * format: runs cargo fmt * prog: comments out unused code from previous design * fix: replaces slip44 references with domain * refactor: moves interval to parameter instead of ProverSync struct field * fix: deletes commented out code from NewLeaf design * refactor: modifies ProverSync error handling to bubble up ProverSyncError type * fix: removes slip44 reference * refactor: adds incremental merkle tree to ProverSync and batch updates prover on successful incremental update * fmt: runs cargo fmt * fix: fixes raw_message_by_sequence filtering after destination and sequence event fields were combined to single field in Home * fix: simplifies error handling, adds retry counter for failed api calls, and clones incremental merkle * fix: renames leaves_by_tree_size to leaves_by_tree_index * fix/docs: fixes Home Dispatch event to use leafIndex instead of treeSize and fixes docs to reflect change * fix: changes num_retries to u32 * refactor: cleans up error handling * docs: adds comment about why leafIndex is count() - 1 * fix: deletes old NewLeaf event from Home * fmt: runs cargo fmt * fix: removes num_retries field * refactor: simplifies destination_and_sequence util * fix: fixes typo in warning message * refactor: derives transparent errors * docs: improves comments for prover-sync/incremental merkle behavior * fix: restarts poll_updates loop if prover root was updated while we were building leaf vector from incremental merkle * feature: adds ProverSync::new function * fix: ProverSync stops polling updates on invalid local_root * fmt: runs cargo fmt
4 years ago
// leafIndex is count() - 1 since new leaf has already been inserted
emit Dispatch(
count() - 1,
_destinationAndSequence(_destination, _sequence),
_leaf,
current,
_message
);
sequences[_destination] = _sequence + 1;
}
/**
* @notice Called with updater's signature. Updates home's `current` root from `_oldRoot`
* to `_newRoot` and emits `Update` event. If fraudulent update
* detected in `improperUpdate`, updater is slashed and home is
* failed. Invalid signed roots on a Replica can be submitted
* by anyone here on Home and will lead to slashing as well.
* @param _oldRoot Old merkle root (should equal home's current 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 notFailed {
if (improperUpdate(_oldRoot, _newRoot, _signature)) return;
while (true) {
bytes32 _next = queue.dequeue();
if (_next == _newRoot) break;
}
current = _newRoot;
emit Update(localDomain, _oldRoot, _newRoot, _signature);
}
/**
* @notice Suggests an update to caller. If queue is non-empty, returns the
* home's current root as `_current` and the queue's latest root as
* `_new`. Null bytes returned if queue is empty.
* @return _current Current root
* @return _new New root
*/
function suggestUpdate()
external
view
returns (bytes32 _current, bytes32 _new)
{
if (queue.length() != 0) {
_current = current;
_new = queue.lastItem();
}
}
/// @notice Hash of home's domain concatenated with "OPTICS"
function homeDomainHash() public view override returns (bytes32) {
return _homeDomainHash(localDomain);
}
/**
* @notice Checks that `_newRoot` in update currently exists in queue. If
* `_newRoot` doesn't exist in queue, update is fraudulent, causing
* updater to be slashed and home to be failed.
* @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 current root)
* @param _newRoot New merkle tree root
* @param _signature Updater's signature on `_oldRoot` and `_newRoot`
* @return Returns true if update was fraudulent
*/
function improperUpdate(
bytes32 _oldRoot,
bytes32 _newRoot,
bytes memory _signature
) public notFailed returns (bool) {
require(
Common._isUpdaterSignature(_oldRoot, _newRoot, _signature),
"bad sig"
);
require(_oldRoot == current, "not a current update");
if (!queue.contains(_newRoot)) {
_fail();
emit ImproperUpdate();
return true;
}
return false;
}
/**
* @notice sets a new updaterManager
* @param _updaterManager Address of new UpdaterManager
*/
function _setUpdaterManager(IUpdaterManager _updaterManager) internal {
require(
Address.isContract(address(_updaterManager)),
"!contract updaterManager"
);
updaterManager = IUpdaterManager(_updaterManager);
emit NewUpdaterManager(address(_updaterManager));
}
/**
* @notice sets a new updater
* @param _updater Address of new Updater
*/
function _setUpdater(address _updater) internal {
updater = _updater;
emit NewUpdater(_updater);
}
/// @notice Sets contract state to FAILED and slashes updater
function _fail() internal override {
_setFailed();
updaterManager.slashUpdater(msg.sender);
emit UpdaterSlashed(updater, msg.sender);
}
/**
* @notice Internal utility function that combines provided `_destination`
* and `_sequence`.
* @dev Both destination and sequence should be < 2^32 - 1
* @param _destination Domain of destination chain
* @param _sequence Current sequence for given destination chain
* @return Returns (`_destination` << 32) & `_sequence`
*/
function _destinationAndSequence(uint32 _destination, uint32 _sequence)
internal
pure
returns (uint64)
{
return (uint64(_destination) << 32) | _sequence;
}
}