feat: add `IStaticWeightedIsm` implementation (#4170)

### Description

- Adding the static variation of the IWeightedIsm config where you
configure validators with their corresponding weights and store it as
bytecode.

### Drive-by changes

None

### Related issues

- fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/4160

### Backward compatibility

Yes

### Testing

Fuzz tests
pull/4263/head
Kunal Arora 4 months ago committed by GitHub
parent d6ede7816c
commit d396e2c553
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      solidity/contracts/interfaces/IInterchainSecurityModule.sol
  2. 39
      solidity/contracts/interfaces/isms/IWeightedMultisigIsm.sol
  3. 16
      solidity/contracts/isms/libs/MerkleRootMultisigIsmMetadata.sol
  4. 16
      solidity/contracts/isms/libs/MessageIdMultisigIsmMetadata.sol
  5. 23
      solidity/contracts/isms/multisig/AbstractMerkleRootMultisigIsm.sol
  6. 21
      solidity/contracts/isms/multisig/AbstractMessageIdMultisigIsm.sol
  7. 67
      solidity/contracts/isms/multisig/AbstractMultisigIsm.sol
  8. 100
      solidity/contracts/isms/multisig/AbstractWeightedMultisigIsm.sol
  9. 12
      solidity/contracts/isms/multisig/StaticMultisigIsm.sol
  10. 67
      solidity/contracts/isms/multisig/WeightedMultisigIsm.sol
  11. 97
      solidity/contracts/libs/StaticWeightedValidatorSetFactory.sol
  12. 13
      solidity/test/isms/MultisigIsm.t.sol
  13. 230
      solidity/test/isms/WeightedMultisigIsm.t.sol

@ -11,7 +11,9 @@ interface IInterchainSecurityModule {
MESSAGE_ID_MULTISIG,
NULL, // used with relayer carrying no metadata
CCIP_READ,
ARB_L2_TO_L1
ARB_L2_TO_L1,
WEIGHT_MERKLE_ROOT_MULTISIG,
WEIGHT_MESSAGE_ID_MULTISIG
}
/**

@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/
import {IInterchainSecurityModule} from "../IInterchainSecurityModule.sol";
interface IStaticWeightedMultisigIsm is IInterchainSecurityModule {
// ============ Structs ============
// ValidatorInfo contains the signing address and weight of a validator
struct ValidatorInfo {
address signingAddress;
uint96 weight;
}
/**
* @notice Returns the validators and threshold weight for this ISM.
* @param _message The message to be verified
* @return validators The validators and their weights
* @return thresholdWeight The threshold weight required to pass verification
*/
function validatorsAndThresholdWeight(
bytes calldata _message
)
external
view
returns (ValidatorInfo[] memory validators, uint96 thresholdWeight);
}

@ -109,4 +109,20 @@ library MerkleRootMultisigIsmMetadata {
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;
}
}

@ -68,4 +68,20 @@ library MessageIdMultisigIsmMetadata {
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;
}
}

