Skip to content

Commit

Permalink
feat: mutable storage ISMs (#4577)
Browse files Browse the repository at this point in the history
### Description

Some chains like zkSync do not support eip1167 (minimal/meta) proxies.
This PR adds an alternative storage based multisig and aggregation ISM
for use on these chains.

### Drive-by changes

Simplify CLI multisig interactive config builder. Remove stale multisig
config.

### Related issues

None

### Backward compatibility

Yes, relayer already supports this module type

### Testing

Contract unit tests
Manual CLI tests

![Screenshot 2024-10-02 at 4 05
08 PM](https://github.com/user-attachments/assets/c7fec896-ea7c-4fd9-a313-463168e66a82)
  • Loading branch information
yorhodes authored Nov 6, 2024
1 parent 0264f70 commit 8360602
Show file tree
Hide file tree
Showing 23 changed files with 549 additions and 155 deletions.
7 changes: 7 additions & 0 deletions .changeset/lazy-carpets-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@hyperlane-xyz/cli': minor
'@hyperlane-xyz/sdk': minor
'@hyperlane-xyz/core': minor
---

Add storage based multisig ISM types
9 changes: 9 additions & 0 deletions solidity/contracts/interfaces/IThresholdAddressFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

interface IThresholdAddressFactory {
function deploy(
address[] calldata _values,
uint8 _threshold
) external returns (address);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {IInterchainSecurityModule} from "../../interfaces/IInterchainSecurityModule.sol";
import {IAggregationIsm} from "../../interfaces/isms/IAggregationIsm.sol";
import {AggregationIsmMetadata} from "../../isms/libs/AggregationIsmMetadata.sol";
import {PackageVersioned} from "../../PackageVersioned.sol";

/**
* @title AggregationIsm
* @notice Manages per-domain m-of-n ISM sets that are used to verify
* interchain messages.
*/
abstract contract AbstractAggregationIsm is IAggregationIsm {
abstract contract AbstractAggregationIsm is IAggregationIsm, PackageVersioned {
// ============ Constants ============

// solhint-disable-next-line const-name-snakecase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {PackageVersioned} from "contracts/PackageVersioned.sol";
* @notice Manages per-domain m-of-n ISM sets that are used to verify
* interchain messages.
*/
contract StaticAggregationIsm is AbstractAggregationIsm, PackageVersioned {
contract StaticAggregationIsm is AbstractAggregationIsm {
// ============ Public Functions ============

/**
Expand Down
89 changes: 89 additions & 0 deletions solidity/contracts/isms/aggregation/StorageAggregationIsm.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

// ============ Internal Imports ============
import {AbstractAggregationIsm} from "./AbstractAggregationIsm.sol";
import {IInterchainSecurityModule} from "../../interfaces/IInterchainSecurityModule.sol";
import {IThresholdAddressFactory} from "../../interfaces/IThresholdAddressFactory.sol";
import {MinimalProxy} from "../../libs/MinimalProxy.sol";
import {PackageVersioned} from "../../PackageVersioned.sol";

// ============ External Imports ============
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";

contract StorageAggregationIsm is
AbstractAggregationIsm,
Ownable2StepUpgradeable
{
address[] public modules;
uint8 public threshold;

event ModulesAndThresholdSet(address[] modules, uint8 threshold);

constructor(
address[] memory _modules,
uint8 _threshold
) Ownable2StepUpgradeable() {
modules = _modules;
threshold = _threshold;
_disableInitializers();
}

function initialize(
address _owner,
address[] memory _modules,
uint8 _threshold
) external initializer {
__Ownable2Step_init();
setModulesAndThreshold(_modules, _threshold);
_transferOwnership(_owner);
}

function setModulesAndThreshold(
address[] memory _modules,
uint8 _threshold
) public onlyOwner {
require(
0 < _threshold && _threshold <= _modules.length,
"Invalid threshold"
);
modules = _modules;
threshold = _threshold;
emit ModulesAndThresholdSet(_modules, _threshold);
}

function modulesAndThreshold(
bytes calldata /* _message */
) public view override returns (address[] memory, uint8) {
return (modules, threshold);
}
}

contract StorageAggregationIsmFactory is
IThresholdAddressFactory,
PackageVersioned
{
address public immutable implementation;

constructor() {
implementation = address(
new StorageAggregationIsm(new address[](1), 1)
);
}

/**
* @notice Emitted when a multisig module is deployed
* @param module The deployed ISM
*/
event ModuleDeployed(address module);

// ============ External Functions ============
function deploy(
address[] calldata _modules,
uint8 _threshold
) external returns (address ism) {
ism = MinimalProxy.create(implementation);
emit ModuleDeployed(ism);
StorageAggregationIsm(ism).initialize(msg.sender, _modules, _threshold);
}
}
2 changes: 1 addition & 1 deletion solidity/contracts/isms/multisig/AbstractMultisigIsm.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ abstract contract AbstractMultisig is PackageVersioned {
* @notice Manages per-domain m-of-n Validator sets of AbstractMultisig that are used to verify
* interchain messages.
*/
abstract contract AbstractMultisigIsm is AbstractMultisig {
abstract contract AbstractMultisigIsm is AbstractMultisig, IMultisigIsm {
// ============ Virtual Functions ============
// ======= OVERRIDE THESE TO IMPLEMENT =======

Expand Down
143 changes: 143 additions & 0 deletions solidity/contracts/isms/multisig/StorageMultisigIsm.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

// ============ Internal Imports ============
import {AbstractMultisigIsm} from "./AbstractMultisigIsm.sol";
import {AbstractMerkleRootMultisigIsm} from "./AbstractMerkleRootMultisigIsm.sol";
import {AbstractMessageIdMultisigIsm} from "./AbstractMessageIdMultisigIsm.sol";
import {IInterchainSecurityModule} from "../../interfaces/IInterchainSecurityModule.sol";
import {IThresholdAddressFactory} from "../../interfaces/IThresholdAddressFactory.sol";
import {MinimalProxy} from "../../libs/MinimalProxy.sol";
import {PackageVersioned} from "../../PackageVersioned.sol";

// ============ External Imports ============
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";

abstract contract AbstractStorageMultisigIsm is
AbstractMultisigIsm,
Ownable2StepUpgradeable
{
address[] public validators;
uint8 public threshold;

event ValidatorsAndThresholdSet(address[] validators, uint8 threshold);

constructor(
address[] memory _validators,
uint8 _threshold
) Ownable2StepUpgradeable() {
validators = _validators;
threshold = _threshold;
_disableInitializers();
}

function initialize(
address _owner,
address[] memory _validators,
uint8 _threshold
) external initializer {
__Ownable2Step_init();
setValidatorsAndThreshold(_validators, _threshold);
_transferOwnership(_owner);
}

function setValidatorsAndThreshold(
address[] memory _validators,
uint8 _threshold
) public onlyOwner {
require(
0 < _threshold && _threshold <= _validators.length,
"Invalid threshold"
);
validators = _validators;
threshold = _threshold;
emit ValidatorsAndThresholdSet(_validators, _threshold);
}

function validatorsAndThreshold(
bytes calldata /* _message */
) public view override returns (address[] memory, uint8) {
return (validators, threshold);
}
}

contract StorageMerkleRootMultisigIsm is
AbstractMerkleRootMultisigIsm,
AbstractStorageMultisigIsm
{
uint8 public constant moduleType =
uint8(IInterchainSecurityModule.Types.MERKLE_ROOT_MULTISIG);

constructor(
address[] memory _validators,
uint8 _threshold
) AbstractStorageMultisigIsm(_validators, _threshold) {}
}

contract StorageMessageIdMultisigIsm is
AbstractMessageIdMultisigIsm,
AbstractStorageMultisigIsm
{
uint8 public constant moduleType =
uint8(IInterchainSecurityModule.Types.MESSAGE_ID_MULTISIG);

constructor(
address[] memory _validators,
uint8 _threshold
) AbstractStorageMultisigIsm(_validators, _threshold) {}
}

abstract contract StorageMultisigIsmFactory is
IThresholdAddressFactory,
PackageVersioned
{
/**
* @notice Emitted when a multisig module is deployed
* @param module The deployed ISM
*/
event ModuleDeployed(address module);

// ============ External Functions ============
function deploy(
address[] calldata _validators,
uint8 _threshold
) external returns (address ism) {
ism = MinimalProxy.create(implementation());
emit ModuleDeployed(ism);
AbstractStorageMultisigIsm(ism).initialize(
msg.sender,
_validators,
_threshold
);
}

function implementation() public view virtual returns (address);
}

contract StorageMerkleRootMultisigIsmFactory is StorageMultisigIsmFactory {
address internal immutable _implementation;

constructor() {
_implementation = address(
new StorageMerkleRootMultisigIsm(new address[](0), 0)
);
}

function implementation() public view override returns (address) {
return _implementation;
}
}

contract StorageMessageIdMultisigIsmFactory is StorageMultisigIsmFactory {
address internal immutable _implementation;

constructor() {
_implementation = address(
new StorageMessageIdMultisigIsm(new address[](0), 0)
);
}

function implementation() public view override returns (address) {
return _implementation;
}
}
10 changes: 9 additions & 1 deletion solidity/contracts/libs/StaticAddressSetFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ 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 {
abstract contract StaticThresholdAddressSetFactory is
PackageVersioned,
IThresholdAddressFactory
{
// ============ Immutables ============
address public immutable implementation;

Expand All @@ -32,6 +36,10 @@ abstract contract StaticThresholdAddressSetFactory is PackageVersioned {
address[] calldata _values,
uint8 _threshold
) public returns (address) {
require(
0 < _threshold && _threshold <= _values.length,
"Invalid threshold"
);
(bytes32 _salt, bytes memory _bytecode) = _saltAndBytecode(
_values,
_threshold
Expand Down
2 changes: 2 additions & 0 deletions solidity/test/hooks/AggregationHook.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ contract AggregationHookTest is Test {
uint8 n,
uint256 fee
) internal returns (address[] memory) {
vm.assume(n > 0);

address[] memory hooks = new address[](n);
for (uint8 i = 0; i < n; i++) {
TestPostDispatchHook subHook = new TestPostDispatchHook();
Expand Down
Loading

0 comments on commit 8360602

Please sign in to comment.