Smaller MultisigMetadata (#1394)

pull/1403/head
Asa Oines 2 years ago committed by GitHub
parent de4ed8bf2b
commit 4fc474ed55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 39
      rust/chains/hyperlane-ethereum/abis/Mailbox.abi.json
  2. 8
      rust/chains/hyperlane-ethereum/abis/MultisigIsm.abi.json
  3. 51
      rust/chains/hyperlane-ethereum/src/multisig_ism.rs
  4. 4
      rust/config/test/test_config.json
  5. 4
      rust/hyperlane-core/src/traits/multisig_ism.rs
  6. 4
      rust/hyperlane-core/src/types/checkpoint.rs
  7. 11
      solidity/contracts/isms/MultisigIsm.sol
  8. 36
      solidity/contracts/libs/MultisigIsmMetadata.sol
  9. 2
      solidity/interfaces/IMultisigIsm.sol
  10. 2
      solidity/test/lib/mailboxes.ts
  11. 4
      typescript/sdk/src/deploy/core/HyperlaneCoreChecker.ts
  12. 2
      typescript/sdk/src/deploy/core/HyperlaneCoreDeployer.ts
  13. 2
      typescript/sdk/src/gas/calculator.test.ts
  14. 4
      typescript/sdk/src/gas/calculator.ts
  15. 12
      typescript/utils/src/utils.ts
  16. 2
      typescript/utils/src/validator.ts

@ -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"
}
]

@ -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",

@ -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<M>,
threshold_cache: RwLock<ExpiringCache<u32, U256>>,
threshold_cache: RwLock<ExpiringCache<u32, u8>>,
validators_cache: RwLock<ExpiringCache<u32, Vec<H160>>>,
}
@ -170,23 +170,16 @@ where
checkpoint: &MultisigSignedCheckpoint,
proof: Proof,
) -> Result<Vec<u8>, ChainCommunicationError> {
let threshold = self.threshold(checkpoint.checkpoint.mailbox_domain).await?;
let validator_addresses: Vec<H160> = self
.validators(checkpoint.checkpoint.mailbox_domain)
.await?;
let validators: Vec<H256> = validator_addresses.iter().map(|&x| H256::from(x)).collect();
let validator_tokens: Vec<Token> = 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<Token> = 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<H160> = 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<Vec<u8>> =
order_signatures(&validator_addresses, &checkpoint.signatures);
let signature_bytes = signature_vecs.concat();
let metadata = [prefix, signature_bytes, suffix].concat();
let validators: Vec<H256> = validator_addresses.iter().map(|&x| H256::from(x)).collect();
let validator_tokens: Vec<Token> = 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<U256, ChainCommunicationError> {
async fn threshold(&self, domain: u32) -> Result<u8, ChainCommunicationError> {
let entry = self.threshold_cache.read().await.get(domain).cloned();
if let Some(threshold) = entry {
Ok(threshold)

@ -34,7 +34,7 @@
"url": ""
},
"index": {
"from": "17"
"from": "16"
}
},
"test3": {
@ -52,7 +52,7 @@
"url": ""
},
"index": {
"from": "29"
"from": "28"
}
}
},

@ -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<Vec<u8>, ChainCommunicationError>;
/// Fetch the threshold for the provided domain
async fn threshold(&self, domain: u32) -> Result<U256, ChainCommunicationError>;
async fn threshold(&self, domain: u32) -> Result<u8, ChainCommunicationError>;
/// Fetch the validators for the provided domain
async fn validators(&self, domain: u32) -> Result<Vec<H160>, ChainCommunicationError>;

@ -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(),

@ -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();

@ -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);
}
}

@ -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

@ -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]);

@ -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 = {

@ -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}`,
);

@ -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;
}

@ -315,7 +315,9 @@ export class InterchainGasCalculator<Chain extends ChainName> {
// 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);
}
/**

@ -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[]',
],

@ -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],
);
}

Loading…
Cancel
Save