feat: mutable storage ISMs (#4577)
### 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)pull/4834/head
parent
0264f709e4
commit
836060240b
@ -0,0 +1,7 @@ |
|||||||
|
--- |
||||||
|
'@hyperlane-xyz/cli': minor |
||||||
|
'@hyperlane-xyz/sdk': minor |
||||||
|
'@hyperlane-xyz/core': minor |
||||||
|
--- |
||||||
|
|
||||||
|
Add storage based multisig ISM types |
@ -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); |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -1,41 +0,0 @@ |
|||||||
import { expect } from 'chai'; |
|
||||||
|
|
||||||
import { ChainMap, MultisigConfig } from '@hyperlane-xyz/sdk'; |
|
||||||
|
|
||||||
import { readMultisigConfig } from '../config/multisig.js'; |
|
||||||
|
|
||||||
describe('readMultisigConfig', () => { |
|
||||||
it('parses and validates example correctly', () => { |
|
||||||
const multisig = readMultisigConfig('examples/ism.yaml'); |
|
||||||
|
|
||||||
const exampleMultisigConfig: ChainMap<MultisigConfig> = { |
|
||||||
anvil1: { |
|
||||||
threshold: 1, |
|
||||||
validators: ['0xa0Ee7A142d267C1f36714E4a8F75612F20a79720'], |
|
||||||
}, |
|
||||||
anvil2: { |
|
||||||
threshold: 1, |
|
||||||
validators: ['0xa0Ee7A142d267C1f36714E4a8F75612F20a79720'], |
|
||||||
}, |
|
||||||
}; |
|
||||||
expect(multisig).to.deep.equal(exampleMultisigConfig); |
|
||||||
}); |
|
||||||
|
|
||||||
it('parsing failure', () => { |
|
||||||
expect(function () { |
|
||||||
readMultisigConfig('src/tests/multisig/safe-parse-fail.yaml'); |
|
||||||
}).to.throw('Invalid multisig config: anvil2,validators => Required'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('threshold cannot be greater than the # of validators', () => { |
|
||||||
expect(function () { |
|
||||||
readMultisigConfig('src/tests/multisig/threshold-gt-fail.yaml'); |
|
||||||
}).to.throw('Threshold cannot be greater than number of validators'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('invalid address', () => { |
|
||||||
expect(function () { |
|
||||||
readMultisigConfig('src/tests/multisig/invalid-address-fail.yaml'); |
|
||||||
}).to.throw('Invalid multisig config: anvil2,validators,0 => Invalid'); |
|
||||||
}); |
|
||||||
}); |
|
@ -1,8 +0,0 @@ |
|||||||
anvil1: |
|
||||||
threshold: 1 # Number: Signatures required to approve a message |
|
||||||
validators: # Array: List of validator addresses |
|
||||||
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' |
|
||||||
anvil2: |
|
||||||
threshold: 1 |
|
||||||
validators: |
|
||||||
- '0xa0ee7a142d267c1n36714e4a8f7561f20a79720' |
|
@ -1,6 +0,0 @@ |
|||||||
anvil1: |
|
||||||
threshold: 1 # Number: Signatures required to approve a message |
|
||||||
validators: # Array: List of validator addresses |
|
||||||
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' |
|
||||||
anvil2: |
|
||||||
threshold: 1 |
|
@ -1,8 +0,0 @@ |
|||||||
anvil1: |
|
||||||
threshold: 1 # Number: Signatures required to approve a message |
|
||||||
validators: # Array: List of validator addresses |
|
||||||
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' |
|
||||||
anvil2: |
|
||||||
threshold: 3 |
|
||||||
validators: |
|
||||||
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' |
|
Loading…
Reference in new issue