From c5516c68cd09f69bff73f5ba9517f064e163b466 Mon Sep 17 00:00:00 2001 From: Attiss Ngo <92927591+AttissNgo@users.noreply.github.com> Date: Wed, 8 Jan 2025 20:25:49 -0600 Subject: [PATCH 1/7] feat(interchain-token-service): add flow limit - implement flow limit for interchain transfers - add Operatable trait and change constructor - update golden files (tests generate different contract addresses due to updated constructor) - minor changes to test helper functions --- .../interchain-token-service/src/contract.rs | 34 ++- .../interchain-token-service/src/error.rs | 2 + .../interchain-token-service/src/event.rs | 23 ++ .../src/flow_limit.rs | 123 ++++++++ .../interchain-token-service/src/interface.rs | 12 + contracts/interchain-token-service/src/lib.rs | 1 + .../src/storage_types.rs | 3 + .../interchain-token-service/tests/execute.rs | 14 +- .../tests/flow_limit.rs | 271 ++++++++++++++++++ .../canonical_token_id_derivation.golden | 4 +- ...hain_token_message_execute_succeeds.golden | 4 +- .../deploy_interchain_token_succeeds.golden | 4 +- ...loy_remote_canonical_token_succeeds.golden | 4 +- ...oy_remote_interchain_token_succeeds.golden | 4 +- ...nterchain_transfer_execute_succeeds.golden | 4 +- ...n_transfer_message_execute_succeeds.golden | 4 +- .../interchain_transfer_send_succeeds.golden | 4 +- .../register_canonical_token_succeeds.golden | 4 +- .../testdata/remove_trusted_chain.golden | 2 +- .../testdata/set_flow_limit_succeeds.golden | 3 + .../tests/testdata/set_trusted_address.golden | 2 +- .../tests/utils/mod.rs | 4 +- 22 files changed, 499 insertions(+), 31 deletions(-) create mode 100644 contracts/interchain-token-service/src/flow_limit.rs create mode 100644 contracts/interchain-token-service/tests/flow_limit.rs create mode 100644 contracts/interchain-token-service/tests/testdata/set_flow_limit_succeeds.golden diff --git a/contracts/interchain-token-service/src/contract.rs b/contracts/interchain-token-service/src/contract.rs index e3607a6e..f04aa058 100644 --- a/contracts/interchain-token-service/src/contract.rs +++ b/contracts/interchain-token-service/src/contract.rs @@ -4,7 +4,7 @@ use axelar_soroban_std::events::Event; use axelar_soroban_std::token::validate_token_metadata; use axelar_soroban_std::ttl::{extend_instance_ttl, extend_persistent_ttl}; use axelar_soroban_std::{ - address::AddressExt, ensure, interfaces, types::Token, Ownable, Upgradable, + address::AddressExt, ensure, interfaces, types::Token, Operatable, Ownable, Upgradable, }; use interchain_token::InterchainTokenClient; use soroban_sdk::token::{self, StellarAssetClient}; @@ -22,10 +22,10 @@ use crate::event::{ use crate::executable::InterchainTokenExecutableClient; use crate::interface::InterchainTokenServiceInterface; use crate::storage_types::{DataKey, TokenIdConfigValue}; -use crate::token_handler; use crate::types::{ DeployInterchainToken, HubMessage, InterchainTransfer, Message, TokenManagerType, }; +use crate::{flow_limit, token_handler}; const ITS_HUB_CHAIN_NAME: &str = "axelar"; const PREFIX_INTERCHAIN_TOKEN_ID: &str = "its-interchain-token-id"; @@ -33,7 +33,7 @@ const PREFIX_INTERCHAIN_TOKEN_SALT: &str = "interchain-token-salt"; const PREFIX_CANONICAL_TOKEN_SALT: &str = "canonical-token-salt"; #[contract] -#[derive(Ownable, Upgradable)] +#[derive(Operatable, Ownable, Upgradable)] pub struct InterchainTokenService; #[contractimpl] @@ -41,6 +41,7 @@ impl InterchainTokenService { pub fn __constructor( env: Env, owner: Address, + operator: Address, gateway: Address, gas_service: Address, its_hub_address: String, @@ -48,6 +49,7 @@ impl InterchainTokenService { interchain_token_wasm_hash: BytesN<32>, ) { interfaces::set_owner(&env, &owner); + interfaces::set_operator(&env, &operator); env.storage().instance().set(&DataKey::Gateway, &gateway); env.storage() .instance() @@ -160,6 +162,28 @@ impl InterchainTokenServiceInterface for InterchainTokenService { .into() } + fn flow_limit(env: &Env, token_id: BytesN<32>) -> Option { + flow_limit::flow_limit(env, token_id) + } + + fn set_flow_limit( + env: &Env, + token_id: BytesN<32>, + flow_limit: Option, + ) -> Result<(), ContractError> { + Self::operator(env).require_auth(); + + flow_limit::set_flow_limit(env, token_id, flow_limit) + } + + fn flow_out_amount(env: &Env, token_id: BytesN<32>) -> i128 { + flow_limit::flow_out_amount(env, token_id) + } + + fn flow_in_amount(env: &Env, token_id: BytesN<32>) -> i128 { + flow_limit::flow_in_amount(env, token_id) + } + /// Computes a 32-byte deployment salt for a canonical token using the provided token address. /// /// The salt is derived by hashing a combination of a prefix, the chain name hash, @@ -337,6 +361,8 @@ impl InterchainTokenServiceInterface for InterchainTokenService { amount, )?; + flow_limit::add_flow_out(env, token_id.clone(), amount)?; + InterchainTransferSentEvent { token_id: token_id.clone(), source_address: caller.clone(), @@ -504,6 +530,8 @@ impl InterchainTokenService { let token_config_value = Self::token_id_config_with_extended_ttl(env, token_id.clone())?; + flow_limit::add_flow_in(env, token_id.clone(), amount)?; + token_handler::give_token( env, &destination_address, diff --git a/contracts/interchain-token-service/src/error.rs b/contracts/interchain-token-service/src/error.rs index 7c977378..c7db7224 100644 --- a/contracts/interchain-token-service/src/error.rs +++ b/contracts/interchain-token-service/src/error.rs @@ -22,4 +22,6 @@ pub enum ContractError { InvalidTokenMetaData = 16, InvalidTokenId = 17, TokenAlreadyDeployed = 18, + InvalidFlowLimit = 19, + FlowLimitExceeded = 20, } diff --git a/contracts/interchain-token-service/src/event.rs b/contracts/interchain-token-service/src/event.rs index 88252c60..5f26e65d 100644 --- a/contracts/interchain-token-service/src/event.rs +++ b/contracts/interchain-token-service/src/event.rs @@ -13,6 +13,12 @@ pub struct TrustedChainRemovedEvent { pub chain: String, } +#[derive(Debug, PartialEq, Eq)] +pub struct FlowLimitSetEvent { + pub token_id: BytesN<32>, + pub flow_limit: Option, +} + #[derive(Debug, PartialEq, Eq)] pub struct InterchainTokenDeployedEvent { pub token_id: BytesN<32>, @@ -84,6 +90,20 @@ impl Event for TrustedChainRemovedEvent { } } +impl Event for FlowLimitSetEvent { + fn topics(&self, env: &Env) -> impl Topics + Debug { + ( + Symbol::new(env, "flow_limit_set"), + self.token_id.to_val(), + self.flow_limit, + ) + } + + fn data(&self, env: &Env) -> impl IntoVal + Debug { + Vec::::new(env) + } +} + impl Event for InterchainTokenDeployedEvent { fn topics(&self, env: &Env) -> impl Topics + Debug { ( @@ -179,6 +199,9 @@ impl_event_testutils!(TrustedChainSetEvent, (Symbol, String), ()); #[cfg(any(test, feature = "testutils"))] impl_event_testutils!(TrustedChainRemovedEvent, (Symbol, String), ()); +#[cfg(any(test, feature = "testutils"))] +impl_event_testutils!(FlowLimitSetEvent, (Symbol, BytesN<32>, Option), ()); + #[cfg(any(test, feature = "testutils"))] impl_event_testutils!( InterchainTokenDeployedEvent, diff --git a/contracts/interchain-token-service/src/flow_limit.rs b/contracts/interchain-token-service/src/flow_limit.rs new file mode 100644 index 00000000..2e37b784 --- /dev/null +++ b/contracts/interchain-token-service/src/flow_limit.rs @@ -0,0 +1,123 @@ +use axelar_soroban_std::ensure; +use axelar_soroban_std::events::Event; +use axelar_soroban_std::ttl::extend_persistent_ttl; +use soroban_sdk::{BytesN, Env}; + +use crate::error::ContractError; +use crate::event::FlowLimitSetEvent; +use crate::storage_types::DataKey; + +const EPOCH_TIME: u64 = 6 * 60 * 60; // 6 hours in seconds = 21600 + +pub fn flow_limit(env: &Env, token_id: BytesN<32>) -> Option { + env.storage() + .persistent() + .get(&DataKey::FlowLimit(token_id)) +} + +pub fn set_flow_limit( + env: &Env, + token_id: BytesN<32>, + flow_limit: Option, +) -> Result<(), ContractError> { + if let Some(limit) = flow_limit { + ensure!(limit > 0, ContractError::InvalidFlowLimit); + } + + env.storage() + .persistent() + .set(&DataKey::FlowLimit(token_id.clone()), &flow_limit); + + FlowLimitSetEvent { + token_id, + flow_limit, + } + .emit(env); + + Ok(()) +} + +pub fn flow_out_amount(env: &Env, token_id: BytesN<32>) -> i128 { + let epoch = env.ledger().timestamp() / EPOCH_TIME; + env.storage() + .temporary() + .get(&DataKey::FlowOut(token_id, epoch)) + .unwrap_or(0) +} + +pub fn flow_in_amount(env: &Env, token_id: BytesN<32>) -> i128 { + let epoch = env.ledger().timestamp() / EPOCH_TIME; + env.storage() + .temporary() + .get(&DataKey::FlowIn(token_id, epoch)) + .unwrap_or(0) +} + +enum FlowDirection { + In, + Out, +} + +fn add_flow( + env: &Env, + token_id: BytesN<32>, + flow_amount: i128, + direction: FlowDirection, +) -> Result<(), ContractError> { + let Some(flow_limit) = flow_limit(env, token_id.clone()) else { + return Ok(()); + }; + + let epoch = env.ledger().timestamp() / EPOCH_TIME; + + let (flow_to_add_key, flow_to_compare_key) = match direction { + FlowDirection::In => ( + DataKey::FlowIn(token_id.clone(), epoch), + DataKey::FlowOut(token_id.clone(), epoch), + ), + FlowDirection::Out => ( + DataKey::FlowOut(token_id.clone(), epoch), + DataKey::FlowIn(token_id.clone(), epoch), + ), + }; + + let flow_to_add: i128 = env.storage().temporary().get(&flow_to_add_key).unwrap_or(0); + let flow_to_compare: i128 = env + .storage() + .temporary() + .get(&flow_to_compare_key) + .unwrap_or(0); + + ensure!(flow_amount <= flow_limit, ContractError::FlowLimitExceeded); + + let new_flow = flow_to_add + .checked_add(flow_amount) + .ok_or(ContractError::FlowLimitExceeded)?; + let max_allowed = flow_to_compare + .checked_add(flow_limit) + .ok_or(ContractError::FlowLimitExceeded)?; + + ensure!(new_flow <= max_allowed, ContractError::FlowLimitExceeded); + + env.storage().temporary().set(&flow_to_add_key, &new_flow); + + extend_persistent_ttl(env, &DataKey::FlowLimit(token_id)); + + Ok(()) +} + +pub fn add_flow_in( + env: &Env, + token_id: BytesN<32>, + flow_amount: i128, +) -> Result<(), ContractError> { + add_flow(env, token_id, flow_amount, FlowDirection::In) +} + +pub fn add_flow_out( + env: &Env, + token_id: BytesN<32>, + flow_amount: i128, +) -> Result<(), ContractError> { + add_flow(env, token_id, flow_amount, FlowDirection::Out) +} diff --git a/contracts/interchain-token-service/src/interface.rs b/contracts/interchain-token-service/src/interface.rs index 918a6167..829c75d4 100644 --- a/contracts/interchain-token-service/src/interface.rs +++ b/contracts/interchain-token-service/src/interface.rs @@ -34,6 +34,18 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface { fn token_manager_type(env: &Env, token_id: BytesN<32>) -> TokenManagerType; + fn flow_limit(env: &Env, token_id: BytesN<32>) -> Option; + + fn set_flow_limit( + env: &Env, + token_id: BytesN<32>, + flow_limit: Option, + ) -> Result<(), ContractError>; + + fn flow_out_amount(env: &Env, token_id: BytesN<32>) -> i128; + + fn flow_in_amount(env: &Env, token_id: BytesN<32>) -> i128; + fn deploy_interchain_token( env: &Env, deployer: Address, diff --git a/contracts/interchain-token-service/src/lib.rs b/contracts/interchain-token-service/src/lib.rs index 73c8b4fb..5d3f241b 100644 --- a/contracts/interchain-token-service/src/lib.rs +++ b/contracts/interchain-token-service/src/lib.rs @@ -17,6 +17,7 @@ cfg_if::cfg_if! { mod storage_types; mod token_handler; mod contract; + mod flow_limit; pub use contract::{InterchainTokenService, InterchainTokenServiceClient}; } diff --git a/contracts/interchain-token-service/src/storage_types.rs b/contracts/interchain-token-service/src/storage_types.rs index 39beacfd..b93be66c 100644 --- a/contracts/interchain-token-service/src/storage_types.rs +++ b/contracts/interchain-token-service/src/storage_types.rs @@ -12,6 +12,9 @@ pub enum DataKey { ChainName, InterchainTokenWasmHash, TokenIdConfigKey(BytesN<32>), + FlowLimit(BytesN<32>), + FlowOut(BytesN<32>, u64), + FlowIn(BytesN<32>, u64), } #[contracttype] diff --git a/contracts/interchain-token-service/tests/execute.rs b/contracts/interchain-token-service/tests/execute.rs index e5d77bfa..de4f9cdf 100644 --- a/contracts/interchain-token-service/tests/execute.rs +++ b/contracts/interchain-token-service/tests/execute.rs @@ -51,7 +51,7 @@ fn execute_fails_with_invalid_message() { }, ]; - approve_gateway_messages(&env, gateway_client, signers, messages); + approve_gateway_messages(&env, &gateway_client, signers, messages); client.execute( &source_chain, @@ -101,7 +101,7 @@ fn interchain_transfer_message_execute_succeeds() { }, ]; - approve_gateway_messages(&env, gateway_client, signers, messages); + approve_gateway_messages(&env, &gateway_client, signers, messages); client.execute(&source_chain, &message_id, &source_address, &payload); @@ -153,7 +153,7 @@ fn deploy_interchain_token_message_execute_succeeds() { }, ]; - approve_gateway_messages(&env, gateway_client, signers, messages); + approve_gateway_messages(&env, &gateway_client, signers, messages); client.execute(&source_chain, &message_id, &source_address, &payload); @@ -207,7 +207,7 @@ fn deploy_interchain_token_message_execute_fails_empty_token_name() { }, ]; - approve_gateway_messages(&env, gateway_client, signers, messages); + approve_gateway_messages(&env, &gateway_client, signers, messages); client.execute( &source_chain, @@ -254,7 +254,7 @@ fn deploy_interchain_token_message_execute_fails_empty_token_symbol() { }, ]; - approve_gateway_messages(&env, gateway_client, signers, messages); + approve_gateway_messages(&env, &gateway_client, signers, messages); client.execute( &source_chain, @@ -303,7 +303,7 @@ fn deploy_interchain_token_message_execute_fails_invalid_minter_address() { }, ]; - approve_gateway_messages(&env, gateway_client, signers, messages); + approve_gateway_messages(&env, &gateway_client, signers, messages); client.execute( &source_chain, @@ -364,7 +364,7 @@ fn deploy_interchain_token_message_execute_fails_token_already_deployed() { }, ]; - approve_gateway_messages(&env, gateway_client, signers, messages); + approve_gateway_messages(&env, &gateway_client, signers, messages); client.execute(&source_chain, &first_message_id, &source_address, &payload); diff --git a/contracts/interchain-token-service/tests/flow_limit.rs b/contracts/interchain-token-service/tests/flow_limit.rs new file mode 100644 index 00000000..efcd0c8e --- /dev/null +++ b/contracts/interchain-token-service/tests/flow_limit.rs @@ -0,0 +1,271 @@ +mod utils; + +use axelar_gateway::types::Message as GatewayMessage; +use axelar_soroban_std::{assert_contract_err, assert_invoke_auth_ok, events, traits::BytesExt}; +use interchain_token_service::{ + error::ContractError, + event::FlowLimitSetEvent, + types::{HubMessage, InterchainTransfer, Message}, + InterchainTokenServiceClient, +}; +use soroban_sdk::testutils::{Address as _, Ledger as _}; +use soroban_sdk::{vec, xdr::ToXdr, Address, Bytes, BytesN, Env, String, Vec}; +use utils::{ + approve_gateway_messages, register_chains, setup_env, setup_gas_token, setup_its_token, + HUB_CHAIN, +}; + +const TEST_FLOW_LIMIT: Option = Some(1000); +const EPOCH_TIME: u64 = 6 * 60 * 60; + +fn setup_flow_limit(env: &Env, client: &InterchainTokenServiceClient) -> (BytesN<32>, Address) { + let supply = i128::MAX; + let deployer = Address::generate(&env); + let token_id = setup_its_token(&env, &client, &deployer, supply); + + env.mock_all_auths(); + client.set_flow_limit(&token_id, &TEST_FLOW_LIMIT); + + (token_id, deployer) +} + +fn create_interchain_transfer_message( + env: &Env, + client: &InterchainTokenServiceClient, + token_id: &BytesN<32>, + amount: i128, +) -> (String, String, String, Bytes, Vec) { + let sender = Address::generate(&env).to_xdr(&env); + let recipient = Address::generate(&env).to_xdr(&env); + let source_chain = client.its_hub_chain_name(); + let source_address = Address::generate(&env).to_string(); + + let msg = HubMessage::ReceiveFromHub { + source_chain: String::from_str(&env, HUB_CHAIN), + message: Message::InterchainTransfer(InterchainTransfer { + token_id: token_id.clone(), + source_address: sender, + destination_address: recipient, + amount, + data: None, + }), + }; + let payload = msg.abi_encode(&env).unwrap(); + let payload_hash: BytesN<32> = env.crypto().keccak256(&payload).into(); + + let message_id = Address::generate(env).to_string(); + + let messages = vec![ + &env, + GatewayMessage { + source_chain: source_chain.clone(), + message_id: message_id.clone(), + source_address: source_address.clone(), + contract_address: client.address.clone(), + payload_hash, + }, + ]; + + (source_chain, message_id, source_address, payload, messages) +} + +#[test] +fn set_flow_limit_succeeds() { + let (env, client, _, _, _) = setup_env(); + let token_id = BytesN::from_array(&env, &[1; 32]); + + assert_eq!(client.flow_limit(&token_id), None); + + assert_invoke_auth_ok!( + client.operator(), + client.try_set_flow_limit(&token_id, &TEST_FLOW_LIMIT) + ); + + assert_eq!(client.flow_limit(&token_id), TEST_FLOW_LIMIT); + + goldie::assert!(events::fmt_last_emitted_event::(&env)); +} + +#[test] +fn set_flow_limit_fails_invalid_amount() { + let (env, client, _, _, _) = setup_env(); + let token_id = BytesN::from_array(&env, &[1; 32]); + + let invalid_limit = Some(-1); + + env.mock_all_auths(); + + assert_contract_err!( + client.try_set_flow_limit(&token_id, &invalid_limit), + ContractError::InvalidFlowLimit + ); +} + +#[test] +fn flow_limit_resets_after_epoch() { + let (env, client, gateway_client, _, signers) = setup_env(); + register_chains(&env, &client); + let (token_id, _) = setup_flow_limit(&env, &client); + + let amount = TEST_FLOW_LIMIT.unwrap(); + + let (source_chain, message_id, source_address, payload, messages) = + create_interchain_transfer_message(&env, &client, &token_id, amount); + approve_gateway_messages(&env, &gateway_client, signers.clone(), messages); + client.execute(&source_chain, &message_id, &source_address, &payload); + assert_eq!(client.flow_in_amount(&token_id), amount); + + let current_timestamp = env.ledger().timestamp(); + env.ledger().set_timestamp(current_timestamp + EPOCH_TIME); + + let (source_chain, message_id, source_address, payload, messages) = + create_interchain_transfer_message(&env, &client, &token_id, amount); + approve_gateway_messages(&env, &gateway_client, signers, messages); + client.execute(&source_chain, &message_id, &source_address, &payload); + assert_eq!(client.flow_in_amount(&token_id), amount); +} + +#[test] +fn add_flow_in_succeeds() { + let (env, client, gateway_client, _, signers) = setup_env(); + register_chains(&env, &client); + let (token_id, _) = setup_flow_limit(&env, &client); + + let amount = TEST_FLOW_LIMIT.unwrap(); + let (source_chain, message_id, source_address, payload, messages) = + create_interchain_transfer_message(&env, &client, &token_id, amount); + approve_gateway_messages(&env, &gateway_client, signers, messages); + + assert_eq!(client.flow_in_amount(&token_id), 0); + + client.execute(&source_chain, &message_id, &source_address, &payload); + + assert_eq!(client.flow_in_amount(&token_id), amount); +} + +#[test] +#[should_panic(expected = "Error(Contract, #20)")] // ContractError::FlowLimitExceeded +fn add_flow_in_fails_exceeds_flow_limit() { + let (env, client, gateway_client, _, signers) = setup_env(); + register_chains(&env, &client); + let (token_id, _) = setup_flow_limit(&env, &client); + + let amount = TEST_FLOW_LIMIT.unwrap(); + let (source_chain, message_id, source_address, payload, messages) = + create_interchain_transfer_message(&env, &client, &token_id, amount); + approve_gateway_messages(&env, &gateway_client, signers.clone(), messages); + + client.execute(&source_chain, &message_id, &source_address, &payload); + + let second_amount = 1; + let (source_chain, message_id, source_address, payload, messages) = + create_interchain_transfer_message(&env, &client, &token_id, second_amount); + approve_gateway_messages(&env, &gateway_client, signers, messages); + + client.execute(&source_chain, &message_id, &source_address, &payload); +} + +#[test] +fn add_flow_out_succeeds() { + let (env, client, _, _, _) = setup_env(); + register_chains(&env, &client); + let (token_id, sender) = setup_flow_limit(&env, &client); + let gas_token = setup_gas_token(&env, &sender); + + let amount = 1000; + let destination_chain = String::from_str(&env, "ethereum"); + let destination_address = Bytes::from_hex(&env, "4F4495243837681061C4743b74B3eEdf548D56A5"); + let data = None; + + client + .mock_all_auths() + .set_trusted_chain(&destination_chain); + + client.mock_all_auths().interchain_transfer( + &sender, + &token_id, + &destination_chain, + &destination_address, + &amount, + &data, + &gas_token, + ); + + assert_eq!(client.flow_out_amount(&token_id), amount); +} + +#[test] +#[should_panic(expected = "Error(Contract, #20)")] // ContractError::FlowLimitExceeded +fn add_flow_out_fails_exceeds_flow_limit() { + let (env, client, _, _, _) = setup_env(); + register_chains(&env, &client); + let (token_id, sender) = setup_flow_limit(&env, &client); + let gas_token = setup_gas_token(&env, &sender); + + let amount = TEST_FLOW_LIMIT.unwrap(); + let destination_chain = String::from_str(&env, "ethereum"); + let destination_address = Bytes::from_hex(&env, "4F4495243837681061C4743b74B3eEdf548D56A5"); + let data = None; + + client + .mock_all_auths() + .set_trusted_chain(&destination_chain); + + client.mock_all_auths().interchain_transfer( + &sender, + &token_id, + &destination_chain, + &destination_address, + &amount, + &data, + &gas_token.clone(), + ); + + let second_amount = 1; + + client.mock_all_auths().interchain_transfer( + &sender, + &token_id, + &destination_chain, + &destination_address, + &second_amount, + &data, + &gas_token, + ); +} + +#[test] +#[should_panic(expected = "Error(Contract, #20)")] // ContractError::FlowLimitExceeded +fn add_flow_fails_on_flow_comparison_overflow() { + let (env, client, gateway_client, _, signers) = setup_env(); + register_chains(&env, &client); + let (token_id, sender) = setup_flow_limit(&env, &client); + let gas_token = setup_gas_token(&env, &sender); + + env.mock_all_auths(); + client.set_flow_limit(&token_id, &Some(i128::MAX - 50)); + + let high_amount = i128::MAX - 100; + let (source_chain, message_id, source_address, payload, messages) = + create_interchain_transfer_message(&env, &client, &token_id, high_amount); + approve_gateway_messages(&env, &gateway_client, signers, messages); + client.execute(&source_chain, &message_id, &source_address, &payload); + + let small_amount = 100; + let destination_chain = String::from_str(&env, "ethereum"); + let destination_address = Bytes::from_hex(&env, "4F4495243837681061C4743b74B3eEdf548D56A5"); + + client + .mock_all_auths() + .set_trusted_chain(&destination_chain); + + client.mock_all_auths().interchain_transfer( + &sender, + &token_id, + &destination_chain, + &destination_address, + &small_amount, + &None, + &gas_token, + ); +} diff --git a/contracts/interchain-token-service/tests/testdata/canonical_token_id_derivation.golden b/contracts/interchain-token-service/tests/testdata/canonical_token_id_derivation.golden index 74b242c2..64ba4111 100644 --- a/contracts/interchain-token-service/tests/testdata/canonical_token_id_derivation.golden +++ b/contracts/interchain-token-service/tests/testdata/canonical_token_id_derivation.golden @@ -1,5 +1,5 @@ [ "bdfb629dd56a9581bfba5ac25009cef0ee7646acbbd1880e99d7d26ea68f0885", - "007a506f6c21cb0e84f844a86f9a8a86be868f99f35015c78ba868a7b66afc6d", - "25c162306962b7a6d3ca7e65974ca39afa329e1cea9ed3d3cdaa70e38475a73a" + "b81a06139cc7eda856e5f063ea31759be22ace7d8b4532abe85c2dd28063776b", + "001664fa3a1ee48676821e0ecd0ae89705ac1983ba91cf432358e9f6e296946f" ] \ No newline at end of file diff --git a/contracts/interchain-token-service/tests/testdata/deploy_interchain_token_message_execute_succeeds.golden b/contracts/interchain-token-service/tests/testdata/deploy_interchain_token_message_execute_succeeds.golden index 39b8f699..fbb7c4e0 100644 --- a/contracts/interchain-token-service/tests/testdata/deploy_interchain_token_message_execute_succeeds.golden +++ b/contracts/interchain-token-service/tests/testdata/deploy_interchain_token_message_execute_succeeds.golden @@ -1,3 +1,3 @@ -contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5) -topics: (Symbol(interchain_token_deployed), BytesN<32>(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), Contract(CC2X6B2EMWFZBSQLILCJNRFQWPAD4MS4XYWW7XU5GL3LHCWD7OU6A3ZS), String(Test), String(TEST), 18, Some(Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON))) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON) +topics: (Symbol(interchain_token_deployed), BytesN<32>(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), Contract(CD2M6PPSH3SRJENCB2343AJLQD4HMFUE262YUBKPU4KMHKCJR24CYLKD), String(Test), String(TEST), 18, Some(Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5))) data: () \ No newline at end of file diff --git a/contracts/interchain-token-service/tests/testdata/deploy_interchain_token_succeeds.golden b/contracts/interchain-token-service/tests/testdata/deploy_interchain_token_succeeds.golden index 1f090518..d0fe05bd 100644 --- a/contracts/interchain-token-service/tests/testdata/deploy_interchain_token_succeeds.golden +++ b/contracts/interchain-token-service/tests/testdata/deploy_interchain_token_succeeds.golden @@ -1,3 +1,3 @@ -contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5) -topics: (Symbol(interchain_token_deployed), BytesN<32>(127, 197, 48, 155, 232, 144, 233, 5, 104, 135, 130, 118, 76, 97, 230, 164, 117, 144, 45, 242, 69, 90, 124, 72, 93, 71, 115, 62, 188, 31, 2, 167), Contract(CBXOPW23I3THDRTZG2QKYC3WFGQ2Y5BEU2OAPTNLCLFFXQE3BEUJKBOQ), String(name), String(symbol), 6, Some(Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5))) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON) +topics: (Symbol(interchain_token_deployed), BytesN<32>(99, 53, 232, 73, 94, 142, 208, 34, 221, 37, 56, 71, 241, 2, 248, 182, 137, 194, 30, 8, 231, 132, 62, 118, 139, 56, 235, 69, 172, 181, 4, 10), Contract(CAQT3W6SGEY6V6DZTKTX5JNNABDPW75R7BWIJFUY2IN4IKCUSIMZPCNG), String(name), String(symbol), 6, Some(Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON))) data: () \ No newline at end of file diff --git a/contracts/interchain-token-service/tests/testdata/deploy_remote_canonical_token_succeeds.golden b/contracts/interchain-token-service/tests/testdata/deploy_remote_canonical_token_succeeds.golden index 6cfdc395..e2c61045 100644 --- a/contracts/interchain-token-service/tests/testdata/deploy_remote_canonical_token_succeeds.golden +++ b/contracts/interchain-token-service/tests/testdata/deploy_remote_canonical_token_succeeds.golden @@ -1,3 +1,3 @@ -contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5) -topics: (Symbol(token_deployment_started), BytesN<32>(242, 242, 113, 171, 85, 152, 84, 8, 108, 160, 123, 152, 207, 224, 199, 212, 81, 160, 129, 59, 136, 60, 3, 77, 101, 229, 115, 108, 194, 43, 206, 19), Contract(CCF7HWZ6PBT2MQ6AUBKXM4CPWAR2PO6ND34WXLB32NDAYD7KIKBAAS7I), String(ethereum), String(aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA35JU), String(aaa), 7, None) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON) +topics: (Symbol(token_deployment_started), BytesN<32>(176, 82, 11, 134, 46, 148, 119, 103, 208, 219, 201, 52, 236, 230, 170, 39, 215, 103, 118, 78, 122, 72, 213, 57, 75, 143, 90, 231, 223, 12, 26, 102), Contract(CD6J4NTZEADXNETD2UY7ALD2N26RY35GGC73JSXXZB2GKB3ZTLR3Y4FH), String(ethereum), String(aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5FQE), String(aaa), 7, None) data: () \ No newline at end of file diff --git a/contracts/interchain-token-service/tests/testdata/deploy_remote_interchain_token_succeeds.golden b/contracts/interchain-token-service/tests/testdata/deploy_remote_interchain_token_succeeds.golden index 23dcca6d..fd5d82ad 100644 --- a/contracts/interchain-token-service/tests/testdata/deploy_remote_interchain_token_succeeds.golden +++ b/contracts/interchain-token-service/tests/testdata/deploy_remote_interchain_token_succeeds.golden @@ -1,3 +1,3 @@ -contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5) -topics: (Symbol(token_deployment_started), BytesN<32>(127, 197, 48, 155, 232, 144, 233, 5, 104, 135, 130, 118, 76, 97, 230, 164, 117, 144, 45, 242, 69, 90, 124, 72, 93, 71, 115, 62, 188, 31, 2, 167), Contract(CBXOPW23I3THDRTZG2QKYC3WFGQ2Y5BEU2OAPTNLCLFFXQE3BEUJKBOQ), String(ethereum), String(name), String(symbol), 6, None) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON) +topics: (Symbol(token_deployment_started), BytesN<32>(99, 53, 232, 73, 94, 142, 208, 34, 221, 37, 56, 71, 241, 2, 248, 182, 137, 194, 30, 8, 231, 132, 62, 118, 139, 56, 235, 69, 172, 181, 4, 10), Contract(CAQT3W6SGEY6V6DZTKTX5JNNABDPW75R7BWIJFUY2IN4IKCUSIMZPCNG), String(ethereum), String(name), String(symbol), 6, None) data: () \ No newline at end of file diff --git a/contracts/interchain-token-service/tests/testdata/interchain_transfer_execute_succeeds.golden b/contracts/interchain-token-service/tests/testdata/interchain_transfer_execute_succeeds.golden index 4aa7effc..e480984b 100644 --- a/contracts/interchain-token-service/tests/testdata/interchain_transfer_execute_succeeds.golden +++ b/contracts/interchain-token-service/tests/testdata/interchain_transfer_execute_succeeds.golden @@ -1,3 +1,3 @@ -contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON) -topics: (Symbol(executed), String(axelar), String(test), Bytes(0, 0, 0, 18, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10), BytesN<32>(127, 60, 199, 172, 30, 8, 21, 148, 10, 241, 246, 188, 96, 212, 234, 84, 53, 106, 109, 125, 167, 249, 31, 45, 164, 255, 214, 11, 85, 184, 125, 106), Contract(CDVVTYOBZQXIA6CA7UDEZMPS45IWRXW3NBEWON44FXYMBLSRSAS3BAU6), 1000) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5) +topics: (Symbol(executed), String(axelar), String(test), Bytes(0, 0, 0, 18, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11), BytesN<32>(19, 164, 112, 128, 76, 247, 2, 236, 196, 20, 87, 206, 58, 2, 208, 102, 225, 238, 175, 151, 45, 250, 47, 204, 76, 253, 158, 193, 68, 73, 208, 149), Contract(CCG4OE5Y32VT7WLMQCATZYK6KJDCUFY5XBSIHW7TEGXVR4ZD27YFN2N2), 1000) data: (Bytes(222, 173)) \ No newline at end of file diff --git a/contracts/interchain-token-service/tests/testdata/interchain_transfer_message_execute_succeeds.golden b/contracts/interchain-token-service/tests/testdata/interchain_transfer_message_execute_succeeds.golden index 7d199c0d..64c914fa 100644 --- a/contracts/interchain-token-service/tests/testdata/interchain_transfer_message_execute_succeeds.golden +++ b/contracts/interchain-token-service/tests/testdata/interchain_transfer_message_execute_succeeds.golden @@ -1,3 +1,3 @@ -contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5) -topics: (Symbol(interchain_transfer_received), String(axelar), BytesN<32>(127, 60, 199, 172, 30, 8, 21, 148, 10, 241, 246, 188, 96, 212, 234, 84, 53, 106, 109, 125, 167, 249, 31, 45, 164, 255, 214, 11, 85, 184, 125, 106), Bytes(0, 0, 0, 18, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9), Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5), 1000) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON) +topics: (Symbol(interchain_transfer_received), String(axelar), BytesN<32>(19, 164, 112, 128, 76, 247, 2, 236, 196, 20, 87, 206, 58, 2, 208, 102, 225, 238, 175, 151, 45, 250, 47, 204, 76, 253, 158, 193, 68, 73, 208, 149), Bytes(0, 0, 0, 18, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10), Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N), 1000) data: (None) \ No newline at end of file diff --git a/contracts/interchain-token-service/tests/testdata/interchain_transfer_send_succeeds.golden b/contracts/interchain-token-service/tests/testdata/interchain_transfer_send_succeeds.golden index d578ac03..9be88994 100644 --- a/contracts/interchain-token-service/tests/testdata/interchain_transfer_send_succeeds.golden +++ b/contracts/interchain-token-service/tests/testdata/interchain_transfer_send_succeeds.golden @@ -1,3 +1,3 @@ -contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5) -topics: (Symbol(interchain_transfer_sent), BytesN<32>(127, 197, 48, 155, 232, 144, 233, 5, 104, 135, 130, 118, 76, 97, 230, 164, 117, 144, 45, 242, 69, 90, 124, 72, 93, 71, 115, 62, 188, 31, 2, 167), Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON), String(ethereum), Bytes(79, 68, 149, 36, 56, 55, 104, 16, 97, 196, 116, 59, 116, 179, 238, 223, 84, 141, 86, 165), 1000) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON) +topics: (Symbol(interchain_transfer_sent), BytesN<32>(99, 53, 232, 73, 94, 142, 208, 34, 221, 37, 56, 71, 241, 2, 248, 182, 137, 194, 30, 8, 231, 132, 62, 118, 139, 56, 235, 69, 172, 181, 4, 10), Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5), String(ethereum), Bytes(79, 68, 149, 36, 56, 55, 104, 16, 97, 196, 116, 59, 116, 179, 238, 223, 84, 141, 86, 165), 1000) data: (Some(Bytes(171, 205))) \ No newline at end of file diff --git a/contracts/interchain-token-service/tests/testdata/register_canonical_token_succeeds.golden b/contracts/interchain-token-service/tests/testdata/register_canonical_token_succeeds.golden index 40453992..b2387d3b 100644 --- a/contracts/interchain-token-service/tests/testdata/register_canonical_token_succeeds.golden +++ b/contracts/interchain-token-service/tests/testdata/register_canonical_token_succeeds.golden @@ -1,3 +1,3 @@ -contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5) -topics: (Symbol(interchain_token_id_claimed), BytesN<32>(37, 193, 98, 48, 105, 98, 183, 166, 211, 202, 126, 101, 151, 76, 163, 154, 250, 50, 158, 28, 234, 158, 211, 211, 205, 170, 112, 227, 132, 117, 167, 58), AccountId(GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF), BytesN<32>(0, 122, 80, 111, 108, 33, 203, 14, 132, 248, 68, 168, 111, 154, 138, 134, 190, 134, 143, 153, 243, 80, 21, 199, 139, 168, 104, 167, 182, 106, 252, 109)) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON) +topics: (Symbol(interchain_token_id_claimed), BytesN<32>(0, 22, 100, 250, 58, 30, 228, 134, 118, 130, 30, 14, 205, 10, 232, 151, 5, 172, 25, 131, 186, 145, 207, 67, 35, 88, 233, 246, 226, 150, 148, 111), AccountId(GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF), BytesN<32>(184, 26, 6, 19, 156, 199, 237, 168, 86, 229, 240, 99, 234, 49, 117, 155, 226, 42, 206, 125, 139, 69, 50, 171, 232, 92, 45, 210, 128, 99, 119, 107)) data: () \ No newline at end of file diff --git a/contracts/interchain-token-service/tests/testdata/remove_trusted_chain.golden b/contracts/interchain-token-service/tests/testdata/remove_trusted_chain.golden index 1d00b35d..4f4410d4 100644 --- a/contracts/interchain-token-service/tests/testdata/remove_trusted_chain.golden +++ b/contracts/interchain-token-service/tests/testdata/remove_trusted_chain.golden @@ -1,3 +1,3 @@ -contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON) topics: (Symbol(trusted_chain_removed), String(chain)) data: () \ No newline at end of file diff --git a/contracts/interchain-token-service/tests/testdata/set_flow_limit_succeeds.golden b/contracts/interchain-token-service/tests/testdata/set_flow_limit_succeeds.golden new file mode 100644 index 00000000..faaf4a6b --- /dev/null +++ b/contracts/interchain-token-service/tests/testdata/set_flow_limit_succeeds.golden @@ -0,0 +1,3 @@ +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON) +topics: (Symbol(flow_limit_set), BytesN<32>(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), Some(1000)) +data: () \ No newline at end of file diff --git a/contracts/interchain-token-service/tests/testdata/set_trusted_address.golden b/contracts/interchain-token-service/tests/testdata/set_trusted_address.golden index e9ee4dbf..555d3462 100644 --- a/contracts/interchain-token-service/tests/testdata/set_trusted_address.golden +++ b/contracts/interchain-token-service/tests/testdata/set_trusted_address.golden @@ -1,3 +1,3 @@ -contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON) topics: (Symbol(trusted_chain_set), String(chain)) data: () \ No newline at end of file diff --git a/contracts/interchain-token-service/tests/utils/mod.rs b/contracts/interchain-token-service/tests/utils/mod.rs index 468798f9..e0c80239 100644 --- a/contracts/interchain-token-service/tests/utils/mod.rs +++ b/contracts/interchain-token-service/tests/utils/mod.rs @@ -26,6 +26,7 @@ fn setup_its<'a>( gas_service: &AxelarGasServiceClient, ) -> InterchainTokenServiceClient<'a> { let owner = Address::generate(env); + let operator = Address::generate(env); let its_hub_address = String::from_str(env, "its_hub_address"); let chain_name = String::from_str(env, "chain_name"); @@ -39,6 +40,7 @@ fn setup_its<'a>( InterchainTokenService, ( &owner, + &operator, &gateway.address, &gas_service.address, its_hub_address, @@ -117,7 +119,7 @@ pub fn register_chains(env: &Env, client: &InterchainTokenServiceClient) { #[allow(dead_code)] pub fn approve_gateway_messages( env: &Env, - gateway_client: AxelarGatewayClient, + gateway_client: &AxelarGatewayClient, signers: TestSignerSet, messages: Vec, ) { From b42d7e7e99a4dfa4f54d57018f13c56ae1cdb54b Mon Sep 17 00:00:00 2001 From: Attiss Ngo <92927591+AttissNgo@users.noreply.github.com> Date: Wed, 8 Jan 2025 20:35:59 -0600 Subject: [PATCH 2/7] remove unneeded references and clones from test --- .../interchain-token-service/tests/flow_limit.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/interchain-token-service/tests/flow_limit.rs b/contracts/interchain-token-service/tests/flow_limit.rs index efcd0c8e..9ec0c466 100644 --- a/contracts/interchain-token-service/tests/flow_limit.rs +++ b/contracts/interchain-token-service/tests/flow_limit.rs @@ -20,8 +20,8 @@ const EPOCH_TIME: u64 = 6 * 60 * 60; fn setup_flow_limit(env: &Env, client: &InterchainTokenServiceClient) -> (BytesN<32>, Address) { let supply = i128::MAX; - let deployer = Address::generate(&env); - let token_id = setup_its_token(&env, &client, &deployer, supply); + let deployer = Address::generate(env); + let token_id = setup_its_token(env, &client, &deployer, supply); env.mock_all_auths(); client.set_flow_limit(&token_id, &TEST_FLOW_LIMIT); @@ -35,13 +35,13 @@ fn create_interchain_transfer_message( token_id: &BytesN<32>, amount: i128, ) -> (String, String, String, Bytes, Vec) { - let sender = Address::generate(&env).to_xdr(&env); - let recipient = Address::generate(&env).to_xdr(&env); + let sender = Address::generate(env).to_xdr(&env); + let recipient = Address::generate(env).to_xdr(&env); let source_chain = client.its_hub_chain_name(); - let source_address = Address::generate(&env).to_string(); + let source_address = Address::generate(env).to_string(); let msg = HubMessage::ReceiveFromHub { - source_chain: String::from_str(&env, HUB_CHAIN), + source_chain: String::from_str(env, HUB_CHAIN), message: Message::InterchainTransfer(InterchainTransfer { token_id: token_id.clone(), source_address: sender, @@ -50,7 +50,7 @@ fn create_interchain_transfer_message( data: None, }), }; - let payload = msg.abi_encode(&env).unwrap(); + let payload = msg.abi_encode(env).unwrap(); let payload_hash: BytesN<32> = env.crypto().keccak256(&payload).into(); let message_id = Address::generate(env).to_string(); @@ -218,7 +218,7 @@ fn add_flow_out_fails_exceeds_flow_limit() { &destination_address, &amount, &data, - &gas_token.clone(), + &gas_token, ); let second_amount = 1; From f4f6f36f7e061bb307048784d80725b655d306b1 Mon Sep 17 00:00:00 2001 From: Attiss Ngo <92927591+AttissNgo@users.noreply.github.com> Date: Wed, 8 Jan 2025 20:40:38 -0600 Subject: [PATCH 3/7] remove unneeded references from tests --- contracts/interchain-token-service/tests/flow_limit.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/interchain-token-service/tests/flow_limit.rs b/contracts/interchain-token-service/tests/flow_limit.rs index 9ec0c466..7a85e0ca 100644 --- a/contracts/interchain-token-service/tests/flow_limit.rs +++ b/contracts/interchain-token-service/tests/flow_limit.rs @@ -21,7 +21,7 @@ const EPOCH_TIME: u64 = 6 * 60 * 60; fn setup_flow_limit(env: &Env, client: &InterchainTokenServiceClient) -> (BytesN<32>, Address) { let supply = i128::MAX; let deployer = Address::generate(env); - let token_id = setup_its_token(env, &client, &deployer, supply); + let token_id = setup_its_token(env, client, &deployer, supply); env.mock_all_auths(); client.set_flow_limit(&token_id, &TEST_FLOW_LIMIT); @@ -35,8 +35,8 @@ fn create_interchain_transfer_message( token_id: &BytesN<32>, amount: i128, ) -> (String, String, String, Bytes, Vec) { - let sender = Address::generate(env).to_xdr(&env); - let recipient = Address::generate(env).to_xdr(&env); + let sender = Address::generate(env).to_xdr(env); + let recipient = Address::generate(env).to_xdr(env); let source_chain = client.its_hub_chain_name(); let source_address = Address::generate(env).to_string(); From 40de86edd41b97ca22a7751e9d1ac9d3cc535fe9 Mon Sep 17 00:00:00 2001 From: Attiss Ngo <92927591+AttissNgo@users.noreply.github.com> Date: Fri, 10 Jan 2025 06:53:18 -0600 Subject: [PATCH 4/7] feat(interchain-token-service): add flow limit - Add docstrings to entrypoints - Create FlowKey struct for better organization of storage keys - Move flow logic into FlowDirection impl - Organize imports and group public functions - Allow zero flow limit to 'freeze' token --- .../interchain-token-service/src/contract.rs | 40 ++++-- .../interchain-token-service/src/event.rs | 27 +--- .../src/flow_limit.rs | 127 +++++++++++------- .../interchain-token-service/src/interface.rs | 8 +- .../src/storage_types.rs | 11 +- .../tests/flow_limit.rs | 33 ++++- packages/axelar-soroban-std/src/events.rs | 6 +- 7 files changed, 154 insertions(+), 98 deletions(-) diff --git a/contracts/interchain-token-service/src/contract.rs b/contracts/interchain-token-service/src/contract.rs index f04aa058..fad26141 100644 --- a/contracts/interchain-token-service/src/contract.rs +++ b/contracts/interchain-token-service/src/contract.rs @@ -162,10 +162,42 @@ impl InterchainTokenServiceInterface for InterchainTokenService { .into() } + /// Retrieves the flow limit for the token associated with the specified token ID. + /// Returns None if no limit is set. fn flow_limit(env: &Env, token_id: BytesN<32>) -> Option { flow_limit::flow_limit(env, token_id) } + /// Retrieves the flow out amount for the current epoch for the token + /// associated with the specified token ID. + fn flow_out_amount(env: &Env, token_id: BytesN<32>) -> i128 { + flow_limit::flow_out_amount(env, token_id) + } + + /// Retrieves the flow out amount for the current epoch for the token + /// associated with the specified token ID. + fn flow_in_amount(env: &Env, token_id: BytesN<32>) -> i128 { + flow_limit::flow_in_amount(env, token_id) + } + + /// Sets or updates the flow limit for a token. + /// + /// Flow limit controls how many tokens can flow in/out during a single epoch. + /// Setting the limit to None disables flow limit checks for the token. + /// Setting the limit to 0 effectively freezes the token by preventing any flow. + /// + /// # Arguments + /// - `env`: Reference to the contract environment. + /// - `token_id`: Unique identifier of the token. + /// - `flow_limit`: The new flow limit value. Must be positive if Some. + /// + /// # Returns + /// - `Result<(), ContractError>`: Ok(()) on success. + /// + /// # Errors + /// - `ContractError::InvalidFlowLimit`: If the provided flow limit is not positive. + /// + /// Authorization: Only the operator can call this function. Unauthorized calls will panic. fn set_flow_limit( env: &Env, token_id: BytesN<32>, @@ -176,14 +208,6 @@ impl InterchainTokenServiceInterface for InterchainTokenService { flow_limit::set_flow_limit(env, token_id, flow_limit) } - fn flow_out_amount(env: &Env, token_id: BytesN<32>) -> i128 { - flow_limit::flow_out_amount(env, token_id) - } - - fn flow_in_amount(env: &Env, token_id: BytesN<32>) -> i128 { - flow_limit::flow_in_amount(env, token_id) - } - /// Computes a 32-byte deployment salt for a canonical token using the provided token address. /// /// The salt is derived by hashing a combination of a prefix, the chain name hash, diff --git a/contracts/interchain-token-service/src/event.rs b/contracts/interchain-token-service/src/event.rs index 5f26e65d..6358187b 100644 --- a/contracts/interchain-token-service/src/event.rs +++ b/contracts/interchain-token-service/src/event.rs @@ -1,7 +1,7 @@ use core::fmt::Debug; use axelar_soroban_std::events::Event; -use soroban_sdk::{Address, Bytes, BytesN, Env, IntoVal, String, Symbol, Topics, Val, Vec}; +use soroban_sdk::{Address, Bytes, BytesN, Env, IntoVal, String, Symbol, Topics, Val}; #[derive(Debug, PartialEq, Eq)] pub struct TrustedChainSetEvent { @@ -16,6 +16,7 @@ pub struct TrustedChainRemovedEvent { #[derive(Debug, PartialEq, Eq)] pub struct FlowLimitSetEvent { pub token_id: BytesN<32>, + /// Setting to None bypasses flow limit checks pub flow_limit: Option, } @@ -71,10 +72,6 @@ impl Event for TrustedChainSetEvent { fn topics(&self, env: &Env) -> impl Topics + Debug { (Symbol::new(env, "trusted_chain_set"), self.chain.to_val()) } - - fn data(&self, env: &Env) -> impl IntoVal + Debug { - Vec::::new(env) - } } impl Event for TrustedChainRemovedEvent { @@ -84,10 +81,6 @@ impl Event for TrustedChainRemovedEvent { self.chain.to_val(), ) } - - fn data(&self, env: &Env) -> impl IntoVal + Debug { - Vec::::new(env) - } } impl Event for FlowLimitSetEvent { @@ -98,10 +91,6 @@ impl Event for FlowLimitSetEvent { self.flow_limit, ) } - - fn data(&self, env: &Env) -> impl IntoVal + Debug { - Vec::::new(env) - } } impl Event for InterchainTokenDeployedEvent { @@ -116,10 +105,6 @@ impl Event for InterchainTokenDeployedEvent { self.minter.clone(), ) } - - fn data(&self, env: &Env) -> impl IntoVal + Debug { - Vec::::new(env) - } } impl Event for InterchainTokenDeploymentStartedEvent { @@ -135,10 +120,6 @@ impl Event for InterchainTokenDeploymentStartedEvent { self.minter.clone(), ) } - - fn data(&self, env: &Env) -> impl IntoVal + Debug { - Vec::::new(env) - } } impl Event for InterchainTokenIdClaimedEvent { @@ -150,10 +131,6 @@ impl Event for InterchainTokenIdClaimedEvent { self.salt.to_val(), ) } - - fn data(&self, env: &Env) -> impl IntoVal + Debug { - Vec::::new(env) - } } impl Event for InterchainTransferSentEvent { diff --git a/contracts/interchain-token-service/src/flow_limit.rs b/contracts/interchain-token-service/src/flow_limit.rs index 2e37b784..f9274260 100644 --- a/contracts/interchain-token-service/src/flow_limit.rs +++ b/contracts/interchain-token-service/src/flow_limit.rs @@ -1,14 +1,51 @@ -use axelar_soroban_std::ensure; -use axelar_soroban_std::events::Event; -use axelar_soroban_std::ttl::extend_persistent_ttl; +use axelar_soroban_std::{ensure, events::Event, ttl::extend_persistent_ttl}; use soroban_sdk::{BytesN, Env}; -use crate::error::ContractError; -use crate::event::FlowLimitSetEvent; -use crate::storage_types::DataKey; +use crate::{ + error::ContractError, + event::FlowLimitSetEvent, + storage_types::{DataKey, FlowKey}, +}; const EPOCH_TIME: u64 = 6 * 60 * 60; // 6 hours in seconds = 21600 +enum FlowDirection { + In, + Out, +} + +impl FlowDirection { + fn flow(&self, env: &Env, token_id: BytesN<32>) -> i128 { + match self { + Self::In => flow_in_amount(env, token_id), + Self::Out => flow_out_amount(env, token_id), + } + } + + fn reverse_flow(&self, env: &Env, token_id: BytesN<32>) -> i128 { + match self { + Self::In => flow_out_amount(env, token_id), + Self::Out => flow_in_amount(env, token_id), + } + } + + fn update_flow(&self, env: &Env, token_id: BytesN<32>, new_flow: i128) { + let flow_key = FlowKey { + token_id, + epoch: current_epoch(env), + }; + let key = match self { + Self::In => DataKey::FlowIn(flow_key), + Self::Out => DataKey::FlowOut(flow_key), + }; + env.storage().temporary().set(&key, &new_flow); + } +} + +fn current_epoch(env: &Env) -> u64 { + env.ledger().timestamp() / EPOCH_TIME +} + pub fn flow_limit(env: &Env, token_id: BytesN<32>) -> Option { env.storage() .persistent() @@ -21,7 +58,7 @@ pub fn set_flow_limit( flow_limit: Option, ) -> Result<(), ContractError> { if let Some(limit) = flow_limit { - ensure!(limit > 0, ContractError::InvalidFlowLimit); + ensure!(limit >= 0, ContractError::InvalidFlowLimit); } env.storage() @@ -38,26 +75,49 @@ pub fn set_flow_limit( } pub fn flow_out_amount(env: &Env, token_id: BytesN<32>) -> i128 { - let epoch = env.ledger().timestamp() / EPOCH_TIME; env.storage() .temporary() - .get(&DataKey::FlowOut(token_id, epoch)) + .get(&DataKey::FlowOut(FlowKey { + token_id, + epoch: current_epoch(env), + })) .unwrap_or(0) } pub fn flow_in_amount(env: &Env, token_id: BytesN<32>) -> i128 { - let epoch = env.ledger().timestamp() / EPOCH_TIME; env.storage() .temporary() - .get(&DataKey::FlowIn(token_id, epoch)) + .get(&DataKey::FlowIn(FlowKey { + token_id, + epoch: current_epoch(env), + })) .unwrap_or(0) } -enum FlowDirection { - In, - Out, +pub fn add_flow_in( + env: &Env, + token_id: BytesN<32>, + flow_amount: i128, +) -> Result<(), ContractError> { + add_flow(env, token_id, flow_amount, FlowDirection::In) +} + +pub fn add_flow_out( + env: &Env, + token_id: BytesN<32>, + flow_amount: i128, +) -> Result<(), ContractError> { + add_flow(env, token_id, flow_amount, FlowDirection::Out) } +/// Adds flow amount in the specified direction (in/out) for a token. +/// Flow amounts are stored in temporary storage since they only need to persist for +/// the 6-hour epoch duration. +/// +/// Checks that: +/// - Flow amount doesn't exceed the flow limit +/// - Adding flows won't cause overflow +/// - Total flow in one direction doesn't exceed flow in opposite direction plus limit fn add_flow( env: &Env, token_id: BytesN<32>, @@ -68,25 +128,8 @@ fn add_flow( return Ok(()); }; - let epoch = env.ledger().timestamp() / EPOCH_TIME; - - let (flow_to_add_key, flow_to_compare_key) = match direction { - FlowDirection::In => ( - DataKey::FlowIn(token_id.clone(), epoch), - DataKey::FlowOut(token_id.clone(), epoch), - ), - FlowDirection::Out => ( - DataKey::FlowOut(token_id.clone(), epoch), - DataKey::FlowIn(token_id.clone(), epoch), - ), - }; - - let flow_to_add: i128 = env.storage().temporary().get(&flow_to_add_key).unwrap_or(0); - let flow_to_compare: i128 = env - .storage() - .temporary() - .get(&flow_to_compare_key) - .unwrap_or(0); + let flow_to_add = direction.flow(env, token_id.clone()); + let flow_to_compare = direction.reverse_flow(env, token_id.clone()); ensure!(flow_amount <= flow_limit, ContractError::FlowLimitExceeded); @@ -99,25 +142,9 @@ fn add_flow( ensure!(new_flow <= max_allowed, ContractError::FlowLimitExceeded); - env.storage().temporary().set(&flow_to_add_key, &new_flow); + direction.update_flow(env, token_id.clone(), new_flow); extend_persistent_ttl(env, &DataKey::FlowLimit(token_id)); Ok(()) } - -pub fn add_flow_in( - env: &Env, - token_id: BytesN<32>, - flow_amount: i128, -) -> Result<(), ContractError> { - add_flow(env, token_id, flow_amount, FlowDirection::In) -} - -pub fn add_flow_out( - env: &Env, - token_id: BytesN<32>, - flow_amount: i128, -) -> Result<(), ContractError> { - add_flow(env, token_id, flow_amount, FlowDirection::Out) -} diff --git a/contracts/interchain-token-service/src/interface.rs b/contracts/interchain-token-service/src/interface.rs index 829c75d4..d83ae0e0 100644 --- a/contracts/interchain-token-service/src/interface.rs +++ b/contracts/interchain-token-service/src/interface.rs @@ -36,16 +36,16 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface { fn flow_limit(env: &Env, token_id: BytesN<32>) -> Option; + fn flow_out_amount(env: &Env, token_id: BytesN<32>) -> i128; + + fn flow_in_amount(env: &Env, token_id: BytesN<32>) -> i128; + fn set_flow_limit( env: &Env, token_id: BytesN<32>, flow_limit: Option, ) -> Result<(), ContractError>; - fn flow_out_amount(env: &Env, token_id: BytesN<32>) -> i128; - - fn flow_in_amount(env: &Env, token_id: BytesN<32>) -> i128; - fn deploy_interchain_token( env: &Env, deployer: Address, diff --git a/contracts/interchain-token-service/src/storage_types.rs b/contracts/interchain-token-service/src/storage_types.rs index b93be66c..75915246 100644 --- a/contracts/interchain-token-service/src/storage_types.rs +++ b/contracts/interchain-token-service/src/storage_types.rs @@ -13,8 +13,8 @@ pub enum DataKey { InterchainTokenWasmHash, TokenIdConfigKey(BytesN<32>), FlowLimit(BytesN<32>), - FlowOut(BytesN<32>, u64), - FlowIn(BytesN<32>, u64), + FlowOut(FlowKey), + FlowIn(FlowKey), } #[contracttype] @@ -23,3 +23,10 @@ pub struct TokenIdConfigValue { pub token_address: Address, pub token_manager_type: TokenManagerType, } + +#[contracttype] +#[derive(Clone, Debug)] +pub struct FlowKey { + pub token_id: BytesN<32>, + pub epoch: u64, +} diff --git a/contracts/interchain-token-service/tests/flow_limit.rs b/contracts/interchain-token-service/tests/flow_limit.rs index 7a85e0ca..d2febd2c 100644 --- a/contracts/interchain-token-service/tests/flow_limit.rs +++ b/contracts/interchain-token-service/tests/flow_limit.rs @@ -23,8 +23,9 @@ fn setup_flow_limit(env: &Env, client: &InterchainTokenServiceClient) -> (BytesN let deployer = Address::generate(env); let token_id = setup_its_token(env, client, &deployer, supply); - env.mock_all_auths(); - client.set_flow_limit(&token_id, &TEST_FLOW_LIMIT); + client + .mock_all_auths() + .set_flow_limit(&token_id, &TEST_FLOW_LIMIT); (token_id, deployer) } @@ -86,6 +87,23 @@ fn set_flow_limit_succeeds() { goldie::assert!(events::fmt_last_emitted_event::(&env)); } +#[test] +#[should_panic(expected = "Error(Contract, #20)")] // FlowLimitExceeded +fn zero_flow_limit_freezes_token() { + let (env, client, gateway_client, _, signers) = setup_env(); + register_chains(&env, &client); + let (token_id, _) = setup_flow_limit(&env, &client); + + client.mock_all_auths().set_flow_limit(&token_id, &Some(0)); + + let amount = 1; + let (source_chain, message_id, source_address, payload, messages) = + create_interchain_transfer_message(&env, &client, &token_id, amount); + approve_gateway_messages(&env, &gateway_client, signers, messages); + + client.execute(&source_chain, &message_id, &source_address, &payload); +} + #[test] fn set_flow_limit_fails_invalid_amount() { let (env, client, _, _, _) = setup_env(); @@ -93,10 +111,10 @@ fn set_flow_limit_fails_invalid_amount() { let invalid_limit = Some(-1); - env.mock_all_auths(); - assert_contract_err!( - client.try_set_flow_limit(&token_id, &invalid_limit), + client + .mock_all_auths() + .try_set_flow_limit(&token_id, &invalid_limit), ContractError::InvalidFlowLimit ); } @@ -242,8 +260,9 @@ fn add_flow_fails_on_flow_comparison_overflow() { let (token_id, sender) = setup_flow_limit(&env, &client); let gas_token = setup_gas_token(&env, &sender); - env.mock_all_auths(); - client.set_flow_limit(&token_id, &Some(i128::MAX - 50)); + client + .mock_all_auths() + .set_flow_limit(&token_id, &Some(i128::MAX - 50)); let high_amount = i128::MAX - 100; let (source_chain, message_id, source_address, payload, messages) = diff --git a/packages/axelar-soroban-std/src/events.rs b/packages/axelar-soroban-std/src/events.rs index 00bcb190..3b1598ed 100644 --- a/packages/axelar-soroban-std/src/events.rs +++ b/packages/axelar-soroban-std/src/events.rs @@ -1,5 +1,5 @@ use core::fmt::Debug; -use soroban_sdk::{Env, IntoVal, Topics, Val}; +use soroban_sdk::{Env, IntoVal, Topics, Val, Vec}; #[cfg(any(test, feature = "testutils"))] pub use testutils::*; @@ -7,7 +7,9 @@ pub use testutils::*; pub trait Event: Debug + PartialEq { fn topics(&self, env: &Env) -> impl Topics + Debug; - fn data(&self, env: &Env) -> impl IntoVal + Debug; + fn data(&self, env: &Env) -> impl IntoVal + Debug { + Vec::::new(env) + } fn emit(&self, env: &Env) { env.events().publish(self.topics(env), self.data(env)); From f4a549af57a45d8cb4c9502523ee370c11316d85 Mon Sep 17 00:00:00 2001 From: Attiss Ngo <92927591+AttissNgo@users.noreply.github.com> Date: Fri, 10 Jan 2025 08:41:32 -0600 Subject: [PATCH 5/7] add newlines for readability --- contracts/interchain-token-service/src/flow_limit.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/interchain-token-service/src/flow_limit.rs b/contracts/interchain-token-service/src/flow_limit.rs index f9274260..a4bc9a6c 100644 --- a/contracts/interchain-token-service/src/flow_limit.rs +++ b/contracts/interchain-token-service/src/flow_limit.rs @@ -34,10 +34,12 @@ impl FlowDirection { token_id, epoch: current_epoch(env), }; + let key = match self { Self::In => DataKey::FlowIn(flow_key), Self::Out => DataKey::FlowOut(flow_key), }; + env.storage().temporary().set(&key, &new_flow); } } From 00bfe8debee11cd5c2af21b1db3d025df7a84c86 Mon Sep 17 00:00:00 2001 From: Attiss Ngo <92927591+AttissNgo@users.noreply.github.com> Date: Mon, 13 Jan 2025 11:29:26 -0600 Subject: [PATCH 6/7] feat(interchain-token-service): add flow limit - encapsulate add flow logic in Flow Direction enum - add comments/docstring where necessary - move entrypoint docstring from contract to interface - refactor imports --- .../interchain-token-service/src/contract.rs | 76 ++++++--------- .../interchain-token-service/src/event.rs | 2 +- .../src/flow_limit.rs | 96 ++++++++----------- .../interchain-token-service/src/interface.rs | 25 +++++ packages/axelar-soroban-std/src/events.rs | 1 + 5 files changed, 97 insertions(+), 103 deletions(-) diff --git a/contracts/interchain-token-service/src/contract.rs b/contracts/interchain-token-service/src/contract.rs index fad26141..b58202de 100644 --- a/contracts/interchain-token-service/src/contract.rs +++ b/contracts/interchain-token-service/src/contract.rs @@ -1,31 +1,39 @@ use axelar_gas_service::AxelarGasServiceClient; use axelar_gateway::{executable::AxelarExecutableInterface, AxelarGatewayMessagingClient}; -use axelar_soroban_std::events::Event; -use axelar_soroban_std::token::validate_token_metadata; -use axelar_soroban_std::ttl::{extend_instance_ttl, extend_persistent_ttl}; use axelar_soroban_std::{ - address::AddressExt, ensure, interfaces, types::Token, Operatable, Ownable, Upgradable, + address::AddressExt, + ensure, + events::Event, + interfaces, + token::validate_token_metadata, + ttl::{extend_instance_ttl, extend_persistent_ttl}, + types::Token, + Operatable, Ownable, Upgradable, }; use interchain_token::InterchainTokenClient; -use soroban_sdk::token::{self, StellarAssetClient}; -use soroban_sdk::xdr::{FromXdr, ToXdr}; -use soroban_sdk::{contract, contractimpl, panic_with_error, Address, Bytes, BytesN, Env, String}; +use soroban_sdk::{ + contract, contractimpl, panic_with_error, + token::{self, StellarAssetClient}, + xdr::{FromXdr, ToXdr}, + Address, Bytes, BytesN, Env, String, +}; use soroban_token_sdk::metadata::TokenMetadata; -use crate::abi::{get_message_type, MessageType as EncodedMessageType}; -use crate::error::ContractError; -use crate::event::{ - InterchainTokenDeployedEvent, InterchainTokenDeploymentStartedEvent, - InterchainTokenIdClaimedEvent, InterchainTransferReceivedEvent, InterchainTransferSentEvent, - TrustedChainRemovedEvent, TrustedChainSetEvent, -}; -use crate::executable::InterchainTokenExecutableClient; -use crate::interface::InterchainTokenServiceInterface; -use crate::storage_types::{DataKey, TokenIdConfigValue}; -use crate::types::{ - DeployInterchainToken, HubMessage, InterchainTransfer, Message, TokenManagerType, +use crate::{ + abi::{get_message_type, MessageType as EncodedMessageType}, + error::ContractError, + event::{ + InterchainTokenDeployedEvent, InterchainTokenDeploymentStartedEvent, + InterchainTokenIdClaimedEvent, InterchainTransferReceivedEvent, + InterchainTransferSentEvent, TrustedChainRemovedEvent, TrustedChainSetEvent, + }, + executable::InterchainTokenExecutableClient, + flow_limit::{self, FlowDirection}, + interface::InterchainTokenServiceInterface, + storage_types::{DataKey, TokenIdConfigValue}, + token_handler, + types::{DeployInterchainToken, HubMessage, InterchainTransfer, Message, TokenManagerType}, }; -use crate::{flow_limit, token_handler}; const ITS_HUB_CHAIN_NAME: &str = "axelar"; const PREFIX_INTERCHAIN_TOKEN_ID: &str = "its-interchain-token-id"; @@ -162,42 +170,18 @@ impl InterchainTokenServiceInterface for InterchainTokenService { .into() } - /// Retrieves the flow limit for the token associated with the specified token ID. - /// Returns None if no limit is set. fn flow_limit(env: &Env, token_id: BytesN<32>) -> Option { flow_limit::flow_limit(env, token_id) } - /// Retrieves the flow out amount for the current epoch for the token - /// associated with the specified token ID. fn flow_out_amount(env: &Env, token_id: BytesN<32>) -> i128 { flow_limit::flow_out_amount(env, token_id) } - /// Retrieves the flow out amount for the current epoch for the token - /// associated with the specified token ID. fn flow_in_amount(env: &Env, token_id: BytesN<32>) -> i128 { flow_limit::flow_in_amount(env, token_id) } - /// Sets or updates the flow limit for a token. - /// - /// Flow limit controls how many tokens can flow in/out during a single epoch. - /// Setting the limit to None disables flow limit checks for the token. - /// Setting the limit to 0 effectively freezes the token by preventing any flow. - /// - /// # Arguments - /// - `env`: Reference to the contract environment. - /// - `token_id`: Unique identifier of the token. - /// - `flow_limit`: The new flow limit value. Must be positive if Some. - /// - /// # Returns - /// - `Result<(), ContractError>`: Ok(()) on success. - /// - /// # Errors - /// - `ContractError::InvalidFlowLimit`: If the provided flow limit is not positive. - /// - /// Authorization: Only the operator can call this function. Unauthorized calls will panic. fn set_flow_limit( env: &Env, token_id: BytesN<32>, @@ -385,7 +369,7 @@ impl InterchainTokenServiceInterface for InterchainTokenService { amount, )?; - flow_limit::add_flow_out(env, token_id.clone(), amount)?; + FlowDirection::Out.add_flow(env, token_id.clone(), amount)?; InterchainTransferSentEvent { token_id: token_id.clone(), @@ -554,7 +538,7 @@ impl InterchainTokenService { let token_config_value = Self::token_id_config_with_extended_ttl(env, token_id.clone())?; - flow_limit::add_flow_in(env, token_id.clone(), amount)?; + FlowDirection::In.add_flow(env, token_id.clone(), amount)?; token_handler::give_token( env, diff --git a/contracts/interchain-token-service/src/event.rs b/contracts/interchain-token-service/src/event.rs index 6358187b..2841d767 100644 --- a/contracts/interchain-token-service/src/event.rs +++ b/contracts/interchain-token-service/src/event.rs @@ -16,7 +16,7 @@ pub struct TrustedChainRemovedEvent { #[derive(Debug, PartialEq, Eq)] pub struct FlowLimitSetEvent { pub token_id: BytesN<32>, - /// Setting to None bypasses flow limit checks + /// A `None` value implies that flow limit checks have been disabled for this `token_id` pub flow_limit: Option, } diff --git a/contracts/interchain-token-service/src/flow_limit.rs b/contracts/interchain-token-service/src/flow_limit.rs index a4bc9a6c..8f177f49 100644 --- a/contracts/interchain-token-service/src/flow_limit.rs +++ b/contracts/interchain-token-service/src/flow_limit.rs @@ -9,8 +9,10 @@ use crate::{ const EPOCH_TIME: u64 = 6 * 60 * 60; // 6 hours in seconds = 21600 -enum FlowDirection { +pub enum FlowDirection { + /// An interchain transfer coming in to this chain from another chain In, + /// An interchain transfer going out from this chain to another chain Out, } @@ -42,6 +44,43 @@ impl FlowDirection { env.storage().temporary().set(&key, &new_flow); } + + /// Adds flow amount in the specified direction (in/out) for a token. + /// Flow amounts are stored in temporary storage since they only need to persist for + /// the 6-hour epoch duration. + /// + /// Checks that: + /// - Flow amount doesn't exceed the flow limit + /// - Adding flows won't cause overflow + /// - Total flow in one direction doesn't exceed flow in opposite direction plus limit + pub fn add_flow( + &self, + env: &Env, + token_id: BytesN<32>, + flow_amount: i128, + ) -> Result<(), ContractError> { + let Some(flow_limit) = flow_limit(env, token_id.clone()) else { + return Ok(()); + }; + + let flow_to_add = self.flow(env, token_id.clone()); + let flow_to_compare = self.reverse_flow(env, token_id.clone()); + + ensure!(flow_amount <= flow_limit, ContractError::FlowLimitExceeded); + let new_flow = flow_to_add + .checked_add(flow_amount) + .ok_or(ContractError::FlowLimitExceeded)?; + let max_allowed = flow_to_compare + .checked_add(flow_limit) + .ok_or(ContractError::FlowLimitExceeded)?; + ensure!(new_flow <= max_allowed, ContractError::FlowLimitExceeded); + + self.update_flow(env, token_id.clone(), new_flow); + + extend_persistent_ttl(env, &DataKey::FlowLimit(token_id)); + + Ok(()) + } } fn current_epoch(env: &Env) -> u64 { @@ -95,58 +134,3 @@ pub fn flow_in_amount(env: &Env, token_id: BytesN<32>) -> i128 { })) .unwrap_or(0) } - -pub fn add_flow_in( - env: &Env, - token_id: BytesN<32>, - flow_amount: i128, -) -> Result<(), ContractError> { - add_flow(env, token_id, flow_amount, FlowDirection::In) -} - -pub fn add_flow_out( - env: &Env, - token_id: BytesN<32>, - flow_amount: i128, -) -> Result<(), ContractError> { - add_flow(env, token_id, flow_amount, FlowDirection::Out) -} - -/// Adds flow amount in the specified direction (in/out) for a token. -/// Flow amounts are stored in temporary storage since they only need to persist for -/// the 6-hour epoch duration. -/// -/// Checks that: -/// - Flow amount doesn't exceed the flow limit -/// - Adding flows won't cause overflow -/// - Total flow in one direction doesn't exceed flow in opposite direction plus limit -fn add_flow( - env: &Env, - token_id: BytesN<32>, - flow_amount: i128, - direction: FlowDirection, -) -> Result<(), ContractError> { - let Some(flow_limit) = flow_limit(env, token_id.clone()) else { - return Ok(()); - }; - - let flow_to_add = direction.flow(env, token_id.clone()); - let flow_to_compare = direction.reverse_flow(env, token_id.clone()); - - ensure!(flow_amount <= flow_limit, ContractError::FlowLimitExceeded); - - let new_flow = flow_to_add - .checked_add(flow_amount) - .ok_or(ContractError::FlowLimitExceeded)?; - let max_allowed = flow_to_compare - .checked_add(flow_limit) - .ok_or(ContractError::FlowLimitExceeded)?; - - ensure!(new_flow <= max_allowed, ContractError::FlowLimitExceeded); - - direction.update_flow(env, token_id.clone(), new_flow); - - extend_persistent_ttl(env, &DataKey::FlowLimit(token_id)); - - Ok(()) -} diff --git a/contracts/interchain-token-service/src/interface.rs b/contracts/interchain-token-service/src/interface.rs index d83ae0e0..08443191 100644 --- a/contracts/interchain-token-service/src/interface.rs +++ b/contracts/interchain-token-service/src/interface.rs @@ -34,12 +34,37 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface { fn token_manager_type(env: &Env, token_id: BytesN<32>) -> TokenManagerType; + /// Retrieves the flow limit for the token associated with the specified token ID. + /// Returns `None` if no limit is set. fn flow_limit(env: &Env, token_id: BytesN<32>) -> Option; + /// Retrieves the amount that has flowed out of the contract during the current epoch + /// for the token associated with the specified token ID. fn flow_out_amount(env: &Env, token_id: BytesN<32>) -> i128; + /// Retrieves the amount that has flowed into the contract during the current epoch + /// for the token associated with the specified token ID. fn flow_in_amount(env: &Env, token_id: BytesN<32>) -> i128; + /// Sets or updates the flow limit for a token. + /// + /// Flow limit controls how many tokens can flow in/out during a single epoch. + /// Setting the limit to `None` disables flow limit checks for the token. + /// Setting the limit to 0 effectively freezes the token by preventing any flow. + /// + /// # Arguments + /// - `env`: Reference to the contract environment. + /// - `token_id`: Unique identifier of the token. + /// - `flow_limit`: The new flow limit value. Must be positive if Some. + /// + /// # Returns + /// - `Result<(), ContractError>`: Ok(()) on success. + /// + /// # Errors + /// - `ContractError::InvalidFlowLimit`: If the provided flow limit is not positive. + /// + /// # Authorization + /// - Must be called by the [`Self::operator`]. fn set_flow_limit( env: &Env, token_id: BytesN<32>, diff --git a/packages/axelar-soroban-std/src/events.rs b/packages/axelar-soroban-std/src/events.rs index 3b1598ed..175a4215 100644 --- a/packages/axelar-soroban-std/src/events.rs +++ b/packages/axelar-soroban-std/src/events.rs @@ -7,6 +7,7 @@ pub use testutils::*; pub trait Event: Debug + PartialEq { fn topics(&self, env: &Env) -> impl Topics + Debug; + /// A default empty tuple/vector is used for event data, since majority of events only use topics. fn data(&self, env: &Env) -> impl IntoVal + Debug { Vec::::new(env) } From a90af894c463f32783b075bdf7e52e4edc5bd367 Mon Sep 17 00:00:00 2001 From: Attiss Ngo <92927591+AttissNgo@users.noreply.github.com> Date: Mon, 13 Jan 2025 20:24:27 -0600 Subject: [PATCH 7/7] feat(interchain-token-service): add flow limit - improve docstring describing flow direction - remove unneeded variables in add_flow --- .../interchain-token-service/src/error.rs | 1 + .../interchain-token-service/src/flow_limit.rs | 18 ++++++++++-------- .../interchain-token-service/src/interface.rs | 5 ++--- .../tests/flow_limit.rs | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/contracts/interchain-token-service/src/error.rs b/contracts/interchain-token-service/src/error.rs index c7db7224..2d18f11a 100644 --- a/contracts/interchain-token-service/src/error.rs +++ b/contracts/interchain-token-service/src/error.rs @@ -24,4 +24,5 @@ pub enum ContractError { TokenAlreadyDeployed = 18, InvalidFlowLimit = 19, FlowLimitExceeded = 20, + FlowAmountOverflow = 21, } diff --git a/contracts/interchain-token-service/src/flow_limit.rs b/contracts/interchain-token-service/src/flow_limit.rs index 8f177f49..84d8e33b 100644 --- a/contracts/interchain-token-service/src/flow_limit.rs +++ b/contracts/interchain-token-service/src/flow_limit.rs @@ -52,7 +52,7 @@ impl FlowDirection { /// Checks that: /// - Flow amount doesn't exceed the flow limit /// - Adding flows won't cause overflow - /// - Total flow in one direction doesn't exceed flow in opposite direction plus limit + /// - Net flow (outgoing minus incoming flow) doesn't exceed the limit pub fn add_flow( &self, env: &Env, @@ -63,16 +63,18 @@ impl FlowDirection { return Ok(()); }; - let flow_to_add = self.flow(env, token_id.clone()); - let flow_to_compare = self.reverse_flow(env, token_id.clone()); - ensure!(flow_amount <= flow_limit, ContractError::FlowLimitExceeded); - let new_flow = flow_to_add + + let new_flow = self + .flow(env, token_id.clone()) .checked_add(flow_amount) - .ok_or(ContractError::FlowLimitExceeded)?; - let max_allowed = flow_to_compare + .ok_or(ContractError::FlowAmountOverflow)?; + let max_allowed = self + .reverse_flow(env, token_id.clone()) .checked_add(flow_limit) - .ok_or(ContractError::FlowLimitExceeded)?; + .ok_or(ContractError::FlowAmountOverflow)?; + + // Equivalent to flow_amount + flow - reverse_flow <= flow_limit ensure!(new_flow <= max_allowed, ContractError::FlowLimitExceeded); self.update_flow(env, token_id.clone(), new_flow); diff --git a/contracts/interchain-token-service/src/interface.rs b/contracts/interchain-token-service/src/interface.rs index 08443191..01e6c711 100644 --- a/contracts/interchain-token-service/src/interface.rs +++ b/contracts/interchain-token-service/src/interface.rs @@ -38,11 +38,11 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface { /// Returns `None` if no limit is set. fn flow_limit(env: &Env, token_id: BytesN<32>) -> Option; - /// Retrieves the amount that has flowed out of the contract during the current epoch + /// Retrieves the amount that has flowed out of the chain to other chains during the current epoch /// for the token associated with the specified token ID. fn flow_out_amount(env: &Env, token_id: BytesN<32>) -> i128; - /// Retrieves the amount that has flowed into the contract during the current epoch + /// Retrieves the amount that has flowed into the chain from other chains during the current epoch /// for the token associated with the specified token ID. fn flow_in_amount(env: &Env, token_id: BytesN<32>) -> i128; @@ -53,7 +53,6 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface { /// Setting the limit to 0 effectively freezes the token by preventing any flow. /// /// # Arguments - /// - `env`: Reference to the contract environment. /// - `token_id`: Unique identifier of the token. /// - `flow_limit`: The new flow limit value. Must be positive if Some. /// diff --git a/contracts/interchain-token-service/tests/flow_limit.rs b/contracts/interchain-token-service/tests/flow_limit.rs index d2febd2c..1882f761 100644 --- a/contracts/interchain-token-service/tests/flow_limit.rs +++ b/contracts/interchain-token-service/tests/flow_limit.rs @@ -253,7 +253,7 @@ fn add_flow_out_fails_exceeds_flow_limit() { } #[test] -#[should_panic(expected = "Error(Contract, #20)")] // ContractError::FlowLimitExceeded +#[should_panic(expected = "Error(Contract, #21)")] // ContractError::FlowAmountOverflow fn add_flow_fails_on_flow_comparison_overflow() { let (env, client, gateway_client, _, signers) = setup_env(); register_chains(&env, &client);