Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts/isms/multisig/StaticMultisigIsm.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
// ============ Internal Imports ============
import {IInterchainSecurityModule} from "../../interfaces/IInterchainSecurityModule.sol";
import {AbstractMultisigIsm} from "./AbstractMultisigIsm.sol";
import {AbstractMerkleRootMultisigIsm} from "./AbstractMerkleRootMultisigIsm.sol";
import {AbstractMessageIdMultisigIsm} from "./AbstractMessageIdMultisigIsm.sol";
import {MetaProxy} from "../../libs/MetaProxy.sol";
import {StaticThresholdAddressSetFactory} from "../../libs/StaticAddressSetFactory.sol";
/**
* @title AbstractMetaProxyMultisigIsm
* @notice Manages per-domain m-of-n Validator set that is used
* to verify interchain messages.
*/
abstract contract AbstractMetaProxyMultisigIsm is AbstractMultisigIsm {
/**
* @inheritdoc AbstractMultisigIsm
*/
function validatorsAndThreshold(
bytes calldata
) public pure override returns (address[] memory, uint8) {
return abi.decode(MetaProxy.metadata(), (address[], uint8));
}
}
// solhint-disable no-empty-blocks
/**
* @title StaticMerkleRootMultisigIsm
* @notice Manages per-domain m-of-n validator set that is used
* to verify interchain messages using a merkle root signature quorum
* and merkle proof of inclusion.
*/
contract StaticMerkleRootMultisigIsm is
AbstractMerkleRootMultisigIsm,
AbstractMetaProxyMultisigIsm
{
uint8 public constant moduleType =
uint8(IInterchainSecurityModule.Types.MERKLE_ROOT_MULTISIG);
}
/**
* @title StaticMessageIdMultisigIsm
* @notice Manages per-domain m-of-n validator set that is used
* to verify interchain messages using a message ID signature quorum.
*/
contract StaticMessageIdMultisigIsm is
AbstractMessageIdMultisigIsm,
AbstractMetaProxyMultisigIsm
{
uint8 public constant moduleType =
uint8(IInterchainSecurityModule.Types.MESSAGE_ID_MULTISIG);
}
// solhint-enable no-empty-blocks
contract StaticMerkleRootMultisigIsmFactory is
StaticThresholdAddressSetFactory
{
function _deployImplementation() internal override returns (address) {
return address(new StaticMerkleRootMultisigIsm());
}
}
contract StaticMessageIdMultisigIsmFactory is StaticThresholdAddressSetFactory {
function _deployImplementation() internal override returns (address) {
return address(new StaticMessageIdMultisigIsm());
}
}
"
},
"contracts/interfaces/IInterchainSecurityModule.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
interface IInterchainSecurityModule {
enum Types {
UNUSED,
ROUTING,
AGGREGATION,
LEGACY_MULTISIG,
MERKLE_ROOT_MULTISIG,
MESSAGE_ID_MULTISIG,
NULL, // used with relayer carrying no metadata
CCIP_READ,
ARB_L2_TO_L1,
WEIGHTED_MERKLE_ROOT_MULTISIG,
WEIGHTED_MESSAGE_ID_MULTISIG,
OP_L2_TO_L1
}
/**
* @notice Returns an enum that represents the type of security model
* encoded by this ISM.
* @dev Relayers infer how to fetch and format metadata.
*/
function moduleType() external view returns (uint8);
/**
* @notice Defines a security model responsible for verifying interchain
* messages based on the provided metadata.
* @param _metadata Off-chain metadata provided by a relayer, specific to
* the security model encoded by the module (e.g. validator signatures)
* @param _message Hyperlane encoded interchain message
* @return True if the message was verified
*/
function verify(
bytes calldata _metadata,
bytes calldata _message
) external returns (bool);
}
interface ISpecifiesInterchainSecurityModule {
function interchainSecurityModule()
external
view
returns (IInterchainSecurityModule);
}
"
},
"contracts/isms/multisig/AbstractMultisigIsm.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/
// ============ External Imports ============
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
// ============ Internal Imports ============
import {IMultisigIsm} from "../../interfaces/isms/IMultisigIsm.sol";
import {PackageVersioned} from "../../PackageVersioned.sol";
/**
* @title AbstractMultisig
* @notice Manages per-domain m-of-n Validator sets
* @dev See ./AbstractMerkleRootMultisigIsm.sol and ./AbstractMessageIdMultisigIsm.sol
* for concrete implementations of `digest` and `signatureAt`.
* @dev See ./StaticMultisigIsm.sol for concrete implementations.
*/
abstract contract AbstractMultisig is PackageVersioned {
/**
* @notice Returns the digest to be used for signature verification.
* @param _metadata ABI encoded module metadata
* @param _message Formatted Hyperlane message (see Message.sol).
* @return digest The digest to be signed by validators
*/
function digest(
bytes calldata _metadata,
bytes calldata _message
) internal view virtual returns (bytes32);
/**
* @notice Returns the signature at a given index from the metadata.
* @param _metadata ABI encoded module metadata
* @param _index The index of the signature to return
* @return signature Packed encoding of signature (65 bytes)
*/
function signatureAt(
bytes calldata _metadata,
uint256 _index
) internal pure virtual returns (bytes calldata);
/**
* @notice Returns the number of signatures in the metadata.
* @param _metadata ABI encoded module metadata
* @return count The number of signatures
*/
function signatureCount(
bytes calldata _metadata
) public pure virtual returns (uint256);
}
/**
* @title AbstractMultisigIsm
* @notice Manages per-domain m-of-n Validator sets of AbstractMultisig that are used to verify
* interchain messages.
*/
abstract contract AbstractMultisigIsm is AbstractMultisig, IMultisigIsm {
// ============ Virtual Functions ============
// ======= OVERRIDE THESE TO IMPLEMENT =======
/**
* @notice Returns the set of validators responsible for verifying _message
* and the number of signatures required
* @dev Can change based on the content of _message
* @dev Signatures provided to `verify` must be consistent with validator ordering
* @param _message Hyperlane formatted interchain message
* @return validators The array of validator addresses
* @return threshold The number of validator signatures needed
*/
function validatorsAndThreshold(
bytes calldata _message
) public view virtual returns (address[] memory, uint8);
// ============ Public Functions ============
/**
* @notice Requires that m-of-n validators verify a merkle root,
* and verifies a merkle proof of `_message` against that root.
* @dev Optimization relies on the caller sorting signatures in the same order as validators.
* @dev Employs https://www.geeksforgeeks.org/two-pointers-technique/ to minimize gas usage.
* @param _metadata ABI encoded module metadata
* @param _message Formatted Hyperlane message (see Message.sol).
*/
function verify(
bytes calldata _metadata,
bytes calldata _message
) public view returns (bool) {
bytes32 _digest = digest(_metadata, _message);
(
address[] memory _validators,
uint8 _threshold
) = validatorsAndThreshold(_message);
require(_threshold > 0, "No MultisigISM threshold present for message");
uint256 _validatorCount = _validators.length;
uint256 _validatorIndex = 0;
// Assumes that signatures are ordered by validator
for (uint256 i = 0; i < _threshold; ++i) {
address _signer = ECDSA.recover(_digest, signatureAt(_metadata, i));
// Loop through remaining validators until we find a match
while (
_validatorIndex < _validatorCount &&
_signer != _validators[_validatorIndex]
) {
++_validatorIndex;
}
// Fail if we never found a match
require(_validatorIndex < _validatorCount, "!threshold");
++_validatorIndex;
}
return true;
}
}
"
},
"contracts/isms/multisig/AbstractMerkleRootMultisigIsm.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
// ============ Internal Imports ============
import {AbstractMultisig} from "./AbstractMultisigIsm.sol";
import {MerkleRootMultisigIsmMetadata} from "../../isms/libs/MerkleRootMultisigIsmMetadata.sol";
import {Message} from "../../libs/Message.sol";
import {MerkleLib} from "../../libs/Merkle.sol";
import {CheckpointLib} from "../../libs/CheckpointLib.sol";
/**
* @title `AbstractMerkleRootMultisigIsm` — multi-sig ISM with the validators-censorship resistance guarantee.
* @notice This ISM allows using a newer signed checkpoint (say #33) to prove existence of an older message (#22) in the validators' MerkleTree.
* This guarantees censorship resistance as validators cannot hide a message
* by refusing to sign its checkpoint but later signing a checkpoint for a newer message.
* If validators decide to censor a message, they are left with only one option — to not produce checkpoints at all.
* Otherwise, the very next signed checkpoint (#33) can be used by any relayer to prove the previous message inclusion using this ISM.
* This is censorship resistance is missing in the sibling implementation `AbstractMessageIdMultisigIsm`,
* since it can only verify messages having the corresponding checkpoints.
* @dev Provides the default implementation of verifying signatures over a checkpoint and the message inclusion in that checkpoint.
* This abstract contract can be overridden for customizing the `validatorsAndThreshold()` (static or dynamic).
* @dev May be adapted in future to support batch message verification against a single root.
*/
abstract contract AbstractMerkleRootMultisigIsm is AbstractMultisig {
using MerkleRootMultisigIsmMetadata for bytes;
using Message for bytes;
// ============ Constants ============
/**
* @inheritdoc AbstractMultisig
*/
function digest(
bytes calldata _metadata,
bytes calldata _message
) internal pure virtual override returns (bytes32) {
require(
_metadata.messageIndex() <= _metadata.signedIndex(),
"Invalid merkle index metadata"
);
// We verify a merkle proof of (messageId, index) I to compute root J
bytes32 _signedRoot = MerkleLib.branchRoot(
_message.id(),
_metadata.proof(),
_metadata.messageIndex()
);
// We provide (messageId, index) J in metadata for digest derivation
return
CheckpointLib.digest(
_message.origin(),
_metadata.originMerkleTreeHook(),
_signedRoot,
_metadata.signedIndex(),
_metadata.signedMessageId()
);
}
/**
* @inheritdoc AbstractMultisig
*/
function signatureAt(
bytes calldata _metadata,
uint256 _index
) internal pure virtual override returns (bytes calldata) {
return _metadata.signatureAt(_index);
}
/**
* @inheritdoc AbstractMultisig
*/
function signatureCount(
bytes calldata _metadata
) public pure override returns (uint256) {
return _metadata.signatureCount();
}
}
"
},
"contracts/isms/multisig/AbstractMessageIdMultisigIsm.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
// ============ Internal Imports ============
import {AbstractMultisig} from "./AbstractMultisigIsm.sol";
import {MessageIdMultisigIsmMetadata} from "../libs/MessageIdMultisigIsmMetadata.sol";
import {Message} from "../../libs/Message.sol";
import {CheckpointLib} from "../../libs/CheckpointLib.sol";
/**
* @title `AbstractMessageIdMultisigIsm` — multi-sig ISM for the censorship-friendly validators.
* @notice This ISM minimizes gas/performance overhead of the checkpoints verification by compromising on the censorship resistance.
* For censorship resistance consider using `AbstractMerkleRootMultisigIsm`.
* If the validators (`validatorsAndThreshold`) skip messages by not sign checkpoints for them,
* the relayers will not be able to aggregate a quorum of signatures sufficient to deliver these messages via this ISM.
* Integrations are free to choose the trade-off between the censorship resistance and the gas/processing overhead.
* @dev Provides the default implementation of verifying signatures over a checkpoint related to a specific message ID.
* This abstract contract can be customized to change the `validatorsAndThreshold()` (static or dynamic).
*/
abstract contract AbstractMessageIdMultisigIsm is AbstractMultisig {
using Message for bytes;
using MessageIdMultisigIsmMetadata for bytes;
// ============ Constants ============
/**
* @inheritdoc AbstractMultisig
*/
function digest(
bytes calldata _metadata,
bytes calldata _message
) internal pure override returns (bytes32) {
return
CheckpointLib.digest(
_message.origin(),
_metadata.originMerkleTreeHook(),
_metadata.root(),
_metadata.index(),
_message.id()
);
}
/**
* @inheritdoc AbstractMultisig
*/
function signatureAt(
bytes calldata _metadata,
uint256 _index
) internal pure virtual override returns (bytes calldata) {
return _metadata.signatureAt(_index);
}
/**
* @inheritdoc AbstractMultisig
*/
function signatureCount(
bytes calldata _metadata
) public pure override returns (uint256) {
return _metadata.signatureCount();
}
}
"
},
"contracts/libs/MetaProxy.sol": {
"content": "// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.7.6;
/// @dev Adapted from https://eips.ethereum.org/EIPS/eip-3448
library MetaProxy {
bytes32 private constant PREFIX =
hex"600b380380600b3d393df3363d3d373d3d3d3d60368038038091363936013d73";
bytes13 private constant SUFFIX = hex"5af43d3d93803e603457fd5bf3";
function bytecode(
address _implementation,
bytes memory _metadata
) internal pure returns (bytes memory) {
return
abi.encodePacked(
PREFIX,
bytes20(_implementation),
SUFFIX,
_metadata,
_metadata.length
);
}
function metadata() internal pure returns (bytes memory) {
bytes memory data;
assembly {
let posOfMetadataSize := sub(calldatasize(), 32)
let size := calldataload(posOfMetadataSize)
let dataPtr := sub(posOfMetadataSize, size)
data := mload(64)
// increment free memory pointer by metadata size + 32 bytes (length)
mstore(64, add(data, add(size, 32)))
mstore(data, size)
let memPtr := add(data, 32)
calldatacopy(memPtr, dataPtr, size)
}
return data;
}
}
"
},
"contracts/libs/StaticAddressSetFactory.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
// ============ External Imports ============
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
// ============ Internal Imports ============
import {MetaProxy} from "./MetaProxy.sol";
import {PackageVersioned} from "../PackageVersioned.sol";
import {IThresholdAddressFactory} from "../interfaces/IThresholdAddressFactory.sol";
abstract contract StaticThresholdAddressSetFactory is
PackageVersioned,
IThresholdAddressFactory
{
// ============ Immutables ============
address public immutable implementation;
// ============ Constructor ============
constructor() {
implementation = _deployImplementation();
}
function _deployImplementation() internal virtual returns (address);
/**
* @notice Deploys a StaticThresholdAddressSet contract address for the given
* values
* @dev Consider sorting addresses to ensure contract reuse
* @param _values An array of addresses
* @param _threshold The threshold value to use
* @return set The contract address representing this StaticThresholdAddressSet
*/
function deploy(
address[] calldata _values,
uint8 _threshold
) public returns (address) {
require(
0 < _threshold && _threshold <= _values.length,
"Invalid threshold"
);
(bytes32 _salt, bytes memory _bytecode) = _saltAndBytecode(
_values,
_threshold
);
address _set = _getAddress(_salt, _bytecode);
if (!Address.isContract(_set)) {
_set = Create2.deploy(0, _salt, _bytecode);
}
return _set;
}
/**
* @notice Returns the StaticThresholdAddressSet contract address for the given
* values
* @dev Consider sorting addresses to ensure contract reuse
* @param _values An array of addresses
* @param _threshold The threshold value to use
* @return set The contract address representing this StaticThresholdAddressSet
*/
function getAddress(
address[] calldata _values,
uint8 _threshold
) external view returns (address) {
(bytes32 _salt, bytes memory _bytecode) = _saltAndBytecode(
_values,
_threshold
);
return _getAddress(_salt, _bytecode);
}
/**
* @notice Returns the StaticThresholdAddressSet contract address for the given
* values
* @param _salt The salt used in Create2
* @param _bytecode The metaproxy bytecode used in Create2
* @return set The contract address representing this StaticThresholdAddressSet
*/
function _getAddress(
bytes32 _salt,
bytes memory _bytecode
) internal view returns (address) {
bytes32 _bytecodeHash = keccak256(_bytecode);
return Create2.computeAddress(_salt, _bytecodeHash);
}
/**
* @notice Returns the create2 salt and bytecode for the given values
* @param _values An array of addresses
* @param _threshold The threshold value to use
* @return _salt The salt used in Create2
* @return _bytecode The metaproxy bytecode used in Create2
*/
function _saltAndBytecode(
address[] calldata _values,
uint8 _threshold
) internal view returns (bytes32, bytes memory) {
bytes memory _metadata = abi.encode(_values, _threshold);
bytes memory _bytecode = MetaProxy.bytecode(implementation, _metadata);
bytes32 _salt = keccak256(_metadata);
return (_salt, _bytecode);
}
}
abstract contract StaticAddressSetFactory is StaticThresholdAddressSetFactory {
/**
* @notice Deploys a StaticAddressSet contract address for the given
* values
* @dev Consider sorting addresses to ensure contract reuse
* @param _values An array of addresses
* @return set The contract address representing this StaticAddressSet
*/
function deploy(address[] calldata _values) external returns (address) {
return super.deploy(_values, uint8(_values.length));
}
/**
* @notice Returns the StaticAddressSet contract address for the given
* values
* @dev Consider sorting addresses to ensure contract reuse
* @param _values An array of addresses
* @return set The contract address representing this StaticAddressSet
*/
function getAddress(
address[] calldata _values
) external view returns (address) {
(bytes32 _salt, bytes memory _bytecode) = _saltAndBytecode(
_values,
uint8(_values.length)
);
return super._getAddress(_salt, _bytecode);
}
}
"
},
"@openzeppelin/contracts/utils/cryptography/ECDSA.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.0;
import "../Strings.sol";
/**
* @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 {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV // Deprecated in v4.8
}
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} else if (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} else if (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature` or error string. 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.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
}
/**
* @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) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*
* _Available since v4.2._
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) {
// 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 (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): 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.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, "\x19Ethereum Signed Message:\
32")
mstore(0x1c, hash)
message := keccak256(0x00, 0x3c)
}
}
/**
* @dev Returns an Ethereum Signed Message, created from `s`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\
", Strings.toString(s.length), s));
}
/**
* @dev Returns an Ethereum Signed Typed Data, created from a
* `domainSeparator` and a `structHash`. This produces hash corresponding
* to the one signed with the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
* JSON-RPC method as part of EIP-712.
*
* See {recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) {
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, "\x19\x01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
data := keccak256(ptr, 0x42)
}
}
/**
* @dev Returns an Ethereum Signed Data with intended validator, created from a
* `validator` and `data` according to the version 0 of EIP-191.
*
* See {recover}.
*/
function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x00", validator, data));
}
}
"
},
"contracts/interfaces/isms/IMultisigIsm.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
import {IInterchainSecurityModule} from "../IInterchainSecurityModule.sol";
interface IMultisigIsm is IInterchainSecurityModule {
/**
* @notice Returns the set of validators responsible for verifying _message
* and the number of signatures required
* @dev Can change based on the content of _message
* @dev Signatures provided to `verify` must be consistent with validator ordering
* @param _message Hyperlane formatted interchain message
* @return validators The array of validator addresses
* @return threshold The number of validator signatures needed
*/
function validatorsAndThreshold(
bytes calldata _message
) external view returns (address[] memory validators, uint8 threshold);
}
"
},
"contracts/PackageVersioned.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
/**
* @title PackageVersioned
* @notice Package version getter for contracts
**/
abstract contract PackageVersioned {
// GENERATED CODE - DO NOT EDIT
string public constant PACKAGE_VERSION = "9.0.9";
}
"
},
"contracts/isms/libs/MerkleRootMultisigIsmMetadata.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
/**
* Format of metadata:
* [ 0: 32] Origin merkle tree address
* [ 32: 36] Index of message ID in merkle tree
* [ 36: 68] Signed checkpoint message ID
* [ 68:1092] Merkle proof
* [1092:1096] Signed checkpoint index (computed from proof and index)
* [1096:????] Validator signatures (length := threshold * 65)
*/
library MerkleRootMultisigIsmMetadata {
uint8 private constant ORIGIN_MERKLE_TREE_OFFSET = 0;
uint8 private constant MESSAGE_INDEX_OFFSET = 32;
uint8 private constant MESSAGE_ID_OFFSET = 36;
uint8 private constant MERKLE_PROOF_OFFSET = 68;
uint16 private constant MERKLE_PROOF_LENGTH = 32 * 32;
uint16 private constant SIGNED_INDEX_OFFSET = 1092;
uint16 private constant SIGNATURES_OFFSET = 1096;
uint8 private constant SIGNATURE_LENGTH = 65;
/**
* @notice Returns the origin merkle tree hook of the signed checkpoint as bytes32.
* @param _metadata ABI encoded Multisig ISM metadata.
* @return Origin merkle tree hook of the signed checkpoint as bytes32
*/
function originMerkleTreeHook(
bytes calldata _metadata
) internal pure returns (bytes32) {
return
bytes32(
_metadata[ORIGIN_MERKLE_TREE_OFFSET:ORIGIN_MERKLE_TREE_OFFSET +
32]
);
}
/**
* @notice Returns the index of the message being proven.
* @param _metadata ABI encoded Multisig ISM metadata.
* @return Index of the target message in the merkle tree.
*/
function messageIndex(
bytes calldata _metadata
) internal pure returns (uint32) {
return
uint32(
bytes4(_metadata[MESSAGE_INDEX_OFFSET:MESSAGE_INDEX_OFFSET + 4])
);
}
/**
* @notice Returns the index of the signed checkpoint.
* @param _metadata ABI encoded Multisig ISM metadata.
* @return Index of the signed checkpoint
*/
function signedIndex(
bytes calldata _metadata
) internal pure returns (uint32) {
return
uint32(
bytes4(_metadata[SIGNED_INDEX_OFFSET:SIGNED_INDEX_OFFSET + 4])
);
}
/**
* @notice Returns the message ID of the signed checkpoint.
* @param _metadata ABI encoded Multisig ISM metadata.
* @return Message ID of the signed checkpoint
*/
function signedMessageId(
bytes calldata _metadata
) internal pure returns (bytes32) {
return bytes32(_metadata[MESSAGE_ID_OFFSET:MESSAGE_ID_OFFSET + 32]);
}
/**
* @notice Returns the merkle proof branch of the message.
* @dev This appears to be more gas efficient than returning a calldata
* slice and using that.
* @param _metadata ABI encoded Multisig ISM metadata.
* @return Merkle proof branch of the message.
*/
function proof(
bytes calldata _metadata
) internal pure returns (bytes32[32] memory) {
return
abi.decode(
_metadata[MERKLE_PROOF_OFFSET:MERKLE_PROOF_OFFSET +
MERKLE_PROOF_LENGTH],
(bytes32[32])
);
}
/**
* @notice Returns the validator ECDSA signature at `_index`.
* @dev Assumes signatures are sorted by validator
* @dev Assumes `_metadata` encodes `threshold` signatures.
* @dev Assumes `_index` is less than `threshold`
* @param _metadata ABI encoded Multisig ISM metadata.
* @param _index The index of the signature to return.
* @return The validator ECDSA signature at `_index`.
*/
function signatureAt(
bytes calldata _metadata,
uint256 _index
) internal pure returns (bytes calldata) {
uint256 _start = SIGNATURES_OFFSET + (_index * SIGNATURE_LENGTH);
uint256 _end = _start + SIGNATURE_LENGTH;
return _metadata[_start:_end];
}
/**
* @notice Returns the number of signatures in the metadata.
* @param _metadata ABI encoded Merkle Root Multisig ISM metadata.
* @return The number of signatures in the metadata.
*/
function signatureCount(
bytes calldata _metadata
) internal pure returns (uint256) {
uint256 signatures = _metadata.length - SIGNATURES_OFFSET;
require(
signatures % SIGNATURE_LENGTH == 0,
"Invalid signatures length"
);
return signatures / SIGNATURE_LENGTH;
}
}
"
},
"contracts/libs/Message.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
import {TypeCasts} from "./TypeCasts.sol";
/**
* @title Hyperlane Message Library
* @notice Library for formatted messages used by Mailbox
**/
library Message {
using TypeCasts for bytes32;
uint256 private constant VERSION_OFFSET = 0;
uint256 private constant NONCE_OFFSET = 1;
uint256 private constant ORIGIN_OFFSET = 5;
uint256 private constant SENDER_OFFSET = 9;
uint256 private constant DESTINATION_OFFSET = 41;
uint256 private constant RECIPIENT_OFFSET = 45;
uint256 private constant BODY_OFFSET = 77;
/**
* @notice Returns formatted (packed) Hyperlane message with provided fields
* @dev This function should only be used in memory message construction.
* @param _version The version of the origin and destination Mailboxes
* @param _nonce A nonce to uniquely identify the message on its origin chain
* @param _originDomain Domain of origin chain
* @param _sender Address of sender as bytes32
* @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(
uint8 _version,
uint32 _nonce,
uint32 _originDomain,
bytes32 _sender,
uint32 _destinationDomain,
bytes32 _recipient,
bytes calldata _messageBody
) internal pure returns (bytes memory) {
return
abi.encodePacked(
_version,
_nonce,
_originDomain,
_sender,
_destinationDomain,
_recipient,
_messageBody
);
}
/**
* @notice Returns the message ID.
* @param _message ABI encoded Hyperlane message.
* @return ID of `_message`
*/
function id(bytes memory _message) internal pure returns (bytes32) {
return keccak256(_message);
}
/**
* @notice Returns the message version.
* @param _message ABI encoded Hyperlane message.
* @return Version of `_message`
*/
function version(bytes calldata _message) internal pure returns (uint8) {
return uint8(bytes1(_message[VERSION_OFFSET:NONCE_OFFSET]));
}
/**
* @notice Returns the message nonce.
* @param _message ABI encoded Hyperlane message.
* @return Nonce of `_message`
*/
function nonce(bytes calldata _message) internal pure returns (uint32) {
return uint32(bytes4(_message[NONCE_OFFSET:ORIGIN_OFFSET]));
}
/**
* @notice Returns the message origin domain.
* @param _message ABI encoded Hyperlane message.
* @return Origin domain of `_message`
*/
function origin(bytes calldata _message) internal pure returns (uint32) {
return uint32(bytes4(_message[ORIGIN_OFFSET:SENDER_OFFSET]));
}
/**
* @notice Returns the message sender as bytes32.
* @param _message ABI encoded Hyperlane message.
* @return Sender of `_message` as bytes32
*/
function sender(bytes calldata _message) internal pure returns (bytes32) {
return bytes32(_message[SENDER_OFFSET:DESTINATION_OFFSET]);
}
/**
* @notice Returns the message sender as address.
* @param _message ABI encoded Hyperlane message.
* @return Sender of `_message` as address
*/
function senderAddress(
bytes calldata _message
) internal pure returns (address) {
return sender(_message).bytes32ToAddress();
}
/**
* @notice Returns the message destination domain.
* @param _message ABI encoded Hyperlane message.
* @return Destination domain of `_message`
*/
function destination(
bytes calldata _message
) internal pure returns (uint32) {
return uint32(bytes4(_message[DESTINATION_OFFSET:RECIPIENT_OFFSET]));
}
/**
* @notice Returns the message recipient as bytes32.
* @param _message ABI encoded Hyperlane message.
* @return Recipient of `_message` as bytes32
*/
function recipient(
bytes calldata _message
) internal pure returns (bytes32) {
return bytes32(_message[RECIPIENT_OFFSET:BODY_OFFSET]);
}
/**
* @notice Returns the message recipient as address.
* @param _message ABI encoded Hyperlane message.
* @return Recipient of `_message` as address
*/
function recipientAddress(
bytes calldata _message
) internal pure returns (address) {
return recipient(_message).bytes32ToAddress();
}
/**
* @notice Returns the message body.
* @param _message ABI encoded Hyperlane message.
* @return Body of `_message`
*/
function body(
bytes calldata _message
) internal pure returns (bytes calldata) {
return bytes(_message[BODY_OFFSET:]);
}
}
"
},
"contracts/libs/Merkle.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
// work based on eth2 deposit contract, which is used under CC0-1.0
uint256 constant TREE_DEPTH = 32;
uint256 constant MAX_LEAVES = 2 ** TREE_DEPTH - 1;
/**
* @title MerkleLib
* @author Celo Labs Inc.
* @notice An incremental merkle tree modeled on the eth2 deposit contract.
**/
library MerkleLib {
/**
* @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, // cheaper than calldata indexing
uint256 _index
) internal pure returns (bytes32 _current) {
_current = _item;
for (uint256 i = 0; i < TREE_DEPTH; i++) {
uint256 _ithBit = (_index >> i) & 0x01;
// cheaper than calldata indexing _branch[i*32:(i+1)*32];
bytes32 _next = _branch[i];
if (_ithBit == 1) {
_current = keccak256(abi.encodePacked(_next, _current));
} else {
_current = keccak256(abi.encodePacked(_current, _next));
}
}
}
/**
* @notice Calculates and returns the merkle root as if the index is
* the topmost leaf in the tree.
* @param _item Merkle leaf
* @param _branch Merkle proof
* @param _index Index of `_item` in tree
* @dev Replaces siblings greater than the index (right subtrees) with zeroes.
* @return _current Calculated merkle root
**/
function reconstructRoot(
bytes32 _item,
bytes32[TREE_DEPTH] memory _branch, // cheaper than calldata indexing
uint256 _index
) internal pure returns (bytes32 _current) {
_current = _item;
bytes32[TREE_DEPTH] memory _zeroes = zeroHashes();
for (uint256 i = 0; i < TREE_DEPTH; i++) {
uint256 _ithBit = (_index >> i) & 0x01;
// cheaper than calldata indexing _branch[i*32:(i+1)*32];
if (_ithBit == 1) {
_current = keccak256(abi.encodePacked(_branch[i], _current));
} else {
// remove right subtree from proof
_current = keccak256(abi.encodePacked(_current, _zeroes[i]));
}
}
}
// 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";
}
"
},
"contracts/libs/CheckpointLib.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {TypeCasts} from "./TypeCasts.sol";
struct Checkpoint {
uint32 origin;
bytes32 merkleTree;
bytes32 root;
uint32 index;
bytes32 messageId;
}
library CheckpointLib {
using TypeCasts for bytes32;
/**
* @notice Returns the digest validators are expected to sign when signing checkpoints.
* @param _origin The origin domain of the checkpoint.
* @param _merkleTreeHook The address of the origin merkle tree hook as bytes32.
* @param _checkpointRoot The root of the checkpoint.
* @param _checkpointIndex The index of the checkpoint.
* @param _messageId The message ID of the checkpoint.
* @dev Message ID must match leaf content of checkpoint root at index.
* @return The digest of the checkpoint.
*/
function digest(
uint32 _origin,
bytes32 _merkleTreeHook,
bytes32 _checkpointRoot,
uint32 _checkpointIndex,
bytes32 _messageId
) internal pure returns (bytes32) {
bytes32 _domainHash = domainHash(_origin, _merkleTreeHook);
return
ECDSA.toEthSignedMessageHash(
keccak256(
abi.encodePacked(
_domainHash,
_checkpointRoot,
_checkpointIndex,
_messageId
)
)
);
}
/**
* @notice Returns the digest validators are expected to sign when signing checkpoints.
* @param checkpoint The checkpoint (struct) to hash.
* @return The digest of the checkpoint.
*/
function digest(
Checkpoint memory checkpoint
) internal pure returns (bytes32) {
return
digest(
checkpoint.origin,
checkpoint.merkleTree,
checkpoint.root,
checkpoint.index,
checkpoint.messageId
);
}
function merkleTreeAddress(
Checkpoint calldata checkpoint
) internal pure returns (address) {
return checkpoint.merkleTree.bytes32ToAddress();
}
/**
* @notice Returns the domain hash that validators are expected to use
* when signing checkpoints.
* @param _origin The origin domain of the checkpoint.
* @param _merkleTreeHook The address of the origin merkle tree as bytes32.
* @return The domain hash.
*/
function domainHash(
uint32 _origin,
bytes32 _merkleTreeHook
) internal pure returns (bytes32) {
// Including the origin merkle tree address in the signature allows the slashing
// protocol to enroll multiple trees. Otherwise, a valid signature for
// tree A would be indistinguishable from a fraudulent signature for tree B.
// The slashing protocol should slash if validators sign attestations for
// anything other than a whitelisted tree.
return
keccak256(abi.encodePacked(_origin, _merkleTreeHook, "HYPERLANE"));
}
}
"
},
"contracts/isms/libs/MessageIdMultisigIsmMetadata.sol": {
"content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
/**
* Format of metadata:
* [ 0: 32] Origin merkle tree address
* [ 32: 64] Signed checkpoint root
* [ 64: 68] Signed checkpoint index
* [ 68:????] Validator signatures (length := threshold * 65)
*/
library MessageIdMultisigIsmMetadata {
uint8 private constant ORIGIN_MERKLE_TREE_OFFSET = 0;
uint8 private constant MERKLE_ROOT_OFFSET = 32;
uint8 private constant MERKLE_INDEX_OFFSET = 64;
uint8 private constant SIGNATURES_OFFSET = 68;
uint8 private constant SIGNATURE_LENGTH = 65;
/**
* @notice Returns the origin merkle tree hook of the signed checkpoint as bytes32.
* @param _metadata ABI encoded Multisig ISM metadata.
* @return Origin merkle tree hook of the signed checkpoint as bytes32
*/
function originMerkleTreeHook(
bytes calldata _metadata
) internal pure returns (bytes32) {
return
bytes32(
_metadata[ORIGIN_MERKLE_TREE_OFFSET:ORIGIN_MERKLE_TREE_OFFSET +
32]
);
}
/**
* @notice Returns the merkle root of the signed checkpoint.
* @param _metadata ABI encoded Multisig ISM metadata.
* @return Merkle root of the signed checkpoint
*/
function root(bytes calldata _metadata) internal pure returns (bytes32) {
return bytes32(_metadata[MERKLE_ROOT_OFFSET:MERKLE_ROOT_OFFSET + 32]);
}
/**
* @notice Returns the merkle index of the signed checkpoint.
* @param _metadata ABI encoded Multisig ISM metadata.
* @return Merkle index of the signed checkpoint
*/
function index(bytes calldata _metadata) internal pure returns (uint32) {
return
uint32(
bytes4(_metadata[MERKLE_INDEX_OFFSET:MERKLE_INDEX_OFFSET + 4])
);
}
/**
* @notice Returns the validator ECDSA signature at `_index`.
* @dev Assumes signatures are sorted by validator
* @dev Assumes `_metadata` encodes `threshold` signatures.
* @dev Assumes `_index` is less than `threshold`
* @param _metadata ABI encoded Multisig ISM metadata.
* @param _index The index of the signature to return.
* @return The validator ECDSA signature at `_index`.
*/
function signatureAt(
bytes calldata _metadata,
uint256 _index
) internal pure returns (bytes calldata) {
uint256 _start = SIGNATURES_OFFSET + (_index * SIGNATURE_LENGTH);
uint256 _end = _start + SIGNATURE_LENGTH;
return _metadata[_start:_end];
}
/**
* @notice Returns the number of signatures in the metadata.
* @param _metadata ABI encoded MessageId Multisig ISM metadata.
* @return The number of signatures in the metadata.
*/
function signatureCount(
bytes calldata _metadata
) internal pure returns (uint256) {
uint256 signatures = _metadata.length - SIGNATURES_OFFSET;
require(
signatures % SIGNATURE_LENGTH == 0,
"Invalid signatures length"
);
return signatures / SIGNATURE_LENGTH;
}
}
"
},
"@openzeppelin/contracts/utils/Address.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @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
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 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://consensys.net/diligence/blog/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.8.0/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");
(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 functionCallWithValue(target, data, 0, "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");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, 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) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, 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 deleg
Submitted on: 2025-10-06 12:47:44
Comments
Log in to comment.
No comments yet.