Sealevel v3 contracts (#3048)

### 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 https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3044
- Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3045

### Backward compatibility

No, since this upgrades sealevel v2 to v3.

### Testing

Unit tests
pull/3063/head
Daniel Savu 11 months ago committed by GitHub
parent e65c2a321c
commit 42ea929785
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      rust/Cargo.lock
  2. 22
      rust/sealevel/libraries/multisig-ism/src/test_data.rs
  3. 4
      rust/sealevel/programs/hyperlane-sealevel-token-collateral/tests/functional.rs
  4. 4
      rust/sealevel/programs/hyperlane-sealevel-token-native/tests/functional.rs
  5. 4
      rust/sealevel/programs/hyperlane-sealevel-token/tests/functional.rs
  6. 5
      rust/sealevel/programs/ism/multisig-ism-message-id/Cargo.toml
  7. 89
      rust/sealevel/programs/ism/multisig-ism-message-id/src/metadata.rs
  8. 4
      rust/sealevel/programs/ism/multisig-ism-message-id/src/processor.rs
  9. 3
      rust/sealevel/programs/ism/multisig-ism-message-id/tests/functional.rs
  10. 22
      rust/sealevel/programs/mailbox-test/src/functional.rs
  11. 2
      rust/sealevel/programs/mailbox/src/instruction.rs

1
rust/Cargo.lock generated

@ -4400,6 +4400,7 @@ dependencies = [
"multisig-ism",
"num-derive 0.4.1",
"num-traits",
"rand 0.8.5",
"serializable-account-meta",
"solana-program",
"solana-program-test",

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

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

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

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

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

@ -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:
@ -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.
@ -55,6 +62,7 @@ impl TryFrom<Vec<u8>> for MultisigIsmMessageIdMetadata {
Ok(Self {
origin_merkle_tree_hook: origin_mailbox,
merkle_root,
merkle_index,
validator_signatures,
})
}
@ -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()[..])?;
}
@ -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],
@ -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");
}
}

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

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

@ -183,7 +183,7 @@ async fn test_dispatch_from_eoa() {
.unwrap();
let expected_message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 0,
origin: LOCAL_DOMAIN,
sender: payer.pubkey().to_bytes().into(),
@ -238,7 +238,7 @@ async fn test_dispatch_from_eoa() {
.unwrap();
let expected_message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 1,
origin: LOCAL_DOMAIN,
sender: payer.pubkey().to_bytes().into(),
@ -300,7 +300,7 @@ async fn test_dispatch_from_program() {
.unwrap();
let expected_message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 0,
origin: LOCAL_DOMAIN,
// The sender should be the program ID because its dispatch authority signed
@ -389,7 +389,7 @@ async fn test_dispatch_returns_message_id() {
message_body: message_body.clone(),
};
let expected_message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 0,
origin: LOCAL_DOMAIN,
sender: payer.pubkey().to_bytes().into(),
@ -574,7 +574,7 @@ async fn test_process_successful_verify_and_handle() {
let recipient_id = hyperlane_sealevel_test_send_receiver::id();
let message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 0,
origin: REMOTE_DOMAIN,
sender: payer.pubkey().to_bytes().into(),
@ -605,7 +605,7 @@ async fn test_process_successful_verify_and_handle() {
// Send another to illustrate that the sequence is incremented
let message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 0,
origin: REMOTE_DOMAIN,
sender: payer.pubkey().to_bytes().into(),
@ -647,7 +647,7 @@ async fn test_process_errors_if_message_already_processed() {
let recipient_id = hyperlane_sealevel_test_send_receiver::id();
let message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 0,
origin: REMOTE_DOMAIN,
sender: payer.pubkey().to_bytes().into(),
@ -697,7 +697,7 @@ async fn test_process_errors_if_ism_verify_fails() {
test_ism.set_accept(false).await.unwrap();
let message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 0,
origin: REMOTE_DOMAIN,
sender: payer.pubkey().to_bytes().into(),
@ -743,7 +743,7 @@ async fn test_process_errors_if_recipient_handle_fails() {
.unwrap();
let message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 0,
origin: REMOTE_DOMAIN,
sender: payer.pubkey().to_bytes().into(),
@ -784,7 +784,7 @@ async fn test_process_errors_if_incorrect_destination_domain() {
let recipient_id = hyperlane_sealevel_test_send_receiver::id();
let message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 0,
origin: REMOTE_DOMAIN,
sender: payer.pubkey().to_bytes().into(),
@ -905,7 +905,7 @@ async fn test_process_errors_if_reentrant() {
let recipient_id = hyperlane_sealevel_test_send_receiver::id();
let message = HyperlaneMessage {
version: 0,
version: 3,
nonce: 0,
origin: REMOTE_DOMAIN,
sender: payer.pubkey().to_bytes().into(),

@ -11,7 +11,7 @@ use solana_program::{
use crate::{mailbox_inbox_pda_seeds, mailbox_outbox_pda_seeds};
/// The current message version.
pub const VERSION: u8 = 0;
pub const VERSION: u8 = 3;
/// Maximum bytes per message = 2 KiB (somewhat arbitrarily set to begin).
pub const MAX_MESSAGE_BODY_BYTES: usize = 2 * 2_usize.pow(10);

Loading…
Cancel
Save