@ -3,7 +3,7 @@ pragma solidity >=0.8.0;
// ============ Internal Imports ============
import {IInterchainSecurityModule} from "../../interfaces/IInterchainSecurityModule.sol";
import {AbstractMultisigIsm} from "./AbstractMultisigIsm.sol";
import {AbstractMultisig} from "./AbstractMultisigIsm.sol";
import {MerkleRootMultisigIsmMetadata} from "../../isms/libs/MerkleRootMultisigIsmMetadata.sol";
import {Message} from "../../libs/Message.sol";
import {MerkleLib} from "../../libs/Merkle.sol";
@ -22,23 +22,19 @@ import {CheckpointLib} from "../../libs/CheckpointLib.sol";
* 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 AbstractMultisigIsm {
abstract contract AbstractMerkleRootMultisigIsm is AbstractMultisig {
using MerkleRootMultisigIsmMetadata for bytes;
using Message for bytes;
// ============ Constants ============
// solhint-disable-next-line const-name-snakecase
uint8 public constant moduleType =
uint8(IInterchainSecurityModule.Types.MERKLE_ROOT_MULTISIG);
/**
* @inheritdoc AbstractMultisigIsm
* @inheritdoc AbstractMultisig
*/
function digest(
bytes calldata _metadata,
bytes calldata _message
) internal pure override returns (bytes32) {
) internal pure virtual override returns (bytes32) {
require(
_metadata.messageIndex() <= _metadata.signedIndex(),
"Invalid merkle index metadata"
@ -61,7 +57,7 @@ abstract contract AbstractMerkleRootMultisigIsm is AbstractMultisigIsm {
}
/**
* @inheritdoc AbstractMultisigIsm
* @inheritdoc AbstractMultisig
*/
function signatureAt(
bytes calldata _metadata,
@ -69,4 +65,13 @@ abstract contract AbstractMerkleRootMultisigIsm is AbstractMultisigIsm {
) 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();
}
}

@ -3,7 +3,7 @@ pragma solidity >=0.8.0;
// ============ Internal Imports ============
import {IInterchainSecurityModule} from "../../interfaces/IInterchainSecurityModule.sol";
import {AbstractMultisigIsm} from "./AbstractMultisigIsm.sol";
import {AbstractMultisig} from "./AbstractMultisigIsm.sol";
import {MessageIdMultisigIsmMetadata} from "../libs/MessageIdMultisigIsmMetadata.sol";
import {Message} from "../../libs/Message.sol";
import {CheckpointLib} from "../../libs/CheckpointLib.sol";
@ -18,18 +18,14 @@ import {CheckpointLib} from "../../libs/CheckpointLib.sol";
* @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 AbstractMultisigIsm {
abstract contract AbstractMessageIdMultisigIsm is AbstractMultisig {
using Message for bytes;
using MessageIdMultisigIsmMetadata for bytes;
// ============ Constants ============
// solhint-disable-next-line const-name-snakecase
uint8 public constant moduleType =
uint8(IInterchainSecurityModule.Types.MESSAGE_ID_MULTISIG);
/**
* @inheritdoc AbstractMultisigIsm
* @inheritdoc AbstractMultisig
*/
function digest(
bytes calldata _metadata,
@ -46,7 +42,7 @@ abstract contract AbstractMessageIdMultisigIsm is AbstractMultisigIsm {
}
/**
* @inheritdoc AbstractMultisigIsm
* @inheritdoc AbstractMultisig
*/
function signatureAt(
bytes calldata _metadata,
@ -54,4 +50,13 @@ abstract contract AbstractMessageIdMultisigIsm is AbstractMultisigIsm {
) 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();
}
}

@ -1,6 +1,18 @@
// 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";
@ -11,30 +23,13 @@ import {Message} from "../../libs/Message.sol";
import {MerkleLib} from "../../libs/Merkle.sol";
/**
* @title MultisigIsm
* @notice Manages per-domain m-of-n Validator sets that are used to verify
* interchain messages.
* @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 AbstractMultisigIsm is 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);
abstract contract AbstractMultisig {
/**
* @notice Returns the digest to be used for signature verification.
* @param _metadata ABI encoded module metadata
@ -57,6 +52,38 @@ abstract contract AbstractMultisigIsm is IMultisigIsm {
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 {
// ============ 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 ============
/**

@ -0,0 +1,100 @@
// 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";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
// ============ Internal Imports ============
import {IInterchainSecurityModule} from "../../interfaces/IInterchainSecurityModule.sol";
import {IStaticWeightedMultisigIsm} from "../../interfaces/isms/IWeightedMultisigIsm.sol";
import {Message} from "../../libs/Message.sol";
import {MerkleLib} from "../../libs/Merkle.sol";
import {AbstractMultisig} from "./AbstractMultisigIsm.sol";
/**
* @title AbstractStaticWeightedMultisigIsm
* @notice Manages per-domain m-of-n Validator sets with stake weights that are used to verify
* interchain messages.
*/
abstract contract AbstractStaticWeightedMultisigIsm is
AbstractMultisig,
IStaticWeightedMultisigIsm
{
// ============ Constants ============
// total weight of all validators
uint96 public constant TOTAL_WEIGHT = 1e10;
/**
* @inheritdoc IStaticWeightedMultisigIsm
*/
function validatorsAndThresholdWeight(
bytes calldata /* _message*/
) public view virtual returns (ValidatorInfo[] memory, uint96);
/**
* @inheritdoc IInterchainSecurityModule
*/
function verify(
bytes calldata _metadata,
bytes calldata _message
) public view virtual returns (bool) {
bytes32 _digest = digest(_metadata, _message);
(
ValidatorInfo[] memory _validators,
uint96 _thresholdWeight
) = validatorsAndThresholdWeight(_message);
require(
_thresholdWeight > 0 && _thresholdWeight <= TOTAL_WEIGHT,
"Invalid threshold weight"
);
uint256 _validatorCount = Math.min(
_validators.length,
signatureCount(_metadata)
);
uint256 _validatorIndex = 0;
uint96 _totalWeight = 0;
// assumes that signatures are ordered by validator
for (
uint256 i = 0;
_totalWeight < _thresholdWeight && i < _validatorCount;
++i
) {
address _signer = ECDSA.recover(_digest, signatureAt(_metadata, i));
// loop through remaining validators until we find a match
while (
_validatorIndex < _validatorCount &&
_signer != _validators[_validatorIndex].signingAddress
) {
++_validatorIndex;
}
// fail if we never found a match
require(_validatorIndex < _validatorCount, "Invalid signer");
// add the weight of the current validator
_totalWeight += _validators[_validatorIndex].weight;
}
require(
_totalWeight >= _thresholdWeight,
"Insufficient validator weight"
);
return true;
}
}

@ -1,6 +1,8 @@
// 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";
@ -34,7 +36,10 @@ abstract contract AbstractMetaProxyMultisigIsm is AbstractMultisigIsm {
contract StaticMerkleRootMultisigIsm is
AbstractMerkleRootMultisigIsm,
AbstractMetaProxyMultisigIsm
{}
{
uint8 public constant moduleType =
uint8(IInterchainSecurityModule.Types.MERKLE_ROOT_MULTISIG);
}
/**
* @title StaticMessageIdMultisigIsm
@ -44,7 +49,10 @@ contract StaticMerkleRootMultisigIsm is
contract StaticMessageIdMultisigIsm is
AbstractMessageIdMultisigIsm,
AbstractMetaProxyMultisigIsm
{}
{
uint8 public constant moduleType =
uint8(IInterchainSecurityModule.Types.MESSAGE_ID_MULTISIG);
}
// solhint-enable no-empty-blocks

@ -0,0 +1,67 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/
import {IInterchainSecurityModule} from "../../interfaces/IInterchainSecurityModule.sol";
import {AbstractMerkleRootMultisigIsm} from "./AbstractMerkleRootMultisigIsm.sol";
import {AbstractMessageIdMultisigIsm} from "./AbstractMessageIdMultisigIsm.sol";
import {AbstractStaticWeightedMultisigIsm} from "./AbstractWeightedMultisigIsm.sol";
import {AbstractMultisigIsm} from "./AbstractMultisigIsm.sol";
import {StaticWeightedValidatorSetFactory} from "../../libs/StaticWeightedValidatorSetFactory.sol";
import {MetaProxy} from "../../libs/MetaProxy.sol";
abstract contract AbstractMetaProxyWeightedMultisigIsm is
AbstractStaticWeightedMultisigIsm
{
/**
* @inheritdoc AbstractStaticWeightedMultisigIsm
*/
function validatorsAndThresholdWeight(
bytes calldata /* _message*/
) public pure override returns (ValidatorInfo[] memory, uint96) {
return abi.decode(MetaProxy.metadata(), (ValidatorInfo[], uint96));
}
}
contract StaticMerkleRootWeightedMultisigIsm is
AbstractMerkleRootMultisigIsm,
AbstractMetaProxyWeightedMultisigIsm
{
uint8 public constant moduleType =
uint8(IInterchainSecurityModule.Types.WEIGHT_MERKLE_ROOT_MULTISIG);
}
contract StaticMessageIdWeightedMultisigIsm is
AbstractMessageIdMultisigIsm,
AbstractMetaProxyWeightedMultisigIsm
{
uint8 public constant moduleType =
uint8(IInterchainSecurityModule.Types.WEIGHT_MESSAGE_ID_MULTISIG);
}
contract StaticMerkleRootWeightedMultisigIsmFactory is
StaticWeightedValidatorSetFactory
{
function _deployImplementation() internal override returns (address) {
return address(new StaticMerkleRootWeightedMultisigIsm());
}
}
contract StaticMessageIdWeightedMultisigIsmFactory is
StaticWeightedValidatorSetFactory
{
function _deployImplementation() internal override returns (address) {
return address(new StaticMessageIdWeightedMultisigIsm());
}
}

@ -0,0 +1,97 @@
// 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";
import {IStaticWeightedMultisigIsm} from "../interfaces/isms/IWeightedMultisigIsm.sol";
// ============ Internal Imports ============
import {MetaProxy} from "./MetaProxy.sol";
abstract contract StaticWeightedValidatorSetFactory {
// ============ Immutables ============
address public immutable implementation;
// ============ Constructor ============
constructor() {
implementation = _deployImplementation();
}
function _deployImplementation() internal virtual returns (address);
/**
* @notice Deploys a StaticWeightedValidatorSet contract address for the given
* values
* @dev Consider sorting addresses to ensure contract reuse
* @param _validators An array of addresses
* @param _thresholdWeight The threshold weight value to use
* @return set The contract address representing this StaticWeightedValidatorSet
*/
function deploy(
IStaticWeightedMultisigIsm.ValidatorInfo[] calldata _validators,
uint96 _thresholdWeight
) public returns (address) {
(bytes32 _salt, bytes memory _bytecode) = _saltAndBytecode(
_validators,
_thresholdWeight
);
address _set = _getAddress(_salt, _bytecode);
if (!Address.isContract(_set)) {
_set = Create2.deploy(0, _salt, _bytecode);
}
return _set;
}
/**
* @notice Returns the StaticWeightedValidatorSet contract address for the given
* values
* @dev Consider sorting addresses to ensure contract reuse
* @param _validators An array of addresses
* @param _thresholdWeight The threshold weight value to use
* @return set The contract address representing this StaticWeightedValidatorSet
*/
function getAddress(
IStaticWeightedMultisigIsm.ValidatorInfo[] calldata _validators,
uint96 _thresholdWeight
) external view returns (address) {
(bytes32 _salt, bytes memory _bytecode) = _saltAndBytecode(
_validators,
_thresholdWeight
);
return _getAddress(_salt, _bytecode);
}
/**
* @notice Returns the StaticWeightedValidatorSet 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 StaticWeightedValidatorSet
*/
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 _validators An array of addresses
* @param _thresholdWeight The threshold weight value to use
* @return _salt The salt used in Create2
* @return _bytecode The metaproxy bytecode used in Create2
*/
function _saltAndBytecode(
IStaticWeightedMultisigIsm.ValidatorInfo[] calldata _validators,
uint96 _thresholdWeight
) internal view returns (bytes32, bytes memory) {
bytes memory _metadata = abi.encode(_validators, _thresholdWeight);
bytes memory _bytecode = MetaProxy.bytecode(implementation, _metadata);
bytes32 _salt = keccak256(_metadata);
return (_salt, _bytecode);
}
}

@ -5,6 +5,7 @@ import "forge-std/Test.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import {IInterchainSecurityModule} from "../../contracts/interfaces/IInterchainSecurityModule.sol";
import {IMultisigIsm} from "../../contracts/interfaces/isms/IMultisigIsm.sol";
import {TestMailbox} from "../../contracts/test/TestMailbox.sol";
import {StaticMerkleRootMultisigIsmFactory, StaticMessageIdMultisigIsmFactory} from "../../contracts/isms/multisig/StaticMultisigIsm.sol";
@ -32,7 +33,7 @@ abstract contract AbstractMultisigIsmTest is Test {
uint32 constant ORIGIN = 11;
StaticThresholdAddressSetFactory factory;
IMultisigIsm ism;
IInterchainSecurityModule ism;
TestMerkleTreeHook internal merkleTreeHook;
TestPostDispatchHook internal noopHook;
TestMailbox mailbox;
@ -86,7 +87,7 @@ abstract contract AbstractMultisigIsmTest is Test {
uint8 n,
bytes32 seed,
bytes memory message
) internal returns (bytes memory) {
) internal virtual returns (bytes memory) {
bytes32 digest;
{
uint32 domain = mailbox.localDomain();
@ -114,8 +115,8 @@ abstract contract AbstractMultisigIsmTest is Test {
for (uint256 i = 0; i < m; i++) {
(uint8 v, bytes32 r, bytes32 s) = vm.sign(signers[i], digest);
metadata = abi.encodePacked(metadata, r, s, v);
metadata = abi.encodePacked(metadata, r, s, v);
fixtureAppendSignature(i, v, r, s);
}
@ -128,7 +129,7 @@ abstract contract AbstractMultisigIsmTest is Test {
uint8 m,
uint8 n,
bytes32 seed
) internal returns (uint256[] memory) {
) internal virtual returns (uint256[] memory) {
uint256[] memory keys = new uint256[](n);
address[] memory addresses = new address[](n);
for (uint256 i = 0; i < n; i++) {
@ -194,7 +195,7 @@ contract MerkleRootMultisigIsmTest is AbstractMultisigIsmTest {
string constant proofKey = "proof";
function setUp() public {
function setUp() public virtual {
mailbox = new TestMailbox(ORIGIN);
merkleTreeHook = new TestMerkleTreeHook(address(mailbox));
noopHook = new TestPostDispatchHook();
@ -250,7 +251,7 @@ contract MerkleRootMultisigIsmTest is AbstractMultisigIsmTest {
contract MessageIdMultisigIsmTest is AbstractMultisigIsmTest {
using TypeCasts for address;
function setUp() public {
function setUp() public virtual {
mailbox = new TestMailbox(ORIGIN);
merkleTreeHook = new TestMerkleTreeHook(address(mailbox));
noopHook = new TestPostDispatchHook();

@ -0,0 +1,230 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;
import {IInterchainSecurityModule} from "../../contracts/interfaces/IInterchainSecurityModule.sol";
import {Message} from "../../contracts/libs/Message.sol";
import {IStaticWeightedMultisigIsm} from "../../contracts/interfaces/isms/IWeightedMultisigIsm.sol";
import {StaticMerkleRootWeightedMultisigIsmFactory, StaticMessageIdWeightedMultisigIsmFactory} from "../../contracts/isms/multisig/WeightedMultisigIsm.sol";
import {TypeCasts} from "../../contracts/libs/TypeCasts.sol";
import {CheckpointLib} from "../../contracts/libs/CheckpointLib.sol";
import {StaticWeightedValidatorSetFactory} from "../../contracts/libs/StaticWeightedValidatorSetFactory.sol";
import {AbstractStaticWeightedMultisigIsm} from "../../contracts/isms/multisig/AbstractWeightedMultisigIsm.sol";
import {TestMailbox} from "../../contracts/test/TestMailbox.sol";
import {TestMerkleTreeHook} from "../../contracts/test/TestMerkleTreeHook.sol";
import {TestPostDispatchHook} from "../../contracts/test/TestPostDispatchHook.sol";
import {MessageIdMultisigIsmMetadata} from "../../contracts/isms/libs/MessageIdMultisigIsmMetadata.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {AbstractMultisigIsmTest, MerkleRootMultisigIsmTest, MessageIdMultisigIsmTest} from "./MultisigIsm.t.sol";
abstract contract AbstractStaticWeightedMultisigIsmTest is
AbstractMultisigIsmTest
{
using Math for uint256;
using Message for bytes;
using TypeCasts for address;
StaticWeightedValidatorSetFactory weightedFactory;
AbstractStaticWeightedMultisigIsm weightedIsm;
uint96 public constant TOTAL_WEIGHT = 1e10;
function addValidators(
uint96 threshold,
uint8 n,
bytes32 seed
)
internal
returns (
uint256[] memory,
IStaticWeightedMultisigIsm.ValidatorInfo[] memory
)
{
bound(threshold, 0, TOTAL_WEIGHT);
uint256[] memory keys = new uint256[](n);
IStaticWeightedMultisigIsm.ValidatorInfo[]
memory validators = new IStaticWeightedMultisigIsm.ValidatorInfo[](
n
);
uint256 remainingWeight = TOTAL_WEIGHT;
for (uint256 i = 0; i < n; i++) {
uint256 key = uint256(keccak256(abi.encode(seed, i)));
keys[i] = key;
validators[i].signingAddress = vm.addr(key);
if (i == n - 1) {
validators[i].weight = uint96(remainingWeight);
} else {
uint256 weight = (uint256(
keccak256(abi.encode(seed, "weight", i))
) % (remainingWeight + 1));
validators[i].weight = uint96(weight);
remainingWeight -= weight;
}
}
// ism = IInterchainSecurityModule(deployedIsm);
ism = IInterchainSecurityModule(
weightedFactory.deploy(validators, threshold)
);
weightedIsm = AbstractStaticWeightedMultisigIsm(address(ism));
return (keys, validators);
}
function getMetadata(
uint8 m,
uint8 n,
bytes32 seed,
bytes memory message
) internal virtual override returns (bytes memory) {
bytes32 digest;
{
uint32 domain = mailbox.localDomain();
(bytes32 root, uint32 index) = merkleTreeHook.latestCheckpoint();
bytes32 messageId = message.id();
bytes32 merkleTreeAddress = address(merkleTreeHook)
.addressToBytes32();
digest = CheckpointLib.digest(
domain,
merkleTreeAddress,
root,
index,
messageId
);
}
uint96 threshold = uint96(
(uint256(m)).mulDiv(TOTAL_WEIGHT, type(uint8).max)
);
(
uint256[] memory keys,
IStaticWeightedMultisigIsm.ValidatorInfo[] memory allValidators
) = addValidators(threshold, n, seed);
(, uint96 thresholdWeight) = weightedIsm.validatorsAndThresholdWeight(
message
);
bytes memory metadata = metadataPrefix(message);
fixtureInit();
uint96 totalWeight = 0;
uint256 signerCount = 0;
while (
totalWeight < thresholdWeight && signerCount < allValidators.length
) {
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
keys[signerCount],
digest
);
metadata = abi.encodePacked(metadata, r, s, v);
fixtureAppendSignature(signerCount, v, r, s);
totalWeight += allValidators[signerCount].weight;
signerCount++;
}
writeFixture(metadata, uint8(signerCount), n);
return metadata;
}
function testVerify_revertInsufficientWeight(
uint32 destination,
bytes32 recipient,
bytes calldata body,
uint8 m,
uint8 n,
bytes32 seed
) public {
vm.assume(0 < m && m <= n && n < 10);
bytes memory message = getMessage(destination, recipient, body);
bytes memory metadata = getMetadata(m, n, seed, message);
uint256 signatureCount = weightedIsm.signatureCount(metadata);
vm.assume(signatureCount >= 1);
uint256 newLength = metadata.length - 65;
bytes memory insufficientMetadata = new bytes(newLength);
for (uint256 i = 0; i < newLength; i++) {
insufficientMetadata[i] = metadata[i];
}
vm.expectRevert("Insufficient validator weight");
ism.verify(insufficientMetadata, message);
}
}
contract StaticMerkleRootWeightedMultisigIsmTest is
MerkleRootMultisigIsmTest,
AbstractStaticWeightedMultisigIsmTest
{
function setUp() public override {
mailbox = new TestMailbox(ORIGIN);
merkleTreeHook = new TestMerkleTreeHook(address(mailbox));
noopHook = new TestPostDispatchHook();
weightedFactory = new StaticMerkleRootWeightedMultisigIsmFactory();
mailbox.setDefaultHook(address(merkleTreeHook));
mailbox.setRequiredHook(address(noopHook));
}
function getMetadata(
uint8 m,
uint8 n,
bytes32 seed,
bytes memory message
)
internal
override(AbstractMultisigIsmTest, AbstractStaticWeightedMultisigIsmTest)
returns (bytes memory)
{
return
AbstractStaticWeightedMultisigIsmTest.getMetadata(
m,
n,
seed,
message
);
}
}
contract StaticMessageIdWeightedMultisigIsmTest is
MessageIdMultisigIsmTest,
AbstractStaticWeightedMultisigIsmTest
{
function setUp() public override {
mailbox = new TestMailbox(ORIGIN);
merkleTreeHook = new TestMerkleTreeHook(address(mailbox));
noopHook = new TestPostDispatchHook();
weightedFactory = new StaticMessageIdWeightedMultisigIsmFactory();
mailbox.setDefaultHook(address(merkleTreeHook));
mailbox.setRequiredHook(address(noopHook));
}
function getMetadata(
uint8 m,
uint8 n,
bytes32 seed,
bytes memory message
)
internal
override(AbstractMultisigIsmTest, AbstractStaticWeightedMultisigIsmTest)
returns (bytes memory)
{
return
AbstractStaticWeightedMultisigIsmTest.getMetadata(
m,
n,
seed,
message
);
}
}
Loading…
Cancel
Save