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/Common.sol

244 lines
7.8 KiB

// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
import "@openzeppelin/contracts/cryptography/ECDSA.sol";
import "@summa-tx/memview-sol/contracts/TypedMemView.sol";
/**
* @title Message Library
* @author Celo Labs 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 _origin Domain of home chain
* @param _sender Address of sender as bytes32
* @param _sequence Destination-specific sequence 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 Formatted message
**/
function formatMessage(
uint32 _origin,
bytes32 _sender,
uint32 _sequence,
uint32 _destination,
bytes32 _recipient,
bytes memory _body
) internal pure returns (bytes memory) {
return
abi.encodePacked(
_origin,
_sender,
_sequence,
_destination,
_recipient,
_body
);
}
/**
* @notice Returns leaf of formatted message with provided fields.
* @param _origin Domain of home chain
* @param _sender Address of sender as bytes32
* @param _sequence Destination-specific sequence 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 _sequence,
uint32 _destination,
bytes32 _recipient,
bytes memory _body
) internal pure returns (bytes32) {
return
keccak256(
formatMessage(
_origin,
_sender,
_sequence,
_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 sequence field
function sequence(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 address(uint160(uint256(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);
}
}
/**
* @title Common
* @author Celo Labs Inc.
* @notice Shared utilities between Home and Replica.
**/
abstract contract Common {
enum States {UNINITIALIZED, ACTIVE, FAILED}
/// @notice Domain of owning contract
uint32 public originDomain;
/// @notice Hash of `originDomain` concatenated with "OPTICS"
bytes32 public domainHash;
/// @notice Address of bonded updater
address public updater;
/// @notice Current state of contract
States public state;
/// @notice Current root
bytes32 public current;
/**
* @notice Event emitted when update is made on Home or unconfirmed update
* root is enqueued on Replica
* @param _originDomain Domain of contract's chain
* @param _oldRoot Old merkle root
* @param _newRoot New merkle root
* @param signature Updater's signature on `_oldRoot` and `_newRoot`
**/
event Update(
uint32 indexed _originDomain,
bytes32 indexed _oldRoot,
bytes32 indexed _newRoot,
bytes signature
);
/**
* @notice Event emitted when valid double update proof is provided to
* contract
* @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 Called when a double update or fraudulent update is detected
function fail() internal virtual;
/// @notice Sets contract state to FAILED
function _setFailed() internal {
state = States.FAILED;
}
/// @notice Ensures that contract state != FAILED
modifier notFailed() {
require(state != States.FAILED, "failed state");
_;
}
function initialize(uint32 _originDomain, address _updater) public virtual {
require(state == States.UNINITIALIZED, "already initialized");
setOriginDomain(_originDomain);
updater = _updater;
state = States.ACTIVE;
}
function setOriginDomain(uint32 _originDomain) internal {
originDomain = _originDomain;
domainHash = keccak256(abi.encodePacked(_originDomain, "OPTICS"));
}
/**
* @notice Called internally. Checks that signature is valid (belongs to
* updater).
* @param _oldRoot Old merkle root
* @param _newRoot New merkle root
* @param _signature Signature on `_oldRoot` and `_newRoot`
* @return Returns true if signature is valid and false if otherwise
**/
function checkSig(
bytes32 _oldRoot,
bytes32 _newRoot,
bytes memory _signature
) internal view returns (bool) {
bytes32 _digest =
keccak256(abi.encodePacked(domainHash, _oldRoot, _newRoot));
_digest = ECDSA.toEthSignedMessageHash(_digest);
return ECDSA.recover(_digest, _signature) == updater;
}
/**
* @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 (
Common.checkSig(_oldRoot, _newRoot[0], _signature) &&
Common.checkSig(_oldRoot, _newRoot[1], _signature2) &&
_newRoot[0] != _newRoot[1]
) {
fail();
emit DoubleUpdate(_oldRoot, _newRoot, _signature, _signature2);
}
}
}