From ca3b4861a1a26b63cad5f12daa86a71a29107cee Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Fri, 24 Jan 2025 16:09:35 -0500 Subject: [PATCH] feat(axelar-gateway): add more queries (#207) --- contracts/stellar-axelar-gateway/src/auth.rs | 57 +++++++++---------- .../stellar-axelar-gateway/src/contract.rs | 12 ++++ .../stellar-axelar-gateway/src/interface.rs | 11 +++- .../stellar-axelar-gateway/src/tests/auth.rs | 48 +++++++++++++++- .../src/tests/utils/mod.rs | 4 +- .../stellar-axelar-gateway/src/testutils.rs | 12 ++-- packages/stellar-axelar-std/src/types.rs | 2 +- 7 files changed, 105 insertions(+), 41 deletions(-) diff --git a/contracts/stellar-axelar-gateway/src/auth.rs b/contracts/stellar-axelar-gateway/src/auth.rs index 76ea7aad..487a28b5 100644 --- a/contracts/stellar-axelar-gateway/src/auth.rs +++ b/contracts/stellar-axelar-gateway/src/auth.rs @@ -1,4 +1,3 @@ -use soroban_sdk::crypto::Hash; use soroban_sdk::{Bytes, BytesN, Env, Vec}; use stellar_axelar_std::ensure; use stellar_axelar_std::events::Event; @@ -17,7 +16,6 @@ pub fn initialize_auth( ) -> Result<(), ContractError> { env.storage().instance().set(&DataKey::Epoch, &0_u64); - // TODO: Do we need to manually expose these in a query, or can it be read directly off of storage in Stellar? env.storage().instance().set( &DataKey::PreviousSignerRetention, &previous_signer_retention, @@ -40,6 +38,27 @@ pub fn initialize_auth( Ok(()) } +pub fn domain_separator(env: &Env) -> BytesN<32> { + env.storage() + .instance() + .get(&DataKey::DomainSeparator) + .expect("domain_separator not found") +} + +pub fn minimum_rotation_delay(env: &Env) -> u64 { + env.storage() + .instance() + .get(&DataKey::MinimumRotationDelay) + .expect("minimum_rotation_delay not found") +} + +pub fn previous_signers_retention(env: &Env) -> u64 { + env.storage() + .instance() + .get(&DataKey::PreviousSignerRetention) + .expect("previous_signers_retention not found") +} + pub fn validate_proof( env: &Env, data_hash: &BytesN<32>, @@ -55,14 +74,8 @@ pub fn validate_proof( let is_latest_signers: bool = signers_epoch == current_epoch; - let previous_signers_retention: u64 = env - .storage() - .instance() - .get(&DataKey::PreviousSignerRetention) - .expect("previous_signers_retention not found"); - ensure!( - current_epoch - signers_epoch <= previous_signers_retention, + current_epoch - signers_epoch <= previous_signers_retention(env), ContractError::OutdatedSigners ); @@ -136,28 +149,15 @@ pub fn signers_hash_by_epoch(env: &Env, epoch: u64) -> Result, Contra .ok_or(ContractError::InvalidEpoch) } -fn message_hash_to_sign(env: &Env, signers_hash: BytesN<32>, data_hash: &BytesN<32>) -> Hash<32> { - let domain_separator: BytesN<32> = env - .storage() - .instance() - .get(&DataKey::DomainSeparator) - .unwrap(); - - let mut msg: Bytes = domain_separator.into(); +fn message_hash_to_sign(env: &Env, signers_hash: BytesN<32>, data_hash: &BytesN<32>) -> BytesN<32> { + let mut msg: Bytes = domain_separator(env).into(); msg.extend_from_array(&signers_hash.to_array()); msg.extend_from_array(&data_hash.to_array()); - // TODO: use an appropriate non tx overlapping prefix - env.crypto().keccak256(&msg) + env.crypto().keccak256(&msg).into() } fn update_rotation_timestamp(env: &Env, enforce_rotation_delay: bool) -> Result<(), ContractError> { - let minimum_rotation_delay: u64 = env - .storage() - .instance() - .get(&DataKey::MinimumRotationDelay) - .expect("minimum_rotation_delay not found"); - let last_rotation_timestamp: u64 = env .storage() .instance() @@ -168,7 +168,7 @@ fn update_rotation_timestamp(env: &Env, enforce_rotation_delay: bool) -> Result< if enforce_rotation_delay { ensure!( - current_timestamp - last_rotation_timestamp >= minimum_rotation_delay, + current_timestamp - last_rotation_timestamp >= minimum_rotation_delay(env), ContractError::InsufficientRotationDelay ); } @@ -180,7 +180,7 @@ fn update_rotation_timestamp(env: &Env, enforce_rotation_delay: bool) -> Result< Ok(()) } -fn validate_signatures(env: &Env, msg_hash: Hash<32>, proof: Proof) -> bool { +fn validate_signatures(env: &Env, msg_hash: BytesN<32>, proof: Proof) -> bool { let mut total_weight = 0u128; for ProofSigner { @@ -193,7 +193,7 @@ fn validate_signatures(env: &Env, msg_hash: Hash<32>, proof: Proof) -> bool { { if let ProofSignature::Signed(signature) = signature { env.crypto() - .ed25519_verify(&public_key, msg_hash.to_bytes().as_ref(), &signature); + .ed25519_verify(&public_key, msg_hash.as_ref(), &signature); total_weight = total_weight.checked_add(weight).unwrap(); @@ -214,7 +214,6 @@ fn validate_signers(env: &Env, weighted_signers: &WeightedSigners) -> Result<(), ContractError::EmptySigners ); - // TODO: what's the min address/hash? let mut previous_signer = BytesN::<32>::from_array(env, &[0; 32]); let mut total_weight = 0u128; diff --git a/contracts/stellar-axelar-gateway/src/contract.rs b/contracts/stellar-axelar-gateway/src/contract.rs index 6a08a9c3..56785b80 100644 --- a/contracts/stellar-axelar-gateway/src/contract.rs +++ b/contracts/stellar-axelar-gateway/src/contract.rs @@ -134,6 +134,18 @@ impl AxelarGatewayMessagingInterface for AxelarGateway { #[contractimpl] impl AxelarGatewayInterface for AxelarGateway { + fn domain_separator(env: &Env) -> BytesN<32> { + auth::domain_separator(env) + } + + fn minimum_rotation_delay(env: &Env) -> u64 { + auth::minimum_rotation_delay(env) + } + + fn previous_signers_retention(env: &Env) -> u64 { + auth::previous_signers_retention(env) + } + fn approve_messages( env: &Env, messages: Vec, diff --git a/contracts/stellar-axelar-gateway/src/interface.rs b/contracts/stellar-axelar-gateway/src/interface.rs index b3c611a9..baa6e2b1 100644 --- a/contracts/stellar-axelar-gateway/src/interface.rs +++ b/contracts/stellar-axelar-gateway/src/interface.rs @@ -9,6 +9,15 @@ use crate::AxelarGatewayMessagingInterface; pub trait AxelarGatewayInterface: AxelarGatewayMessagingInterface + UpgradableInterface + OwnableInterface + OperatableInterface { + /// Returns the domain separator. + fn domain_separator(env: &Env) -> BytesN<32>; + + /// Returns the number of epochs that previous signers are retained for after rotations. + fn previous_signers_retention(env: &Env) -> u64; + + /// Returns the minimum delay between rotations. + fn minimum_rotation_delay(env: &Env) -> u64; + /// Approves a collection of messages. fn approve_messages( env: &Env, @@ -16,7 +25,7 @@ pub trait AxelarGatewayInterface: proof: Proof, ) -> Result<(), ContractError>; - // TODO: add docstring about how bypass_rotation_delay supposed to be used. + /// Rotates the signers. fn rotate_signers( env: &Env, signers: WeightedSigners, diff --git a/contracts/stellar-axelar-gateway/src/tests/auth.rs b/contracts/stellar-axelar-gateway/src/tests/auth.rs index 29fb5318..cc4357cd 100644 --- a/contracts/stellar-axelar-gateway/src/tests/auth.rs +++ b/contracts/stellar-axelar-gateway/src/tests/auth.rs @@ -17,7 +17,7 @@ fn initialization_fails_with_empty_signer_set() { let empty_signer_set = Vec::::new(&env); let domain_separator: BytesN<32> = BytesN::random(&env); - let previous_signers_retention = randint(0, 10) as u64; + let previous_signers_retention = randint(0, 10); let minimum_rotation_delay: u64 = 0; let initial_signers = empty_signer_set; @@ -47,6 +47,50 @@ fn validate_proof_fails_with_invalid_signatures() { client.validate_proof(&random_hash, &proof); } +#[test] +fn domain_separator_succeeds_with_register() { + let (_, signers, client) = setup_env(randint(0, 10), randint(1, 10)); + + assert_eq!(client.domain_separator(), signers.domain_separator); +} + +#[test] +fn minimum_rotation_delay_succeeds_with_register() { + let env = &Env::default(); + + let owner = Address::generate(env); + let operator = Address::generate(env); + let signer_set = generate_signers_set(env, randint(1, 10), BytesN::random(env)); + let initial_signers = vec![&env, signer_set.signers.clone()]; + let minimum_rotation_delay: u64 = randint(0, u64::MAX); + + let contract_id = env.register( + AxelarGateway, + ( + owner, + operator, + &signer_set.domain_separator, + minimum_rotation_delay, + randint(0, 10), + initial_signers, + ), + ); + let client = AxelarGatewayClient::new(env, &contract_id); + + assert_eq!(client.minimum_rotation_delay(), minimum_rotation_delay); +} + +#[test] +fn previous_signers_retention_succeeds_with_register() { + let previous_signers_retention = randint(0, 10); + let (_, _, client) = setup_env(previous_signers_retention, randint(1, 10)); + + assert_eq!( + client.previous_signers_retention(), + previous_signers_retention + ); +} + #[test] fn validate_proof_fails_with_empty_signatures() { let (env, signers, client) = setup_env(randint(0, 10), randint(1, 10)); @@ -348,7 +392,7 @@ fn rotate_signers_fails_with_insufficient_rotation_delay() { operator, &signers.domain_separator, minimum_rotation_delay, - previous_signers_retention as u64, + previous_signers_retention, initial_signers, ), ); diff --git a/contracts/stellar-axelar-gateway/src/tests/utils/mod.rs b/contracts/stellar-axelar-gateway/src/tests/utils/mod.rs index 90d7674f..1a1db091 100644 --- a/contracts/stellar-axelar-gateway/src/tests/utils/mod.rs +++ b/contracts/stellar-axelar-gateway/src/tests/utils/mod.rs @@ -4,8 +4,8 @@ use crate::testutils::{setup_gateway, TestSignerSet}; use crate::AxelarGatewayClient; pub fn setup_env<'a>( - previous_signers_retention: u32, - num_signers: u32, + previous_signers_retention: u64, + num_signers: u64, ) -> (Env, TestSignerSet, AxelarGatewayClient<'a>) { let env = Env::default(); let (signers, client) = setup_gateway(&env, previous_signers_retention, num_signers); diff --git a/contracts/stellar-axelar-gateway/src/testutils.rs b/contracts/stellar-axelar-gateway/src/testutils.rs index f546138d..999e9338 100644 --- a/contracts/stellar-axelar-gateway/src/testutils.rs +++ b/contracts/stellar-axelar-gateway/src/testutils.rs @@ -22,8 +22,8 @@ pub struct TestSignerSet { pub fn setup_gateway<'a>( env: &Env, - previous_signers_retention: u32, - num_signers: u32, + previous_signers_retention: u64, + num_signers: u64, ) -> (TestSignerSet, AxelarGatewayClient<'a>) { let owner = Address::generate(env); let operator = Address::generate(env); @@ -38,7 +38,7 @@ pub fn setup_gateway<'a>( operator, &signer_set.domain_separator, minimum_rotation_delay, - previous_signers_retention as u64, + previous_signers_retention, initial_signers, ), ); @@ -79,13 +79,13 @@ pub fn generate_test_message_with_rng( ) } -pub fn randint(a: u32, b: u32) -> u32 { +pub fn randint(a: u64, b: u64) -> u64 { rand::thread_rng().gen_range(a..b) } pub fn generate_signers_set( env: &Env, - num_signers: u32, + num_signers: u64, domain_separator: BytesN<32>, ) -> TestSignerSet { generate_signers_set_with_rng(env, num_signers, domain_separator, rand::thread_rng()) @@ -93,7 +93,7 @@ pub fn generate_signers_set( pub fn generate_signers_set_with_rng( env: &Env, - num_signers: u32, + num_signers: u64, domain_separator: BytesN<32>, mut rng: impl Rng + rand::CryptoRng, ) -> TestSignerSet { diff --git a/packages/stellar-axelar-std/src/types.rs b/packages/stellar-axelar-std/src/types.rs index 999072c8..09c3f381 100644 --- a/packages/stellar-axelar-std/src/types.rs +++ b/packages/stellar-axelar-std/src/types.rs @@ -3,6 +3,6 @@ use soroban_sdk::{contracttype, Address}; #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub struct Token { - pub address: Address, // TODO: check if this can be changed to a TokenClient type instead which is richer than Address, or a generic type implementing TokenInterface + pub address: Address, pub amount: i128, }