From 4fc474ed55c34bb91f87908e486872f769dc961e Mon Sep 17 00:00:00 2001 From: Asa Oines Date: Tue, 6 Dec 2022 14:00:59 -0500 Subject: [PATCH] Smaller MultisigMetadata (#1394) --- .../hyperlane-ethereum/abis/Mailbox.abi.json | 39 ++++++++++++++ .../abis/MultisigIsm.abi.json | 8 +-- .../hyperlane-ethereum/src/multisig_ism.rs | 51 ++++++++++++------- rust/config/test/test_config.json | 4 +- .../hyperlane-core/src/traits/multisig_ism.rs | 4 +- rust/hyperlane-core/src/types/checkpoint.rs | 4 +- solidity/contracts/isms/MultisigIsm.sol | 11 ++-- .../contracts/libs/MultisigIsmMetadata.sol | 36 +++++++------ solidity/interfaces/IMultisigIsm.sol | 2 +- solidity/test/lib/mailboxes.ts | 2 +- .../src/deploy/core/HyperlaneCoreChecker.ts | 4 +- .../src/deploy/core/HyperlaneCoreDeployer.ts | 2 +- typescript/sdk/src/gas/calculator.test.ts | 2 +- typescript/sdk/src/gas/calculator.ts | 4 +- typescript/utils/src/utils.ts | 12 ++--- typescript/utils/src/validator.ts | 2 +- 16 files changed, 117 insertions(+), 70 deletions(-) diff --git a/rust/chains/hyperlane-ethereum/abis/Mailbox.abi.json b/rust/chains/hyperlane-ethereum/abis/Mailbox.abi.json index cbd5b914e..f254b57a4 100644 --- a/rust/chains/hyperlane-ethereum/abis/Mailbox.abi.json +++ b/rust/chains/hyperlane-ethereum/abis/Mailbox.abi.json @@ -99,6 +99,12 @@ "name": "OwnershipTransferred", "type": "event" }, + { + "anonymous": false, + "inputs": [], + "name": "Paused", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -137,6 +143,12 @@ "name": "ProcessId", "type": "event" }, + { + "anonymous": false, + "inputs": [], + "name": "Unpaused", + "type": "event" + }, { "inputs": [], "name": "MAX_MESSAGE_BODY_BYTES", @@ -250,6 +262,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "isPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "latestCheckpoint", @@ -294,6 +319,13 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -370,5 +402,12 @@ ], "stateMutability": "view", "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" } ] diff --git a/rust/chains/hyperlane-ethereum/abis/MultisigIsm.abi.json b/rust/chains/hyperlane-ethereum/abis/MultisigIsm.abi.json index c68b5bc87..c769279b3 100644 --- a/rust/chains/hyperlane-ethereum/abis/MultisigIsm.abi.json +++ b/rust/chains/hyperlane-ethereum/abis/MultisigIsm.abi.json @@ -199,9 +199,9 @@ "type": "uint32" }, { - "internalType": "uint256", + "internalType": "uint8", "name": "_threshold", - "type": "uint256" + "type": "uint8" } ], "name": "setThreshold", @@ -220,9 +220,9 @@ "name": "threshold", "outputs": [ { - "internalType": "uint256", + "internalType": "uint8", "name": "", - "type": "uint256" + "type": "uint8" } ], "stateMutability": "view", diff --git a/rust/chains/hyperlane-ethereum/src/multisig_ism.rs b/rust/chains/hyperlane-ethereum/src/multisig_ism.rs index 7b076ea26..98b07e7cc 100644 --- a/rust/chains/hyperlane-ethereum/src/multisig_ism.rs +++ b/rust/chains/hyperlane-ethereum/src/multisig_ism.rs @@ -9,7 +9,7 @@ use std::time::{Duration, Instant}; use async_trait::async_trait; use ethers::abi::Token; use ethers::providers::Middleware; -use ethers::types::{Selector, H160, H256, U256}; +use ethers::types::{Selector, H160, H256}; use eyre::Result; use tokio::sync::RwLock; use tracing::instrument; @@ -112,7 +112,7 @@ where chain_name: String, #[allow(dead_code)] provider: Arc, - threshold_cache: RwLock>, + threshold_cache: RwLock>, validators_cache: RwLock>>, } @@ -170,23 +170,16 @@ where checkpoint: &MultisigSignedCheckpoint, proof: Proof, ) -> Result, ChainCommunicationError> { - let threshold = self.threshold(checkpoint.checkpoint.mailbox_domain).await?; - let validator_addresses: Vec = self - .validators(checkpoint.checkpoint.mailbox_domain) - .await?; - let validators: Vec = validator_addresses.iter().map(|&x| H256::from(x)).collect(); - let validator_tokens: Vec = validators - .iter() - .map(|x| Token::FixedBytes(x.to_fixed_bytes().into())) - .collect(); + let root_bytes = checkpoint.checkpoint.root.to_fixed_bytes().into(); + + let index_bytes = checkpoint.checkpoint.index.to_be_bytes().into(); + let proof_tokens: Vec = proof .path .iter() .map(|x| Token::FixedBytes(x.to_fixed_bytes().into())) .collect(); - let prefix = ethers::abi::encode(&[ - Token::FixedBytes(checkpoint.checkpoint.root.to_fixed_bytes().into()), - Token::Uint(U256::from(checkpoint.checkpoint.index)), + let mailbox_and_proof_bytes = ethers::abi::encode(&[ Token::FixedBytes( checkpoint .checkpoint @@ -195,20 +188,42 @@ where .into(), ), Token::FixedArray(proof_tokens), - Token::Uint(threshold), ]); - let suffix = ethers::abi::encode(&[Token::FixedArray(validator_tokens)]); + + let threshold = self.threshold(checkpoint.checkpoint.mailbox_domain).await?; + let threshold_bytes = threshold.to_be_bytes().into(); + + let validator_addresses: Vec = self + .validators(checkpoint.checkpoint.mailbox_domain) + .await?; + // The ethers encoder likes to zero-pad non word-aligned byte arrays. // Thus, we pack the signatures, which are not word-aligned, ourselves. let signature_vecs: Vec> = order_signatures(&validator_addresses, &checkpoint.signatures); let signature_bytes = signature_vecs.concat(); - let metadata = [prefix, signature_bytes, suffix].concat(); + + let validators: Vec = validator_addresses.iter().map(|&x| H256::from(x)).collect(); + let validator_tokens: Vec = validators + .iter() + .map(|x| Token::FixedBytes(x.to_fixed_bytes().into())) + .collect(); + let validator_bytes = ethers::abi::encode(&[Token::FixedArray(validator_tokens)]); + + let metadata = [ + root_bytes, + index_bytes, + mailbox_and_proof_bytes, + threshold_bytes, + signature_bytes, + validator_bytes, + ] + .concat(); Ok(metadata) } #[instrument(err, ret, skip(self))] - async fn threshold(&self, domain: u32) -> Result { + async fn threshold(&self, domain: u32) -> Result { let entry = self.threshold_cache.read().await.get(domain).cloned(); if let Some(threshold) = entry { Ok(threshold) diff --git a/rust/config/test/test_config.json b/rust/config/test/test_config.json index 4d12ec839..e594ca6e0 100644 --- a/rust/config/test/test_config.json +++ b/rust/config/test/test_config.json @@ -34,7 +34,7 @@ "url": "" }, "index": { - "from": "17" + "from": "16" } }, "test3": { @@ -52,7 +52,7 @@ "url": "" }, "index": { - "from": "29" + "from": "28" } } }, diff --git a/rust/hyperlane-core/src/traits/multisig_ism.rs b/rust/hyperlane-core/src/traits/multisig_ism.rs index 2a240591d..af2a03304 100644 --- a/rust/hyperlane-core/src/traits/multisig_ism.rs +++ b/rust/hyperlane-core/src/traits/multisig_ism.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use async_trait::async_trait; use auto_impl::auto_impl; -use ethers::types::{H160, U256}; +use ethers::types::H160; use eyre::Result; use crate::{ @@ -23,7 +23,7 @@ pub trait MultisigIsm: HyperlaneContract + Send + Sync + Debug { ) -> Result, ChainCommunicationError>; /// Fetch the threshold for the provided domain - async fn threshold(&self, domain: u32) -> Result; + async fn threshold(&self, domain: u32) -> Result; /// Fetch the validators for the provided domain async fn validators(&self, domain: u32) -> Result, ChainCommunicationError>; diff --git a/rust/hyperlane-core/src/types/checkpoint.rs b/rust/hyperlane-core/src/types/checkpoint.rs index ddbfbdee5..0907916f5 100644 --- a/rust/hyperlane-core/src/types/checkpoint.rs +++ b/rust/hyperlane-core/src/types/checkpoint.rs @@ -73,14 +73,12 @@ impl Decode for Checkpoint { impl Checkpoint { fn signing_hash(&self) -> H256 { - let buffer = [0u8; 28]; // sign: - // domain_hash(mailbox_address, mailbox_domain) || root || index (as u256) + // domain_hash(mailbox_address, mailbox_domain) || root || index (as u32) H256::from_slice( Keccak256::new() .chain(domain_hash(self.mailbox_address, self.mailbox_domain)) .chain(self.root) - .chain(buffer) .chain(self.index.to_be_bytes()) .finalize() .as_slice(), diff --git a/solidity/contracts/isms/MultisigIsm.sol b/solidity/contracts/isms/MultisigIsm.sol index a5411b595..14763024e 100644 --- a/solidity/contracts/isms/MultisigIsm.sol +++ b/solidity/contracts/isms/MultisigIsm.sol @@ -28,7 +28,7 @@ contract MultisigIsm is IMultisigIsm, Ownable { // ============ Mutable Storage ============ /// @notice The validator threshold for each remote domain. - mapping(uint32 => uint256) public threshold; + mapping(uint32 => uint8) public threshold; /// @notice The validator set for each remote domain. mapping(uint32 => EnumerableSet.AddressSet) private validatorSet; @@ -137,10 +137,7 @@ contract MultisigIsm is IMultisigIsm, Ownable { * @param _domain The remote domain of the validator set. * @param _threshold The new quorum threshold. */ - function setThreshold(uint32 _domain, uint256 _threshold) - external - onlyOwner - { + function setThreshold(uint32 _domain, uint8 _threshold) external onlyOwner { require( _threshold > 0 && _threshold <= validatorCount(_domain), "!range" @@ -217,7 +214,7 @@ contract MultisigIsm is IMultisigIsm, Ownable { */ function _updateCommitment(uint32 _domain) internal returns (bytes32) { address[] memory _validators = validators(_domain); - uint256 _threshold = threshold[_domain]; + uint8 _threshold = threshold[_domain]; bytes32 _commitment = keccak256( abi.encodePacked(_threshold, _validators) ); @@ -254,7 +251,7 @@ contract MultisigIsm is IMultisigIsm, Ownable { bytes calldata _metadata, bytes calldata _message ) internal view returns (bool) { - uint256 _threshold = _metadata.threshold(); + uint8 _threshold = _metadata.threshold(); bytes32 _digest; { uint32 _origin = _message.origin(); diff --git a/solidity/contracts/libs/MultisigIsmMetadata.sol b/solidity/contracts/libs/MultisigIsmMetadata.sol index d873d727e..2e5c36e51 100644 --- a/solidity/contracts/libs/MultisigIsmMetadata.sol +++ b/solidity/contracts/libs/MultisigIsmMetadata.sol @@ -4,20 +4,20 @@ pragma solidity >=0.8.0; /** * Format of metadata: * [ 0: 32] Merkle root - * [ 32: 64] Root index - * [ 64: 96] Origin mailbox address - * [ 96:1120] Merkle proof - * [1120:1152] Threshold - * [1152:????] Validator signatures, 65 bytes each, length == Threshold + * [ 32: 36] Root index + * [ 36: 68] Origin mailbox address + * [ 68:1092] Merkle proof + * [1092:1093] Threshold + * [1093:????] Validator signatures, 65 bytes each, length == Threshold * [????:????] Addresses of the entire validator set, left padded to bytes32 */ library MultisigIsmMetadata { uint256 private constant MERKLE_ROOT_OFFSET = 0; uint256 private constant MERKLE_INDEX_OFFSET = 32; - uint256 private constant ORIGIN_MAILBOX_OFFSET = 64; - uint256 private constant MERKLE_PROOF_OFFSET = 96; - uint256 private constant THRESHOLD_OFFSET = 1120; - uint256 private constant SIGNATURES_OFFSET = 1152; + uint256 private constant ORIGIN_MAILBOX_OFFSET = 36; + uint256 private constant MERKLE_PROOF_OFFSET = 68; + uint256 private constant THRESHOLD_OFFSET = 1092; + uint256 private constant SIGNATURES_OFFSET = 1093; uint256 private constant SIGNATURE_LENGTH = 65; /** @@ -34,10 +34,10 @@ library MultisigIsmMetadata { * @param _metadata ABI encoded Multisig ISM metadata. * @return Index of the signed checkpoint */ - function index(bytes calldata _metadata) internal pure returns (uint256) { + function index(bytes calldata _metadata) internal pure returns (uint32) { return - uint256( - bytes32(_metadata[MERKLE_INDEX_OFFSET:ORIGIN_MAILBOX_OFFSET]) + uint32( + bytes4(_metadata[MERKLE_INDEX_OFFSET:ORIGIN_MAILBOX_OFFSET]) ); } @@ -79,12 +79,8 @@ library MultisigIsmMetadata { * @param _metadata ABI encoded Multisig ISM metadata. * @return The number of required signatures. */ - function threshold(bytes calldata _metadata) - internal - pure - returns (uint256) - { - return uint256(bytes32(_metadata[THRESHOLD_OFFSET:SIGNATURES_OFFSET])); + function threshold(bytes calldata _metadata) internal pure returns (uint8) { + return uint8(bytes1(_metadata[THRESHOLD_OFFSET:SIGNATURES_OFFSET])); } /** @@ -182,6 +178,8 @@ library MultisigIsmMetadata { pure returns (uint256) { - return SIGNATURES_OFFSET + (threshold(_metadata) * SIGNATURE_LENGTH); + return + SIGNATURES_OFFSET + + (uint256(threshold(_metadata)) * SIGNATURE_LENGTH); } } diff --git a/solidity/interfaces/IMultisigIsm.sol b/solidity/interfaces/IMultisigIsm.sol index b60f75bae..04fe8a585 100644 --- a/solidity/interfaces/IMultisigIsm.sol +++ b/solidity/interfaces/IMultisigIsm.sol @@ -9,7 +9,7 @@ interface IMultisigIsm is IInterchainSecurityModule { view returns (bool); - function threshold(uint32 _domain) external view returns (uint256); + function threshold(uint32 _domain) external view returns (uint8); function validators(uint32 _domain) external diff --git a/solidity/test/lib/mailboxes.ts b/solidity/test/lib/mailboxes.ts index 6d7e44f9a..f72133fae 100644 --- a/solidity/test/lib/mailboxes.ts +++ b/solidity/test/lib/mailboxes.ts @@ -115,7 +115,7 @@ export function getCommitment( validators: types.Address[], ): string { const packed = ethers.utils.solidityPack( - ['uint256', 'address[]'], + ['uint8', 'address[]'], [threshold, validators], ); return ethers.utils.solidityKeccak256(['bytes'], [packed]); diff --git a/typescript/sdk/src/deploy/core/HyperlaneCoreChecker.ts b/typescript/sdk/src/deploy/core/HyperlaneCoreChecker.ts index fef96c2db..1b90a0e6a 100644 --- a/typescript/sdk/src/deploy/core/HyperlaneCoreChecker.ts +++ b/typescript/sdk/src/deploy/core/HyperlaneCoreChecker.ts @@ -125,9 +125,7 @@ export class HyperlaneCoreChecker< const expectedThreshold = multisigIsmConfig.threshold; utils.assert(expectedThreshold !== undefined); - const actualThreshold = ( - await multisigIsm.threshold(remoteDomain) - ).toNumber(); + const actualThreshold = await multisigIsm.threshold(remoteDomain); if (expectedThreshold !== actualThreshold) { const violation: ThresholdViolation = { diff --git a/typescript/sdk/src/deploy/core/HyperlaneCoreDeployer.ts b/typescript/sdk/src/deploy/core/HyperlaneCoreDeployer.ts index 94ae15a0f..bc2a72cce 100644 --- a/typescript/sdk/src/deploy/core/HyperlaneCoreDeployer.ts +++ b/typescript/sdk/src/deploy/core/HyperlaneCoreDeployer.ts @@ -87,7 +87,7 @@ export class HyperlaneCoreDeployer< }), ); const threshold = await multisigIsm.threshold(domain); - if (!threshold.eq(multisigIsmConfig.threshold)) { + if (threshold !== multisigIsmConfig.threshold) { this.logger( `Setting ${remote} threshold to ${multisigIsmConfig.threshold} on ${chain}`, ); diff --git a/typescript/sdk/src/gas/calculator.test.ts b/typescript/sdk/src/gas/calculator.test.ts index 1a349e4b9..9887d51ae 100644 --- a/typescript/sdk/src/gas/calculator.test.ts +++ b/typescript/sdk/src/gas/calculator.test.ts @@ -254,7 +254,7 @@ describe('InterchainGasCalculator', () => { if (!thresholdStub) { thresholdStub = sinon .stub(multisigIsm, 'threshold') - .callsFake(() => Promise.resolve(BigNumber.from(threshold))); + .callsFake(() => Promise.resolve(threshold)); contracts.multisigIsm = multisigIsm; } diff --git a/typescript/sdk/src/gas/calculator.ts b/typescript/sdk/src/gas/calculator.ts index e63183076..171ad0094 100644 --- a/typescript/sdk/src/gas/calculator.ts +++ b/typescript/sdk/src/gas/calculator.ts @@ -315,7 +315,9 @@ export class InterchainGasCalculator { // TODO: Check the recipient module const module = this.core.getContracts(destination).multisigIsm; const threshold = await module.threshold(ChainNameToDomainId[origin]); - return threshold.mul(GAS_OVERHEAD_PER_SIGNATURE).add(GAS_OVERHEAD_BASE); + return BigNumber.from(threshold) + .mul(GAS_OVERHEAD_PER_SIGNATURE) + .add(GAS_OVERHEAD_BASE); } /** diff --git a/typescript/utils/src/utils.ts b/typescript/utils/src/utils.ts index 50a389a85..2aba2ffff 100644 --- a/typescript/utils/src/utils.ts +++ b/typescript/utils/src/utils.ts @@ -50,10 +50,10 @@ export const parseMultisigIsmMetadata = ( ): ParsedMultisigIsmMetadata => { const MERKLE_ROOT_OFFSET = 0; const MERKLE_INDEX_OFFSET = 32; - const ORIGIN_MAILBOX_OFFSET = 64; - const MERKLE_PROOF_OFFSET = 96; - const THRESHOLD_OFFSET = 1120; - const SIGNATURES_OFFSET = 1152; + const ORIGIN_MAILBOX_OFFSET = 36; + const MERKLE_PROOF_OFFSET = 68; + const THRESHOLD_OFFSET = 1092; + const SIGNATURES_OFFSET = 1093; const SIGNATURE_LENGTH = 65; const buf = Buffer.from(utils.arrayify(metadata)); @@ -99,10 +99,10 @@ export const formatMultisigIsmMetadata = ( return ethers.utils.solidityPack( [ 'bytes32', - 'uint256', + 'uint32', 'bytes32', 'bytes32[32]', - 'uint256', + 'uint8', 'bytes', 'address[]', ], diff --git a/typescript/utils/src/validator.ts b/typescript/utils/src/validator.ts index 80cbf9890..ab9576615 100644 --- a/typescript/utils/src/validator.ts +++ b/typescript/utils/src/validator.ts @@ -23,7 +23,7 @@ export class BaseValidator { message(root: HexString, index: number) { return ethers.utils.solidityPack( - ['bytes32', 'bytes32', 'uint256'], + ['bytes32', 'bytes32', 'uint32'], [this.domainHash(), root, index], ); }