Skip to content

Commit

Permalink
Sealevel v3 contracts (#3048)
Browse files Browse the repository at this point in the history
### Description

MVP contracts for sealevel v3 - note this doesn't include hooks so IGP
payments still have to be made separately. However contracts will now be
compatible with the metadata format used by the other V3 deployments.

### Drive-by changes

The unit tests in
`rust/sealevel/programs/ism/multisig-ism-message-id/src/metadata.rs` are
refactored to reuse struct creation logic.

### Related issues

- Fixes #3044
- Fixes #3045

### Backward compatibility

No, since this upgrades sealevel v2 to v3.

### Testing

Unit tests
  • Loading branch information
daniel-savu authored Dec 15, 2023
1 parent e65c2a3 commit 42ea929
Show file tree
Hide file tree
Showing 11 changed files with 97 additions and 63 deletions.
1 change: 1 addition & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 11 additions & 11 deletions rust/sealevel/libraries/multisig-ism/src/test_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const DESTINATION_DOMAIN: u32 = 4321u32;

pub fn get_multisig_ism_test_data() -> MultisigIsmTestData {
let message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 69,
origin: ORIGIN_DOMAIN,
sender: H256::from_str(
Expand Down Expand Up @@ -49,31 +49,31 @@ pub fn get_multisig_ism_test_data() -> MultisigIsmTestData {
};

// checkpoint.signing_hash() is equal to:
// 0x4fc33ff33d5e9305a2d87f7824d1b943ba219cff4c153ae8fd39b0d8620fc332
// 0x5c2b4cbafdd7319d9a3dea4aa078748ae53368e1521494a80cf4259b9c6dba6a

// Validator 0:
// Address: 0xE3DCDBbc248cE191bDc271f3FCcd0d95911BFC5D
// Private Key: 0x788aa7213bd92ff92017d767fde0d75601425818c8e4b21e87314c2a4dcd6091
let validator_0 = H160::from_str("0xE3DCDBbc248cE191bDc271f3FCcd0d95911BFC5D").unwrap();
// > await (new ethers.Wallet('0x788aa7213bd92ff92017d767fde0d75601425818c8e4b21e87314c2a4dcd6091')).signMessage(ethers.utils.arrayify('0x4fc33ff33d5e9305a2d87f7824d1b943ba219cff4c153ae8fd39b0d8620fc332'))
// '0x3a06cc01fef07025ee5ae9e29ae783338fe11f5c21af383fb8cc5878a2ea3616125c230ec07b059eaebb842af0a51040ad3214f9050cccef36b5c21c9c9cc4ba1b'
let signature_0 = hex::decode("3a06cc01fef07025ee5ae9e29ae783338fe11f5c21af383fb8cc5878a2ea3616125c230ec07b059eaebb842af0a51040ad3214f9050cccef36b5c21c9c9cc4ba1b").unwrap();
// > await (new ethers.Wallet('0x788aa7213bd92ff92017d767fde0d75601425818c8e4b21e87314c2a4dcd6091')).signMessage(ethers.utils.arrayify('0x5c2b4cbafdd7319d9a3dea4aa078748ae53368e1521494a80cf4259b9c6dba6a'))
// '0xb8875fb75adf471e43a943b78eb422ffe86a4291fa6324f9f875241605ca831d2b4225358936deee7501cf305aafc1677e3dc9bcfea4caec54f4cde49d416bd91b'
let signature_0 = hex::decode("b8875fb75adf471e43a943b78eb422ffe86a4291fa6324f9f875241605ca831d2b4225358936deee7501cf305aafc1677e3dc9bcfea4caec54f4cde49d416bd91b").unwrap();

// Validator 1:
// Address: 0xb25206874C24733F05CC0dD11924724A8E7175bd
// Private Key: 0x4a599de3915f404d84a2ebe522bfe7032ebb1ca76a65b55d6eb212b129043a0e
let validator_1 = H160::from_str("0xb25206874C24733F05CC0dD11924724A8E7175bd").unwrap();
// > await (new ethers.Wallet('0x4a599de3915f404d84a2ebe522bfe7032ebb1ca76a65b55d6eb212b129043a0e')).signMessage(ethers.utils.arrayify('0x4fc33ff33d5e9305a2d87f7824d1b943ba219cff4c153ae8fd39b0d8620fc332'))
// '0xfd34aac152ec85a79211c990f308c7e719145e2e67e48f2d10db4347d3a9102131254eccbcd0fe389afad96b88d368192b33649336893dfe1bbad43901d1bef71b'
let signature_1 = hex::decode("fd34aac152ec85a79211c990f308c7e719145e2e67e48f2d10db4347d3a9102131254eccbcd0fe389afad96b88d368192b33649336893dfe1bbad43901d1bef71b").unwrap();
// > await (new ethers.Wallet('0x4a599de3915f404d84a2ebe522bfe7032ebb1ca76a65b55d6eb212b129043a0e')).signMessage(ethers.utils.arrayify('0x5c2b4cbafdd7319d9a3dea4aa078748ae53368e1521494a80cf4259b9c6dba6a'))
// '0xfa8d61da2e0ac6f32c8432e75770d90483613efe96b442fbca9ca8d200447bf979f46529c7341879333a9c24a7d3fba08b53d13447618b71cf2ee4734e82f96e1c'
let signature_1 = hex::decode("fa8d61da2e0ac6f32c8432e75770d90483613efe96b442fbca9ca8d200447bf979f46529c7341879333a9c24a7d3fba08b53d13447618b71cf2ee4734e82f96e1c").unwrap();

// Validator 2:
// Address: 0x28b8d0E2bBfeDe9071F8Ff3DaC9CcE3d3176DBd3
// Private Key: 0x2cc76d56db9924ddc3388164454dfea9edd2d5f5da81102fd3594fc7c5281515
let validator_2 = H160::from_str("0x28b8d0E2bBfeDe9071F8Ff3DaC9CcE3d3176DBd3").unwrap();
// > await (new ethers.Wallet('0x2cc76d56db9924ddc3388164454dfea9edd2d5f5da81102fd3594fc7c5281515')).signMessage(ethers.utils.arrayify('0x4fc33ff33d5e9305a2d87f7824d1b943ba219cff4c153ae8fd39b0d8620fc332'))
// '0x85992e471002c40730d2b91831ba40cd8ffcebf4905646c25b7b6abb7575f25d19395045466e833b7700e233bfa5836f0a459da05bf817efd6cb4f55bcaec4b51c'
let signature_2 = hex::decode("85992e471002c40730d2b91831ba40cd8ffcebf4905646c25b7b6abb7575f25d19395045466e833b7700e233bfa5836f0a459da05bf817efd6cb4f55bcaec4b51c").unwrap();
// > await (new ethers.Wallet('0x2cc76d56db9924ddc3388164454dfea9edd2d5f5da81102fd3594fc7c5281515')).signMessage(ethers.utils.arrayify('0x5c2b4cbafdd7319d9a3dea4aa078748ae53368e1521494a80cf4259b9c6dba6a'))
// '0x6c60c3744f6bf779017b8ab9aa8eed60bf53c317cf4d74d765015cc0a7dcad783dee39334bfc7e4baab944355914e7431e4286df8c0557a0c1f6ba867677da421b'
let signature_2 = hex::decode("6c60c3744f6bf779017b8ab9aa8eed60bf53c317cf4d74d765015cc0a7dcad783dee39334bfc7e4baab944355914e7431e4286df8c0557a0c1f6ba867677da421b").unwrap();

MultisigIsmTestData {
message,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,7 @@ async fn test_transfer_remote(spl_token_program_id: Pubkey) {
.unwrap();

let message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 0,
origin: LOCAL_DOMAIN,
sender: program_id.to_bytes().into(),
Expand Down Expand Up @@ -869,7 +869,7 @@ async fn transfer_from_remote(
);

let message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 0,
origin: origin_override.unwrap_or(REMOTE_DOMAIN),
// Default to the remote router as the sender
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ async fn test_transfer_remote() {
.unwrap();

let message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 0,
origin: LOCAL_DOMAIN,
sender: program_id.to_bytes().into(),
Expand Down Expand Up @@ -616,7 +616,7 @@ async fn transfer_from_remote(
let recipient: H256 = recipient_pubkey.to_bytes().into();

let message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 0,
origin: origin_override.unwrap_or(REMOTE_DOMAIN),
// Default to the remote router as the sender
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ async fn transfer_from_remote(
);

let message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 0,
origin: origin_override.unwrap_or(REMOTE_DOMAIN),
// Default to the remote router as the sender
Expand Down Expand Up @@ -783,7 +783,7 @@ async fn test_transfer_remote() {
.unwrap();

let message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 0,
origin: LOCAL_DOMAIN,
sender: program_id.to_bytes().into(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@ serializable-account-meta = { path = "../../../libraries/serializable-account-me
[dev-dependencies]
hyperlane-sealevel-multisig-ism-message-id = { path = "../multisig-ism-message-id" }
hyperlane-test-utils = { path = "../../../libraries/test-utils" }
multisig-ism = { path = "../../../libraries/multisig-ism", features = ["test-data"] }
solana-program-test.workspace = true
solana-sdk.workspace = true
hex.workspace = true
multisig-ism = { path = "../../../libraries/multisig-ism", features = ["test-data"] }
# Can't have as a workspace dep, because this is already in the dep tree twice: once as
# an older solana one, once as a newer one used more generally.
rand = "0.8.5"

[lib]
crate-type = ["cdylib", "lib"]
Expand Down
89 changes: 57 additions & 32 deletions rust/sealevel/programs/ism/multisig-ism-message-id/src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ use crate::error::Error;
pub struct MultisigIsmMessageIdMetadata {
pub origin_merkle_tree_hook: H256,
pub merkle_root: H256,
pub merkle_index: u32,
pub validator_signatures: Vec<EcdsaSignature>,
}

const ORIGIN_MAILBOX_OFFSET: usize = 0;
const MERKLE_ROOT_OFFSET: usize = 32;
const SIGNATURES_OFFSET: usize = 64;
const MERKLE_INDEX_OFFSET: usize = 64;
const SIGNATURES_OFFSET: usize = 68;
const SIGNATURE_LENGTH: usize = 65;

/// Format of metadata:
Expand All @@ -32,7 +34,12 @@ impl TryFrom<Vec<u8>> for MultisigIsmMessageIdMetadata {
}

let origin_mailbox = H256::from_slice(&bytes[ORIGIN_MAILBOX_OFFSET..MERKLE_ROOT_OFFSET]);
let merkle_root = H256::from_slice(&bytes[MERKLE_ROOT_OFFSET..SIGNATURES_OFFSET]);
let merkle_root = H256::from_slice(&bytes[MERKLE_ROOT_OFFSET..MERKLE_INDEX_OFFSET]);
// This cannot panic since SIGNATURES_OFFSET - MERKLE_INDEX_OFFSET is 4.
let merkle_index_bytes: [u8; 4] = bytes[MERKLE_INDEX_OFFSET..SIGNATURES_OFFSET]
.try_into()
.map_err(|_| Error::InvalidMetadata)?;
let merkle_index = u32::from_be_bytes(merkle_index_bytes);

let signature_bytes_len = bytes_len - SIGNATURES_OFFSET;
// Require the signature bytes to be a multiple of the signature length.
Expand All @@ -55,6 +62,7 @@ impl TryFrom<Vec<u8>> for MultisigIsmMessageIdMetadata {
Ok(Self {
origin_merkle_tree_hook: origin_mailbox,
merkle_root,
merkle_index,
validator_signatures,
})
}
Expand All @@ -68,6 +76,7 @@ impl Encode for MultisigIsmMessageIdMetadata {
let mut bytes_written = 0;
bytes_written += writer.write(self.origin_merkle_tree_hook.as_ref())?;
bytes_written += writer.write(self.merkle_root.as_ref())?;
bytes_written += writer.write(&self.merkle_index.to_be_bytes())?;
for signature in &self.validator_signatures {
bytes_written += writer.write(&signature.as_fixed_bytes()[..])?;
}
Expand All @@ -78,11 +87,21 @@ impl Encode for MultisigIsmMessageIdMetadata {
#[cfg(test)]
mod test {
use super::*;
use rand::Rng;

// Provide a default test implementation
fn dummy_metadata_with_sigs(sigs: Vec<EcdsaSignature>) -> MultisigIsmMessageIdMetadata {
let mut rng = rand::thread_rng();
MultisigIsmMessageIdMetadata {
origin_merkle_tree_hook: H256::random(),
merkle_root: H256::random(),
merkle_index: rng.gen(),
validator_signatures: sigs,
}
}

#[test]
fn test_decode_correctly_formatted_metadata() {
let origin_mailbox = H256::random();
let merkle_root = H256::random();
let validator_signatures = vec![
EcdsaSignature {
serialized_rs: [11u8; 64],
Expand All @@ -97,43 +116,49 @@ mod test {
recovery_id: 0,
},
];
let mut metadata_bytes = origin_mailbox.as_bytes().to_vec();
metadata_bytes.extend_from_slice(merkle_root.as_bytes());
for signature in &validator_signatures {
metadata_bytes.extend_from_slice(&signature.as_fixed_bytes()[..]);
}

let metadata = MultisigIsmMessageIdMetadata::try_from(metadata_bytes).unwrap();
assert_eq!(metadata.origin_merkle_tree_hook, origin_mailbox);
assert_eq!(metadata.merkle_root, merkle_root);
assert_eq!(metadata.validator_signatures, validator_signatures);
let test_meta = dummy_metadata_with_sigs(validator_signatures);
let encoded_meta = test_meta.to_vec();
let metadata = MultisigIsmMessageIdMetadata::try_from(encoded_meta.clone()).unwrap();
assert_eq!(
metadata.origin_merkle_tree_hook,
test_meta.origin_merkle_tree_hook
);
assert_eq!(metadata.merkle_root, test_meta.merkle_root);
assert_eq!(metadata.merkle_index, test_meta.merkle_index);
assert_eq!(
metadata.validator_signatures,
test_meta.validator_signatures
);
}

#[test]
fn test_decode_no_signatures_is_err() {
let origin_mailbox = H256::random();
let merkle_root = H256::random();
let metadata_bytes = origin_mailbox
.as_bytes()
.iter()
.chain(merkle_root.as_bytes().iter())
.cloned()
.collect::<Vec<u8>>();

let result = MultisigIsmMessageIdMetadata::try_from(metadata_bytes);
let test_meta = dummy_metadata_with_sigs(vec![]);
let encoded_meta = test_meta.to_vec();
let result = MultisigIsmMessageIdMetadata::try_from(encoded_meta);
assert!(result.unwrap_err() == Error::InvalidMetadata);
}

#[test]
fn test_decode_incorrect_signature_length_is_err() {
let origin_mailbox = H256::random();
let merkle_root = H256::random();
let mut metadata_bytes = origin_mailbox.as_bytes().to_vec();
metadata_bytes.extend_from_slice(merkle_root.as_bytes());
// 64 byte signature instead of 65.
metadata_bytes.extend_from_slice(&[1u8; 64]);

let result = MultisigIsmMessageIdMetadata::try_from(metadata_bytes);
let sigs = vec![EcdsaSignature {
serialized_rs: [1u8; 64],
recovery_id: 0,
}];
let test_meta = dummy_metadata_with_sigs(sigs);
let encoded_meta = test_meta.to_vec();
// remove the last byte from the encoded signature
let faulty_encoded_meta = encoded_meta[..encoded_meta.len() - 1].to_vec();
let result = MultisigIsmMessageIdMetadata::try_from(faulty_encoded_meta);
assert!(result.unwrap_err() == Error::InvalidMetadata);
MultisigIsmMessageIdMetadata::try_from(encoded_meta).expect("Decoding should succeed");
}

#[test]
fn test_decode_real_meta() {
// multisig ism message id metadata from this tx:
// https://arbiscan.io//tx/0xe558f04ad446b1d9ec4d4a1284661869b73daff38ec9fb7e809be652732fff30#txninfo
let bytes = hex::decode("000000000000000000000000149db7afd694722747035d5aec7007ccb6f8f112fb91807ccda2db543bfbd013242643553bc1238f891ae9d0abb3b8b46c5a89990000017addc429c97ca8bcd6ad86ef4461379374b0d545308a1f47db246a6c028f74d7af521dd9355afd2f2a02565a24f22ac7b7e388cbd1f2a931acc97ce689be5456851b4d22f1aece05d293e574e38edcda9f2db64f1dc5b69a89a6a5989e7aaa4f443c137e593bb794eb211de719ed0f466a0778c4d204cc275f54c0936eee918ae1651c").unwrap();
MultisigIsmMessageIdMetadata::try_from(bytes).expect("Decoding should succeed");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ pub mod test {
metadata: MultisigIsmMessageIdMetadata {
origin_merkle_tree_hook: checkpoint.merkle_tree_hook_address,
merkle_root: checkpoint.root,
merkle_index: checkpoint.index,
validator_signatures: vec![
EcdsaSignature::from_bytes(&signatures[0]).unwrap(),
EcdsaSignature::from_bytes(&signatures[1]).unwrap(),
Expand All @@ -626,6 +627,7 @@ pub mod test {
metadata: MultisigIsmMessageIdMetadata {
origin_merkle_tree_hook: checkpoint.merkle_tree_hook_address,
merkle_root: checkpoint.root,
merkle_index: checkpoint.index,
validator_signatures: vec![
EcdsaSignature::from_bytes(&signatures[1]).unwrap(),
EcdsaSignature::from_bytes(&signatures[0]).unwrap(),
Expand Down Expand Up @@ -654,6 +656,7 @@ pub mod test {
metadata: MultisigIsmMessageIdMetadata {
origin_merkle_tree_hook: checkpoint.merkle_tree_hook_address,
merkle_root: checkpoint.root,
merkle_index: checkpoint.index,
validator_signatures: vec![
EcdsaSignature::from_bytes(&signatures[0]).unwrap(),
EcdsaSignature::from_bytes(&signatures[2]).unwrap(),
Expand All @@ -678,6 +681,7 @@ pub mod test {
metadata: MultisigIsmMessageIdMetadata {
origin_merkle_tree_hook: checkpoint.merkle_tree_hook_address,
merkle_root: checkpoint.root,
merkle_index: checkpoint.index,
validator_signatures: vec![
EcdsaSignature::from_bytes(&signatures[0]).unwrap(),
EcdsaSignature::from_bytes(&signatures[1]).unwrap(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ async fn test_set_validators_and_threshold_creates_pda_account() {
// to fetch the account metas required for the instruction.

let test_message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 0,
origin: domain,
sender: H256::random(),
Expand Down Expand Up @@ -421,6 +421,7 @@ async fn test_ism_verify() {
metadata: MultisigIsmMessageIdMetadata {
origin_merkle_tree_hook: checkpoint.merkle_tree_hook_address,
merkle_root: checkpoint.root,
merkle_index: checkpoint.index,
validator_signatures: vec![
EcdsaSignature::from_bytes(&signatures[0]).unwrap(),
EcdsaSignature::from_bytes(&signatures[1]).unwrap(),
Expand Down
Loading

0 comments on commit 42ea929

Please sign in to comment.