From e8deb754bf5dc9c6d46ecc27a54a522bcb9eb682 Mon Sep 17 00:00:00 2001 From: Attiss Ngo <92927591+AttissNgo@users.noreply.github.com> Date: Mon, 23 Dec 2024 09:56:28 -0600 Subject: [PATCH 1/3] chore: simplify auth test utils - simplify auth_invocation macro usage - update tests - test auth for deploy_remote_canonical_token --- contracts/example/tests/test.rs | 56 +++++----- .../tests/deploy_remote_canonical_token.rs | 66 ++++++++++-- .../tests/deploy_remote_interchain_token.rs | 101 +++++------------- packages/axelar-soroban-std/src/testutils.rs | 49 ++++----- 4 files changed, 128 insertions(+), 144 deletions(-) diff --git a/contracts/example/tests/test.rs b/contracts/example/tests/test.rs index b29b1fe0..8d52214c 100644 --- a/contracts/example/tests/test.rs +++ b/contracts/example/tests/test.rs @@ -1,21 +1,17 @@ #![cfg(test)] extern crate std; -use axelar_gas_service::AxelarGasService; -use axelar_gas_service::AxelarGasServiceClient; -use axelar_gateway::testutils::{self, generate_proof, get_approve_hash, TestSignerSet}; -use axelar_gateway::types::Message; -use axelar_gateway::AxelarGatewayClient; -use axelar_soroban_std::types::Token; -use axelar_soroban_std::{assert_last_emitted_event, auth_invocation}; -use example::Example; -use example::ExampleClient; -use soroban_sdk::testutils::{AuthorizedFunction, AuthorizedInvocation}; -use soroban_sdk::token::StellarAssetClient; -use soroban_sdk::{ - testutils::Address as _, testutils::BytesN as _, vec, Address, BytesN, Env, String, +use axelar_gas_service::{AxelarGasService, AxelarGasServiceClient}; +use axelar_gateway::{ + testutils::{self, generate_proof, get_approve_hash, TestSignerSet}, + types::Message, + AxelarGatewayClient, }; -use soroban_sdk::{Bytes, IntoVal, Symbol}; +use axelar_soroban_std::{assert_last_emitted_event, auth_invocation, types::Token}; +use example::{Example, ExampleClient}; +use soroban_sdk::testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation, BytesN as _}; +use soroban_sdk::token::StellarAssetClient; +use soroban_sdk::{vec, Address, Bytes, BytesN, Env, IntoVal, String, Symbol}; fn setup_gateway<'a>(env: &Env) -> (TestSignerSet, AxelarGatewayClient<'a>) { let (signers, client) = testutils::setup_gateway(env, 0, 5); @@ -49,7 +45,7 @@ fn gmp_example() { let source_chain = String::from_str(&env, "source"); let (_, source_gateway_client) = setup_gateway(&env); let source_gateway_id = source_gateway_client.address; - let (_source_gas_service_client, _source_gas_collector, source_gas_service_id) = + let (source_gas_service_client, _source_gas_collector, source_gas_service_id) = setup_gas_service(&env); let source_app = setup_app(&env, &source_gateway_id, &source_gas_service_id); @@ -73,13 +69,14 @@ fn gmp_example() { // Initiate cross-chain contract call, sending message from source to destination let asset = &env.register_stellar_asset_contract_v2(user.clone()); + let asset_client = StellarAssetClient::new(&env, &asset.address()); let gas_amount: i128 = 100; let gas_token = Token { address: asset.address(), amount: gas_amount, }; - StellarAssetClient::new(&env, &asset.address()).mint(&user, &gas_amount); + asset_client.mint(&user, &gas_amount); source_app.send( &user, @@ -89,20 +86,16 @@ fn gmp_example() { &gas_token, ); - let transfer_auth = auth_invocation!(&env, - "transfer", - asset.address() => - ( - &user, - source_gas_service_id.clone(), - gas_token.amount - ) + let transfer_auth = auth_invocation!( + &env, + user, + asset_client.transfer(&user, source_gas_service_id, gas_token.amount) ); - let pay_gas_auth = auth_invocation!(&env, - "pay_gas", - source_gas_service_id.clone() => - ( + let pay_gas_auth = auth_invocation!( + &env, + user, + source_gas_service_client.pay_gas( source_app.address.clone(), destination_chain.clone(), destination_address.clone(), @@ -114,11 +107,10 @@ fn gmp_example() { transfer_auth ); - let send_auth = auth_invocation!(&env, + let send_auth = auth_invocation!( + &env, user, - "send", - source_app.address.clone() => - ( + source_app.send( &user, destination_chain.clone(), destination_address.clone(), diff --git a/contracts/interchain-token-service/tests/deploy_remote_canonical_token.rs b/contracts/interchain-token-service/tests/deploy_remote_canonical_token.rs index 13fff672..fbf16b7b 100644 --- a/contracts/interchain-token-service/tests/deploy_remote_canonical_token.rs +++ b/contracts/interchain-token-service/tests/deploy_remote_canonical_token.rs @@ -1,16 +1,19 @@ mod utils; -use axelar_soroban_std::{address::AddressExt, events}; +use axelar_soroban_std::{address::AddressExt, auth_invocation, events}; use interchain_token_service::{ - event::InterchainTokenDeploymentStartedEvent, types::TokenManagerType, + event::InterchainTokenDeploymentStartedEvent, + types::{DeployInterchainToken, HubMessage, Message, TokenManagerType}, }; -use soroban_sdk::{testutils::Address as _, token::StellarAssetClient, Address, String}; +use soroban_sdk::testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation}; +use soroban_sdk::token::{self, StellarAssetClient}; +use soroban_sdk::{Address, Bytes, IntoVal, String, Symbol}; +use soroban_token_sdk::metadata::TokenMetadata; use utils::{setup_env, setup_gas_token}; #[test] fn deploy_remote_canonical_token_succeeds() { - let (env, client, _, _, _) = setup_env(); - + let (env, client, _, gas_service, _) = setup_env(); let spender = Address::generate(&env); let gas_token = setup_gas_token(&env, &spender); let asset = &env.register_stellar_asset_contract_v2(Address::generate(&env)); @@ -23,20 +26,42 @@ fn deploy_remote_canonical_token_succeeds() { let token_address = asset.address(); let expected_deploy_salt = client.canonical_token_deploy_salt(&token_address); let expected_id = client.interchain_token_id(&Address::zero(&env), &expected_deploy_salt); - assert_eq!(client.register_canonical_token(&token_address), expected_id); assert_eq!(client.token_address(&expected_id), token_address); - assert_eq!( client.token_manager_type(&expected_id), TokenManagerType::LockUnlock ); let destination_chain = String::from_str(&env, "ethereum"); + let its_hub_chain = String::from_str(&env, "axelar"); + let its_hub_address = String::from_str(&env, "its_hub_address"); + client .mock_all_auths() .set_trusted_chain(&destination_chain); + let token = token::Client::new(&env, &asset.address()); + let token_metadata = TokenMetadata { + name: token.name(), + decimal: token.decimals(), + symbol: token.symbol(), + }; + + let message = Message::DeployInterchainToken(DeployInterchainToken { + token_id: expected_id.clone(), + name: token_metadata.name.clone(), + symbol: token_metadata.symbol.clone(), + decimals: token_metadata.decimal as u8, + minter: None, + }); + + let payload = HubMessage::SendToHub { + destination_chain: destination_chain.clone(), + message, + } + .abi_encode(&env); + let deployed_token_id = client .mock_all_auths_allowing_non_root_auth() .deploy_remote_canonical_token(&token_address, &destination_chain, &spender, &gas_token); @@ -45,6 +70,33 @@ fn deploy_remote_canonical_token_succeeds() { goldie::assert!(events::fmt_emitted_event_at_idx::< InterchainTokenDeploymentStartedEvent, >(&env, -4)); + + let transfer_auth = auth_invocation!( + &env, + spender, + gas_token.transfer( + spender.clone(), + gas_service.address.clone(), + gas_token.amount + ) + ); + + let gas_service_auth = auth_invocation!( + &env, + spender, + gas_service.pay_gas( + client.address.clone(), + its_hub_chain, + its_hub_address, + payload, + spender.clone(), + gas_token.clone(), + Bytes::new(&env) + ), + transfer_auth + ); + + assert_eq!(env.auths(), gas_service_auth); } #[test] diff --git a/contracts/interchain-token-service/tests/deploy_remote_interchain_token.rs b/contracts/interchain-token-service/tests/deploy_remote_interchain_token.rs index 4b9e4bf8..491869d6 100644 --- a/contracts/interchain-token-service/tests/deploy_remote_interchain_token.rs +++ b/contracts/interchain-token-service/tests/deploy_remote_interchain_token.rs @@ -1,63 +1,18 @@ mod utils; -use axelar_soroban_std::assert_contract_err; -use axelar_soroban_std::auth_invocation; -use axelar_soroban_std::events; -use interchain_token_service::error::ContractError; -use interchain_token_service::event::InterchainTokenDeploymentStartedEvent; -use interchain_token_service::types::DeployInterchainToken; -use interchain_token_service::types::HubMessage; -use interchain_token_service::types::Message; -use soroban_sdk::testutils::AuthorizedFunction; -use soroban_sdk::testutils::AuthorizedInvocation; -use soroban_sdk::Bytes; -use soroban_sdk::IntoVal; -use soroban_sdk::Symbol; -use soroban_sdk::{testutils::Address as _, Address, BytesN, String}; +use axelar_soroban_std::{assert_contract_err, auth_invocation, events}; +use interchain_token_service::{ + error::ContractError, + event::InterchainTokenDeploymentStartedEvent, + types::{DeployInterchainToken, HubMessage, Message}, +}; +use soroban_sdk::testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation}; +use soroban_sdk::{Address, Bytes, BytesN, IntoVal, String, Symbol}; use soroban_token_sdk::metadata::TokenMetadata; -use utils::setup_env; -use utils::setup_gas_token; -use utils::TokenMetadataExt; +use utils::{setup_env, setup_gas_token, TokenMetadataExt}; #[test] fn deploy_remote_interchain_token_succeeds() { - let (env, client, _, _, _) = setup_env(); - - let sender = Address::generate(&env); - let gas_token = setup_gas_token(&env, &sender); - let minter: Option
= None; - let salt = BytesN::<32>::from_array(&env, &[1; 32]); - let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6); - let initial_supply = 1; - - let token_id = client.mock_all_auths().deploy_interchain_token( - &sender, - &salt, - &token_metadata, - &initial_supply, - &minter, - ); - - let destination_chain = String::from_str(&env, "ethereum"); - client - .mock_all_auths() - .set_trusted_chain(&destination_chain); - - let deployed_token_id = client.mock_all_auths().deploy_remote_interchain_token( - &sender, - &salt, - &destination_chain, - &gas_token, - ); - assert_eq!(token_id, deployed_token_id); - - goldie::assert!(events::fmt_emitted_event_at_idx::< - InterchainTokenDeploymentStartedEvent, - >(&env, -4)); -} - -#[test] -fn deploy_remote_interchain_token_auth_test() { let (env, client, _, gas_service, _) = setup_env(); let sender = Address::generate(&env); @@ -89,8 +44,13 @@ fn deploy_remote_interchain_token_auth_test() { &destination_chain, &gas_token, ); + assert_eq!(token_id, deployed_token_id); + goldie::assert!(events::fmt_emitted_event_at_idx::< + InterchainTokenDeploymentStartedEvent, + >(&env, -4)); + let message = Message::DeployInterchainToken(DeployInterchainToken { token_id, name: token_metadata.name.clone(), @@ -98,27 +58,22 @@ fn deploy_remote_interchain_token_auth_test() { decimals: token_metadata.decimal as u8, minter: None, }); - let payload = HubMessage::SendToHub { destination_chain: destination_chain.clone(), message, } .abi_encode(&env); - let transfer_auth = auth_invocation!(&env, - "transfer", - gas_token.clone().address => - ( - &sender, - gas_service.address.clone(), - gas_token.amount - ) + let transfer_auth = auth_invocation!( + &env, + sender, + gas_token.transfer(&sender, gas_service.address.clone(), gas_token.amount) ); - let pay_gas_auth = auth_invocation!(&env, - "pay_gas", - gas_service.address => - ( + let pay_gas_auth = auth_invocation!( + &env, + sender, + gas_service.pay_gas( client.address.clone(), its_hub_chain, its_hub_address, @@ -130,16 +85,10 @@ fn deploy_remote_interchain_token_auth_test() { transfer_auth ); - let deploy_remote_interchain_token_auth = auth_invocation!(&env, + let deploy_remote_interchain_token_auth = auth_invocation!( + &env, sender, - "deploy_remote_interchain_token", - client.address => - ( - &sender, - salt, - destination_chain, - gas_token - ), + client.deploy_remote_interchain_token(&sender, salt, destination_chain, gas_token), pay_gas_auth ); diff --git a/packages/axelar-soroban-std/src/testutils.rs b/packages/axelar-soroban-std/src/testutils.rs index 6ab99329..7c744f78 100644 --- a/packages/axelar-soroban-std/src/testutils.rs +++ b/packages/axelar-soroban-std/src/testutils.rs @@ -73,41 +73,32 @@ where #[macro_export] macro_rules! auth_invocation { - // Normal invocation: auth!(env, "function_name", address => args) - ($env:expr, $fn:expr, $addr:expr => $args:expr) => {{ - std::vec![AuthorizedInvocation { - function: AuthorizedFunction::Contract(( - $addr, - Symbol::new($env, $fn), - $args.into_val($env), - )), - sub_invocations: std::vec![], - }] - }}; - - // Invocation with sub-invocations: auth!(env, "function_name", address => args, subs) - ($env:expr, $fn:expr, $addr:expr => $args:expr, $subs:expr) => {{ - std::vec![AuthorizedInvocation { - function: AuthorizedFunction::Contract(( - $addr, - Symbol::new($env, $fn), - $args.into_val($env), - )), - sub_invocations: $subs, - }] + // Basic case without sub-invocations + ($env:expr, $caller:expr, $client:ident.$method:ident($($arg:expr),* $(,)?)) => {{ + std::vec![( + $caller.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + $client.address.clone(), + Symbol::new($env, stringify!($method)), + ($($arg),*).into_val($env), + )), + sub_invocations: std::vec![], + } + )] }}; - // User invocation: auth!(env, user, "function_name", address => args, subs) - ($env:expr, $user:expr, $fn:expr, $addr:expr => $args:expr, $subs:expr) => {{ + // Case with sub-invocations (handles both regular and user auth cases) + ($env:expr, $caller:expr, $client:ident.$method:ident($($arg:expr),* $(,)?), $subs:expr $(, $user:ident)?) => {{ std::vec![( - $user.clone(), + $caller.clone(), AuthorizedInvocation { function: AuthorizedFunction::Contract(( - $addr, - Symbol::new($env, $fn), - $args.into_val($env), + $client.address.clone(), + Symbol::new($env, stringify!($method)), + ($($arg),*).into_val($env), )), - sub_invocations: $subs + sub_invocations: $subs.into_iter().map(|(_, inv)| inv).collect(), } )] }}; From d43238778cd242348afde475915af65b250ece0d Mon Sep 17 00:00:00 2001 From: Attiss Ngo <92927591+AttissNgo@users.noreply.github.com> Date: Mon, 23 Dec 2024 10:03:06 -0600 Subject: [PATCH 2/3] remove unnecessary clones from test --- .../tests/deploy_remote_canonical_token.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/interchain-token-service/tests/deploy_remote_canonical_token.rs b/contracts/interchain-token-service/tests/deploy_remote_canonical_token.rs index fbf16b7b..f8b9b427 100644 --- a/contracts/interchain-token-service/tests/deploy_remote_canonical_token.rs +++ b/contracts/interchain-token-service/tests/deploy_remote_canonical_token.rs @@ -85,12 +85,12 @@ fn deploy_remote_canonical_token_succeeds() { &env, spender, gas_service.pay_gas( - client.address.clone(), + client.address, its_hub_chain, its_hub_address, payload, - spender.clone(), - gas_token.clone(), + spender, + gas_token, Bytes::new(&env) ), transfer_auth From 323ef2f577fba0373f7670b9a1b1f853d90c72a7 Mon Sep 17 00:00:00 2001 From: Attiss Ngo <92927591+AttissNgo@users.noreply.github.com> Date: Mon, 23 Dec 2024 13:39:02 -0600 Subject: [PATCH 3/3] add docstring to auth_invocation macro --- packages/axelar-soroban-std/src/testutils.rs | 37 ++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/axelar-soroban-std/src/testutils.rs b/packages/axelar-soroban-std/src/testutils.rs index 7c744f78..f94bdbec 100644 --- a/packages/axelar-soroban-std/src/testutils.rs +++ b/packages/axelar-soroban-std/src/testutils.rs @@ -71,6 +71,43 @@ where assert_emitted_event(env, -1, contract_id, topics, data); } +/// Helper macro for building and verifying authorization chains in Soroban contract tests. +/// +/// Used to verify that contract calls require the correct sequence of authorizations. +/// See the example package for usage in gas payment and cross-chain message verification scenarios. +/// +/// # Example +/// ```rust,ignore +/// // Create authorization for a token transfer +/// let transfer_auth = auth_invocation!( +/// &env, +/// user, +/// asset_client.transfer( +/// &user, +/// source_gas_service_id, +/// gas_token.amount +/// ) +/// ); +/// +/// // Create nested authorization chain for gas payment +/// let pay_gas_auth = auth_invocation!( +/// &env, +/// user, +/// source_gas_service_client.pay_gas( +/// source_app.address, +/// destination_chain, +/// destination_address, +/// payload, +/// &user, +/// gas_token, +/// &Bytes::new(&env) +/// ), +/// transfer_auth +/// ); +/// +/// // Verify authorizations +/// assert_eq!(env.auths(), pay_gas_auth); +/// ``` #[macro_export] macro_rules! auth_invocation { // Basic case without sub-invocations