diff --git a/contracts/interchain-token-service/src/contract.rs b/contracts/interchain-token-service/src/contract.rs index d45e4d72..45b36e13 100644 --- a/contracts/interchain-token-service/src/contract.rs +++ b/contracts/interchain-token-service/src/contract.rs @@ -1,5 +1,5 @@ -use soroban_sdk::token::{self, StellarAssetClient}; -use soroban_sdk::xdr::ToXdr; +use soroban_sdk::token::StellarAssetClient; +use soroban_sdk::xdr::{FromXdr, ToXdr}; use soroban_sdk::{contract, contractimpl, Address, Bytes, BytesN, Env, String}; use soroban_token_sdk::metadata::TokenMetadata; use stellar_axelar_gas_service::AxelarGasServiceClient; @@ -7,13 +7,11 @@ use stellar_axelar_gateway::executable::AxelarExecutableInterface; use stellar_axelar_gateway::AxelarGatewayMessagingClient; use stellar_axelar_std::address::AddressExt; use stellar_axelar_std::events::Event; -use stellar_axelar_std::token::validate_token_metadata; use stellar_axelar_std::ttl::{extend_instance_ttl, extend_persistent_ttl}; use stellar_axelar_std::types::Token; use stellar_axelar_std::{ensure, interfaces, Operatable, Ownable, Upgradable}; use stellar_interchain_token::InterchainTokenClient; -use crate::abi::{get_message_type, MessageType as EncodedMessageType}; use crate::error::ContractError; use crate::event::{ InterchainTokenDeployedEvent, InterchainTokenDeploymentStartedEvent, @@ -24,10 +22,11 @@ use crate::executable::InterchainTokenExecutableClient; use crate::flow_limit::FlowDirection; use crate::interface::InterchainTokenServiceInterface; use crate::storage_types::{DataKey, TokenIdConfigValue}; +use crate::token_metadata::TokenMetadataExt; use crate::types::{ DeployInterchainToken, HubMessage, InterchainTransfer, Message, TokenManagerType, }; -use crate::{flow_limit, token_handler}; +use crate::{flow_limit, token_handler, token_metadata}; const ITS_HUB_CHAIN_NAME: &str = "axelar"; const PREFIX_INTERCHAIN_TOKEN_ID: &str = "its-interchain-token-id"; @@ -48,6 +47,7 @@ impl InterchainTokenService { gas_service: Address, its_hub_address: String, chain_name: String, + native_token_address: Address, interchain_token_wasm_hash: BytesN<32>, ) { interfaces::set_owner(&env, &owner); @@ -62,6 +62,9 @@ impl InterchainTokenService { env.storage() .instance() .set(&DataKey::ChainName, &chain_name); + env.storage() + .instance() + .set(&DataKey::NativeTokenAddress, &native_token_address); env.storage().instance().set( &DataKey::InterchainTokenWasmHash, &interchain_token_wasm_hash, @@ -71,13 +74,6 @@ impl InterchainTokenService { #[contractimpl] impl InterchainTokenServiceInterface for InterchainTokenService { - fn chain_name(env: &Env) -> String { - env.storage() - .instance() - .get(&DataKey::ChainName) - .expect("chain name not found") - } - fn gas_service(env: &Env) -> Address { env.storage() .instance() @@ -85,11 +81,15 @@ impl InterchainTokenServiceInterface for InterchainTokenService { .expect("gas service not found") } - fn interchain_token_wasm_hash(env: &Env) -> BytesN<32> { + fn chain_name(env: &Env) -> String { env.storage() .instance() - .get(&DataKey::InterchainTokenWasmHash) - .expect("interchain token wasm hash not found") + .get(&DataKey::ChainName) + .expect("chain name not found") + } + + fn its_hub_chain_name(env: &Env) -> String { + String::from_str(env, ITS_HUB_CHAIN_NAME) } fn its_hub_address(env: &Env) -> String { @@ -99,8 +99,18 @@ impl InterchainTokenServiceInterface for InterchainTokenService { .expect("its hub address not found") } - fn its_hub_chain_name(env: &Env) -> String { - String::from_str(env, ITS_HUB_CHAIN_NAME) + fn native_token_address(env: &Env) -> Address { + env.storage() + .instance() + .get(&DataKey::NativeTokenAddress) + .expect("native token address not found") + } + + fn interchain_token_wasm_hash(env: &Env) -> BytesN<32> { + env.storage() + .instance() + .get(&DataKey::InterchainTokenWasmHash) + .expect("interchain token wasm hash not found") } fn is_trusted_chain(env: &Env, chain: String) -> bool { @@ -230,6 +240,8 @@ impl InterchainTokenServiceInterface for InterchainTokenService { let deploy_salt = Self::interchain_token_deploy_salt(env, caller.clone(), salt); let token_id = Self::interchain_token_id(env, Address::zero(env), deploy_salt); + token_metadata.validate()?; + let deployed_address = Self::deploy_interchain_token_contract( env, initial_minter, @@ -457,101 +469,18 @@ impl InterchainTokenService { env: &Env, source_chain: String, message_id: String, - _source_address: String, + source_address: String, payload: Bytes, ) -> Result<(), ContractError> { - let (source_chain, message) = Self::get_execute_params(env, source_chain, &payload)?; + let (source_chain, message) = + Self::get_execute_params(env, source_chain, source_address, payload)?; match message { - Message::InterchainTransfer(InterchainTransfer { - token_id, - source_address, - destination_address, - amount, - data, - }) => { - let destination_address = Address::from_string_bytes(&destination_address); - let token_config_value = - Self::token_id_config_with_extended_ttl(env, token_id.clone())?; - - FlowDirection::In.add_flow(env, token_id.clone(), amount)?; - - token_handler::give_token( - env, - &destination_address, - token_config_value.clone(), - amount, - )?; - - InterchainTransferReceivedEvent { - source_chain: source_chain.clone(), - token_id: token_id.clone(), - source_address: source_address.clone(), - destination_address: destination_address.clone(), - amount, - data: data.clone(), - } - .emit(env); - - let token_address = token_config_value.token_address; - - if let Some(payload) = data { - let executable = - InterchainTokenExecutableClient::new(env, &destination_address); - executable.execute_with_interchain_token( - &source_chain, - &message_id, - &source_address, - &payload, - &token_id, - &token_address, - &amount, - ); - } + Message::InterchainTransfer(message) => { + Self::execute_transfer_message(env, &source_chain, message_id, message) } - Message::DeployInterchainToken(DeployInterchainToken { - token_id, - name, - symbol, - decimals, - minter, - }) => { - ensure!( - Self::token_id_config(env, token_id.clone()).is_err(), - ContractError::TokenAlreadyDeployed - ); - - let token_metadata = TokenMetadata { - name, - symbol, - decimal: decimals as u32, - }; - - ensure!( - validate_token_metadata(&token_metadata).is_ok(), - ContractError::InvalidTokenMetaData - ); - - // Note: converting a byte string which doesn't represent a valid Soroban address fails at the Host level - let minter = minter.map(|m| Address::from_string_bytes(&m)); - - let deployed_address = Self::deploy_interchain_token_contract( - env, - minter, - token_id.clone(), - token_metadata, - ); - - Self::set_token_id_config( - env, - token_id, - TokenIdConfigValue { - token_address: deployed_address, - token_manager_type: TokenManagerType::NativeInterchainToken, - }, - ); - } - }; + Message::DeployInterchainToken(message) => Self::execute_deploy_message(env, message), + }?; extend_persistent_ttl(env, &DataKey::TrustedChain(source_chain)); extend_instance_ttl(env); @@ -559,39 +488,35 @@ impl InterchainTokenService { Ok(()) } + /// Validate that the message is coming from the ITS Hub and decode the message fn get_execute_params( env: &Env, source_chain: String, - payload: &Bytes, + source_address: String, + payload: Bytes, ) -> Result<(String, Message), ContractError> { - let message_type = get_message_type(&payload.to_alloc_vec())?; - ensure!( - message_type == EncodedMessageType::ReceiveFromHub, - ContractError::InvalidMessageType + source_chain == Self::its_hub_chain_name(env), + ContractError::NotHubChain ); - ensure!( - source_chain == Self::its_hub_chain_name(env), - ContractError::InvalidHubChain + source_address == Self::its_hub_address(env), + ContractError::NotHubAddress ); - let decoded_message = HubMessage::abi_decode(env, payload)?; - let HubMessage::ReceiveFromHub { source_chain: original_source_chain, - message: inner_message, - } = decoded_message + message, + } = HubMessage::abi_decode(env, &payload)? else { return Err(ContractError::InvalidMessageType); }; - ensure!( Self::is_trusted_chain(env, original_source_chain.clone()), ContractError::UntrustedChain ); - Ok((original_source_chain, inner_message)) + Ok((original_source_chain, message)) } fn set_token_id_config(env: &Env, token_id: BytesN<32>, token_data: TokenIdConfigValue) { @@ -675,23 +600,17 @@ impl InterchainTokenService { let token_id = Self::interchain_token_id(env, Address::zero(env), deploy_salt); let token_address = Self::token_id_config(env, token_id.clone())?.token_address; - let token = token::Client::new(env, &token_address); - let token_metadata = TokenMetadata { - name: token.name(), - decimal: token.decimals(), - symbol: token.symbol(), - }; - - ensure!( - validate_token_metadata(&token_metadata).is_ok(), - ContractError::InvalidTokenMetaData - ); + let TokenMetadata { + name, + symbol, + decimal, + } = token_metadata::token_metadata(env, &token_address, &Self::native_token_address(env))?; let message = Message::DeployInterchainToken(DeployInterchainToken { token_id: token_id.clone(), - name: token_metadata.name.clone(), - symbol: token_metadata.symbol.clone(), - decimals: token_metadata.decimal as u8, + name: name.clone(), + symbol: symbol.clone(), + decimals: decimal as u8, minter: None, }); @@ -699,9 +618,9 @@ impl InterchainTokenService { token_id: token_id.clone(), token_address, destination_chain: destination_chain.clone(), - name: token_metadata.name, - symbol: token_metadata.symbol, - decimals: token_metadata.decimal, + name, + symbol, + decimals: decimal, minter: None, } .emit(env); @@ -742,4 +661,93 @@ impl InterchainTokenService { deployed_address } + + fn execute_transfer_message( + env: &Env, + source_chain: &String, + message_id: String, + InterchainTransfer { + token_id, + source_address, + destination_address, + amount, + data, + }: InterchainTransfer, + ) -> Result<(), ContractError> { + let destination_address = Address::from_string_bytes(&destination_address); + + let token_config_value = Self::token_id_config_with_extended_ttl(env, token_id.clone())?; + + FlowDirection::In.add_flow(env, token_id.clone(), amount)?; + + token_handler::give_token( + env, + &destination_address, + token_config_value.clone(), + amount, + )?; + + InterchainTransferReceivedEvent { + source_chain: source_chain.clone(), + token_id: token_id.clone(), + source_address: source_address.clone(), + destination_address: destination_address.clone(), + amount, + data: data.clone(), + } + .emit(env); + + let token_address = token_config_value.token_address; + + if let Some(payload) = data { + let executable = InterchainTokenExecutableClient::new(env, &destination_address); + executable.execute_with_interchain_token( + source_chain, + &message_id, + &source_address, + &payload, + &token_id, + &token_address, + &amount, + ); + } + + Ok(()) + } + + fn execute_deploy_message( + env: &Env, + DeployInterchainToken { + token_id, + name, + symbol, + decimals, + minter, + }: DeployInterchainToken, + ) -> Result<(), ContractError> { + ensure!( + Self::token_id_config(env, token_id.clone()).is_err(), + ContractError::TokenAlreadyDeployed + ); + + let token_metadata = TokenMetadata::new(name, symbol, decimals as u32)?; + + // Note: attempt to convert a byte string which doesn't represent a valid Soroban address fails at the Host level + let minter = minter + .map(|m| Address::from_string_bytes(&m)); + + let deployed_address = + Self::deploy_interchain_token_contract(env, minter, token_id.clone(), token_metadata); + + Self::set_token_id_config( + env, + token_id, + TokenIdConfigValue { + token_address: deployed_address, + token_manager_type: TokenManagerType::NativeInterchainToken, + }, + ); + + Ok(()) + } } diff --git a/contracts/interchain-token-service/src/error.rs b/contracts/interchain-token-service/src/error.rs index ed82ffc2..ed368d98 100644 --- a/contracts/interchain-token-service/src/error.rs +++ b/contracts/interchain-token-service/src/error.rs @@ -19,8 +19,8 @@ pub enum ContractError { InvalidUtf8 = 11, InvalidMinter = 12, InvalidDestinationAddress = 13, - InvalidHubChain = 14, - TokenAlreadyRegistered = 15, + NotHubChain = 14, + NotHubAddress = 15, InvalidTokenMetaData = 16, InvalidTokenId = 17, TokenAlreadyDeployed = 18, @@ -30,6 +30,10 @@ pub enum ContractError { NotApproved = 22, InvalidDestinationChain = 23, InvalidData = 24, + InvalidTokenName = 25, + InvalidTokenSymbol = 26, + InvalidTokenDecimals = 27, + TokenAlreadyRegistered = 28, } impl_not_approved_error!(ContractError); diff --git a/contracts/interchain-token-service/src/flow_limit.rs b/contracts/interchain-token-service/src/flow_limit.rs index da2446cc..833cddbb 100644 --- a/contracts/interchain-token-service/src/flow_limit.rs +++ b/contracts/interchain-token-service/src/flow_limit.rs @@ -100,14 +100,18 @@ pub fn set_flow_limit( token_id: BytesN<32>, flow_limit: Option, ) -> Result<(), ContractError> { - if let Some(limit) = flow_limit { - ensure!(limit >= 0, ContractError::InvalidFlowLimit); + if let Some(flow_limit) = flow_limit { + ensure!(flow_limit >= 0, ContractError::InvalidFlowLimit); + + env.storage() + .persistent() + .set(&DataKey::FlowLimit(token_id.clone()), &flow_limit); + } else { + env.storage() + .persistent() + .remove(&DataKey::FlowLimit(token_id.clone())); } - env.storage() - .persistent() - .set(&DataKey::FlowLimit(token_id.clone()), &flow_limit); - FlowLimitSetEvent { token_id, flow_limit, diff --git a/contracts/interchain-token-service/src/interface.rs b/contracts/interchain-token-service/src/interface.rs index b51d354e..259fe71e 100644 --- a/contracts/interchain-token-service/src/interface.rs +++ b/contracts/interchain-token-service/src/interface.rs @@ -9,20 +9,23 @@ use crate::types::TokenManagerType; #[allow(dead_code)] #[contractclient(name = "InterchainTokenServiceClient")] pub trait InterchainTokenServiceInterface: AxelarExecutableInterface { - /// Returns the name of the current chain. - fn chain_name(env: &Env) -> String; - /// Returns the address of the Gas Service contract. fn gas_service(env: &Env) -> Address; - /// Returns the WASM hash of the token contract used for deploying interchain tokens. - fn interchain_token_wasm_hash(env: &Env) -> BytesN<32>; + /// Returns the name of the current chain. + fn chain_name(env: &Env) -> String; + + /// Returns the name of the chain on which the ITS Hub is deployed. + fn its_hub_chain_name(env: &Env) -> String; /// Returns the address of the ITS Hub. fn its_hub_address(env: &Env) -> String; - /// Returns the name of the chain on which the ITS Hub is deployed. - fn its_hub_chain_name(env: &Env) -> String; + /// Returns the address of the native token on the current chain. + fn native_token_address(env: &Env) -> Address; + + /// Returns the WASM hash of the token contract used for deploying interchain tokens. + fn interchain_token_wasm_hash(env: &Env) -> BytesN<32>; /// Returns whether the specified chain is trusted for cross-chain messaging. fn is_trusted_chain(env: &Env, chain: String) -> bool; @@ -129,7 +132,7 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface { /// - `ContractError::InvalidMinter`: If the minter address is invalid. /// /// # Authorization - /// - The caller must authenticate. + /// - The `deployer` must authenticate. fn deploy_interchain_token( env: &Env, deployer: Address, @@ -155,7 +158,7 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface { /// - Any error propagated from `pay_gas_and_call_contract`. /// /// # Authorization - /// - The caller must authenticate. + /// - The `caller` must authenticate. fn deploy_remote_interchain_token( env: &Env, caller: Address, @@ -182,6 +185,8 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface { /// Deploys a remote canonical token on a specified destination chain. /// /// Anyone can call this to deploy a trustless canonical representation of the token to any trusted destination chain. + /// If the token name is longer than 32 characters, the symbol will be used as the name. + /// Specifically, natively issued Stellar assets will be deployed with the symbol as the name. /// /// # Arguments /// * `token_address` - The address of the token to be deployed. @@ -197,7 +202,7 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface { /// - Any error propagated from `pay_gas_and_call_contract`. /// /// # Authorization - /// - Gas Service requires authorization for spender. + /// - `spender` needs to authorize `pay_gas` call to the gas service. fn deploy_remote_canonical_token( env: &Env, token_address: Address, @@ -233,7 +238,7 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface { /// - Any error propagated from `pay_gas_and_call_contract`. /// /// # Authorization - /// - The caller must authenticate. + /// - The `caller` must authenticate. fn interchain_transfer( env: &Env, caller: Address, diff --git a/contracts/interchain-token-service/src/lib.rs b/contracts/interchain-token-service/src/lib.rs index 5d3f241b..c285da87 100644 --- a/contracts/interchain-token-service/src/lib.rs +++ b/contracts/interchain-token-service/src/lib.rs @@ -15,6 +15,7 @@ cfg_if::cfg_if! { mod abi; pub mod event; mod storage_types; + mod token_metadata; mod token_handler; mod contract; mod flow_limit; diff --git a/contracts/interchain-token-service/src/storage_types.rs b/contracts/interchain-token-service/src/storage_types.rs index 75915246..b133c72e 100644 --- a/contracts/interchain-token-service/src/storage_types.rs +++ b/contracts/interchain-token-service/src/storage_types.rs @@ -5,12 +5,13 @@ use crate::types::TokenManagerType; #[contracttype] #[derive(Clone, Debug)] pub enum DataKey { - TrustedChain(String), Gateway, GasService, - ItsHubAddress, ChainName, + ItsHubAddress, + NativeTokenAddress, InterchainTokenWasmHash, + TrustedChain(String), TokenIdConfigKey(BytesN<32>), FlowLimit(BytesN<32>), FlowOut(FlowKey), diff --git a/contracts/interchain-token-service/src/token_metadata.rs b/contracts/interchain-token-service/src/token_metadata.rs new file mode 100644 index 00000000..7fe2527a --- /dev/null +++ b/contracts/interchain-token-service/src/token_metadata.rs @@ -0,0 +1,74 @@ +use soroban_sdk::{token, Address, Env, String}; +use soroban_token_sdk::metadata::TokenMetadata; +use stellar_axelar_std::ensure; + +use crate::error::ContractError; + +const NATIVE_TOKEN_NAME: &str = "Stellar"; +const NATIVE_TOKEN_SYMBOL: &str = "XLM"; +const MAX_DECIMALS: u32 = u8::MAX as u32; +const MAX_NAME_LENGTH: u32 = 32; +const MAX_SYMBOL_LENGTH: u32 = 32; + +pub trait TokenMetadataExt: Sized { + fn new(name: String, symbol: String, decimals: u32) -> Result; + + fn validate(&self) -> Result<(), ContractError>; +} + +impl TokenMetadataExt for TokenMetadata { + fn new(name: String, symbol: String, decimals: u32) -> Result { + let token_metadata = Self { + name, + symbol, + decimal: decimals, + }; + + token_metadata.validate()?; + + Ok(token_metadata) + } + + fn validate(&self) -> Result<(), ContractError> { + ensure!( + self.decimal <= MAX_DECIMALS, + ContractError::InvalidTokenDecimals + ); + ensure!( + !self.name.is_empty() && self.name.len() <= MAX_NAME_LENGTH, + ContractError::InvalidTokenName + ); + ensure!( + !self.symbol.is_empty() && self.symbol.len() <= MAX_SYMBOL_LENGTH, + ContractError::InvalidTokenSymbol + ); + + Ok(()) + } +} + +pub fn token_metadata( + env: &Env, + token_address: &Address, + native_token_address: &Address, +) -> Result { + let token = token::Client::new(env, token_address); + let decimals = token.decimals(); + let name = token.name(); + let symbol = token.symbol(); + + // Stellar's native token sets the name and symbol to 'native'. Override it to make it more readable + let (name, symbol) = if token_address == native_token_address { + ( + String::from_str(env, NATIVE_TOKEN_NAME), + String::from_str(env, NATIVE_TOKEN_SYMBOL), + ) + // If the name is longer than 32 characters, use the symbol as the name to avoid a deployment error on the destination chain + } else if name.len() > MAX_NAME_LENGTH { + (symbol.clone(), symbol) + } else { + (name, symbol) + }; + + TokenMetadata::new(name, symbol, decimals) +} diff --git a/contracts/interchain-token-service/tests/deploy_interchain_token.rs b/contracts/interchain-token-service/tests/deploy_interchain_token.rs index e4e8bf5b..ada93b36 100644 --- a/contracts/interchain-token-service/tests/deploy_interchain_token.rs +++ b/contracts/interchain-token-service/tests/deploy_interchain_token.rs @@ -18,7 +18,7 @@ fn deploy_interchain_token_succeeds() { let sender = Address::generate(&env); let minter: Option
= None; let salt = BytesN::<32>::from_array(&env, &[1; 32]); - let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6); + let token_metadata = TokenMetadata::new(&env, "Test", "TEST", 6); let initial_supply = 100; client.mock_all_auths().deploy_interchain_token( @@ -41,7 +41,7 @@ fn deploy_interchain_token_with_initial_supply_no_minter() { let sender = Address::generate(&env); let minter: Option
= None; let salt = BytesN::<32>::from_array(&env, &[1; 32]); - let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6); + let token_metadata = TokenMetadata::new(&env, "Test", "TEST", 6); let initial_supply = 100; let token_id = client.mock_all_auths().deploy_interchain_token( @@ -68,7 +68,7 @@ fn deploy_interchain_token_with_initial_supply_valid_minter() { let sender = Address::generate(&env); let minter = Address::generate(&env); let salt = BytesN::<32>::from_array(&env, &[1; 32]); - let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6); + let token_metadata = TokenMetadata::new(&env, "Test", "TEST", 6); let initial_supply = 100; let token_id = client.deploy_interchain_token( @@ -96,7 +96,7 @@ fn deploy_interchain_token_check_token_id_and_token_manager_type() { let sender = Address::generate(&env); let minter = Address::generate(&env); let salt = BytesN::<32>::from_array(&env, &[1; 32]); - let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6); + let token_metadata = TokenMetadata::new(&env, "Test", "TEST", 6); let initial_supply = 100; let deploy_salt = client.interchain_token_deploy_salt(&sender, &salt); @@ -125,7 +125,7 @@ fn deploy_interchain_token_zero_initial_supply_and_valid_minter() { let sender = Address::generate(&env); let minter = Address::generate(&env); let salt = BytesN::<32>::from_array(&env, &[1; 32]); - let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6); + let token_metadata = TokenMetadata::new(&env, "Test", "TEST", 6); let initial_supply = 0; let token_id = client.deploy_interchain_token( @@ -154,7 +154,7 @@ fn deploy_interchain_token_falis_zero_initial_supply_and_invalid_minter() { let sender = Address::generate(&env); let minter: Option
= Some(client.address.clone()); let salt = BytesN::<32>::from_array(&env, &[1; 32]); - let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6); + let token_metadata = TokenMetadata::new(&env, "Test", "TEST", 6); let initial_supply = 0; assert_contract_err!( @@ -177,7 +177,7 @@ fn deploy_interchain_token_zero_initial_supply_no_minter() { let sender = Address::generate(&env); let minter: Option
= None; let salt = BytesN::<32>::from_array(&env, &[1; 32]); - let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6); + let token_metadata = TokenMetadata::new(&env, "Test", "TEST", 6); let initial_supply = 0; let token_id = @@ -193,36 +193,69 @@ fn deploy_interchain_token_zero_initial_supply_no_minter() { } #[test] -#[should_panic(expected = "HostError: Error(Context, InvalidAction)")] -fn deploy_interchain_token_fails_with_invalid_decimals() { +fn deploy_interchain_token_fails_with_invalid_token_metadata() { let (env, client, _, _, _) = setup_env(); - env.mock_all_auths(); let sender = Address::generate(&env); let minter: Option
= None; let salt = BytesN::<32>::from_array(&env, &[1; 32]); - let invalid_decimals = (u8::MAX) as u32 + 1; - let token_metadata = TokenMetadata::new(&env, "name", "symbol", invalid_decimals); let initial_supply = 0; - client.deploy_interchain_token(&sender, &salt, &token_metadata, &initial_supply, &minter); + let cases = [ + ( + TokenMetadata::new(&env, "", "symbol", 6), + ContractError::InvalidTokenName, + ), + ( + TokenMetadata::new(&env, "A".repeat(33).as_str(), "symbol", 6), + ContractError::InvalidTokenName, + ), + ( + TokenMetadata::new(&env, "name", "", 6), + ContractError::InvalidTokenSymbol, + ), + ( + TokenMetadata::new(&env, "name", "A".repeat(33).as_str(), 6), + ContractError::InvalidTokenSymbol, + ), + ( + TokenMetadata::new(&env, "name", "symbol", (u8::MAX) as u32 + 1), + ContractError::InvalidTokenDecimals, + ), + ( + TokenMetadata::new(&env, "name", "symbol", u32::MAX), + ContractError::InvalidTokenDecimals, + ), + ]; + + for (token_metadata, expected_error) in cases { + assert_contract_err!( + client.mock_all_auths().try_deploy_interchain_token( + &sender, + &salt, + &token_metadata, + &initial_supply, + &minter + ), + expected_error + ); + } } #[test] fn deploy_interchain_token_fails_with_invalid_auth() { let (env, client, _, _, _) = setup_env(); - env.mock_all_auths(); let sender = Address::generate(&env); let user = Address::generate(&env); let minter: Option
= None; let salt = BytesN::<32>::from_array(&env, &[1; 32]); - let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6); + let token_metadata = TokenMetadata::new(&env, "Test", "TEST", 6); let initial_supply = 100; assert_auth_err!( user, - client.deploy_interchain_token(&sender, &salt, &token_metadata, &initial_supply, &minter,) + client.deploy_interchain_token(&sender, &salt, &token_metadata, &initial_supply, &minter) ); } 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 53a6d45c..980d7192 100644 --- a/contracts/interchain-token-service/tests/deploy_remote_canonical_token.rs +++ b/contracts/interchain-token-service/tests/deploy_remote_canonical_token.rs @@ -2,7 +2,7 @@ mod utils; use soroban_sdk::testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation}; use soroban_sdk::token::{self, StellarAssetClient}; -use soroban_sdk::{Address, Bytes, IntoVal, String, Symbol}; +use soroban_sdk::{Address, Bytes, BytesN, IntoVal, String, Symbol}; use soroban_token_sdk::metadata::TokenMetadata; use stellar_axelar_std::address::AddressExt; use stellar_axelar_std::{auth_invocation, events, mock_auth}; @@ -10,7 +10,7 @@ use stellar_interchain_token_service::event::InterchainTokenDeploymentStartedEve use stellar_interchain_token_service::types::{ DeployInterchainToken, HubMessage, Message, TokenManagerType, }; -use utils::{setup_env, setup_gas_token}; +use utils::{setup_env, setup_gas_token, TokenMetadataExt}; #[test] fn deploy_remote_canonical_token_succeeds() { @@ -43,20 +43,13 @@ fn deploy_remote_canonical_token_succeeds() { .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, + name: token.symbol(), + symbol: token.symbol(), + decimals: token.decimals() as u8, minter: None, }); - let payload = HubMessage::SendToHub { destination_chain: destination_chain.clone(), message, @@ -127,6 +120,62 @@ fn deploy_remote_canonical_token_succeeds() { assert_eq!(env.auths(), gas_service_auth); } +#[test] +fn deploy_remote_canonical_token_succeeds_native_token() { + let (env, client, _, _, _) = setup_env(); + let spender = Address::generate(&env); + let gas_token = setup_gas_token(&env, &spender); + let token_address = client.native_token_address(); + let destination_chain = String::from_str(&env, "ethereum"); + + client.register_canonical_token(&token_address); + + client + .mock_all_auths() + .set_trusted_chain(&destination_chain); + client + .mock_all_auths_allowing_non_root_auth() + .deploy_remote_canonical_token(&token_address, &destination_chain, &spender, &gas_token); + + goldie::assert!(events::fmt_emitted_event_at_idx::< + InterchainTokenDeploymentStartedEvent, + >(&env, -4)); +} + +#[test] +fn deploy_remote_canonical_token_succeeds_without_name_truncation() { + let (env, client, _, _, _) = setup_env(); + let spender = Address::generate(&env); + let gas_token = setup_gas_token(&env, &spender); + + let token_metadata = TokenMetadata::new(&env, "name", "symbol", 255); + let initial_supply = 1; + let minter: Option
= None; + let salt = BytesN::<32>::from_array(&env, &[1; 32]); + let token_id = client.mock_all_auths().deploy_interchain_token( + &Address::generate(&env), + &salt, + &token_metadata, + &initial_supply, + &minter, + ); + let token_address = client.token_address(&token_id); + let destination_chain = String::from_str(&env, "ethereum"); + + client.register_canonical_token(&token_address); + + client + .mock_all_auths() + .set_trusted_chain(&destination_chain); + client + .mock_all_auths_allowing_non_root_auth() + .deploy_remote_canonical_token(&token_address, &destination_chain, &spender, &gas_token); + + goldie::assert!(events::fmt_emitted_event_at_idx::< + InterchainTokenDeploymentStartedEvent, + >(&env, -4)); +} + #[test] #[should_panic(expected = "HostError: Error(Storage, MissingValue)")] fn deploy_remote_canonical_token_fail_no_actual_token() { diff --git a/contracts/interchain-token-service/tests/executable.rs b/contracts/interchain-token-service/tests/executable.rs index 2700e604..2b5a10f3 100644 --- a/contracts/interchain-token-service/tests/executable.rs +++ b/contracts/interchain-token-service/tests/executable.rs @@ -8,7 +8,7 @@ use stellar_axelar_std::address::AddressExt; use stellar_axelar_std::traits::BytesExt; use stellar_axelar_std::{assert_auth_err, events}; use stellar_interchain_token_service::types::{HubMessage, InterchainTransfer, Message}; -use utils::{register_chains, setup_env, setup_its_token, HUB_CHAIN}; +use utils::{setup_env, setup_its_token}; mod test { use core::fmt::Debug; @@ -121,22 +121,25 @@ mod test { #[test] fn interchain_transfer_execute_succeeds() { let (env, client, gateway_client, _, signers) = setup_env(); - register_chains(&env, &client); let executable_id = env.register(test::ExecutableContract, (client.address.clone(),)); let sender = Address::generate(&env).to_string_bytes(&env); let source_chain = client.its_hub_chain_name(); - let source_address = Address::generate(&env).to_string(); + let source_address: String = client.its_hub_address(); let amount = 1000; let deployer = Address::generate(&env); let token_id = setup_its_token(&env, &client, &deployer, amount); let data = Bytes::from_hex(&env, "dead"); let destination_address = executable_id.to_string_bytes(&env); + let original_source_chain = String::from_str(&env, "ethereum"); + client + .mock_all_auths() + .set_trusted_chain(&original_source_chain); let msg = HubMessage::ReceiveFromHub { - source_chain: String::from_str(&env, HUB_CHAIN), + source_chain: original_source_chain, message: Message::InterchainTransfer(InterchainTransfer { token_id: token_id.clone(), source_address: sender, diff --git a/contracts/interchain-token-service/tests/execute.rs b/contracts/interchain-token-service/tests/execute.rs index 4f8eb77c..16003c8c 100644 --- a/contracts/interchain-token-service/tests/execute.rs +++ b/contracts/interchain-token-service/tests/execute.rs @@ -1,19 +1,23 @@ mod utils; use soroban_sdk::testutils::Address as _; -use soroban_sdk::{vec, Address, Bytes, BytesN, String}; +use soroban_sdk::xdr::ToXdr; +use soroban_sdk::{vec, Address, Bytes, BytesN, Env, String}; use soroban_token_sdk::metadata::TokenMetadata; use stellar_axelar_gateway::types::Message as GatewayMessage; use stellar_axelar_std::address::AddressExt; -use stellar_axelar_std::events; +use stellar_axelar_std::{assert_contract_err, events}; use stellar_interchain_token::InterchainTokenClient; +use stellar_interchain_token_service::error::ContractError; use stellar_interchain_token_service::event::{ InterchainTokenDeployedEvent, InterchainTransferReceivedEvent, }; use stellar_interchain_token_service::types::{ DeployInterchainToken, HubMessage, InterchainTransfer, Message, TokenManagerType, }; -use utils::{approve_gateway_messages, register_chains, setup_env, setup_its_token, HUB_CHAIN}; +use utils::{ + approve_gateway_messages, register_chains, setup_env, setup_its_token, TokenMetadataExt, +}; #[test] #[should_panic(expected = "Error(Contract, #22)")] // ContractError::NotApproved @@ -33,9 +37,9 @@ fn execute_fails_without_gateway_approval() { fn execute_fails_with_invalid_message() { let (env, client, gateway_client, _, signers) = setup_env(); - let source_chain = client.its_hub_chain_name(); let message_id = String::from_str(&env, "test"); - let source_address = Address::generate(&env).to_string(); + let source_chain = client.its_hub_chain_name(); + let source_address = client.its_hub_address(); let invalid_payload = Bytes::from_array(&env, &[1u8; 16]); let payload_hash: BytesN<32> = env.crypto().keccak256(&invalid_payload).into(); @@ -61,22 +65,81 @@ fn execute_fails_with_invalid_message() { ); } +#[test] +fn execute_fails_with_invalid_source_chain() { + let (env, client, gateway_client, _, signers) = setup_env(); + + let message_id = String::from_str(&env, "test"); + let source_chain = String::from_str(&env, "invalid"); + let source_address = client.its_hub_address(); + let payload = Bytes::new(&env); + let payload_hash: BytesN<32> = env.crypto().keccak256(&payload).into(); + 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, + }, + ]; + + approve_gateway_messages(&env, &gateway_client, signers, messages); + + assert_contract_err!( + client.try_execute(&source_chain, &message_id, &source_address, &payload,), + ContractError::NotHubChain + ); +} + +#[test] +fn execute_fails_with_invalid_source_address() { + let (env, client, gateway_client, _, signers) = setup_env(); + + let message_id = String::from_str(&env, "test"); + let source_chain = client.its_hub_chain_name(); + let source_address = String::from_str(&env, "invalid"); + let payload = Bytes::new(&env); + let payload_hash: BytesN<32> = env.crypto().keccak256(&payload).into(); + 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, + }, + ]; + + approve_gateway_messages(&env, &gateway_client, signers, messages); + + assert_contract_err!( + client.try_execute(&source_chain, &message_id, &source_address, &payload,), + ContractError::NotHubAddress + ); +} + #[test] fn interchain_transfer_message_execute_succeeds() { let (env, client, gateway_client, _, signers) = setup_env(); - register_chains(&env, &client); let sender = Address::generate(&env).to_string_bytes(&env); let recipient = Address::generate(&env).to_string_bytes(&env); let source_chain = client.its_hub_chain_name(); - let source_address = Address::generate(&env).to_string(); + let source_address = client.its_hub_address(); + let original_source_chain = String::from_str(&env, "ethereum"); let amount = 1000; let deployer = Address::generate(&env); let token_id = setup_its_token(&env, &client, &deployer, amount); + client + .mock_all_auths() + .set_trusted_chain(&original_source_chain); let msg = HubMessage::ReceiveFromHub { - source_chain: String::from_str(&env, HUB_CHAIN), + source_chain: original_source_chain, message: Message::InterchainTransfer(InterchainTransfer { token_id, source_address: sender, @@ -85,11 +148,10 @@ fn interchain_transfer_message_execute_succeeds() { data: None, }), }; + let message_id = String::from_str(&env, "test"); let payload = msg.abi_encode(&env).unwrap(); let payload_hash: BytesN<32> = env.crypto().keccak256(&payload).into(); - let message_id = String::from_str(&env, "test"); - let messages = vec![ &env, GatewayMessage { @@ -118,7 +180,7 @@ fn deploy_interchain_token_message_execute_succeeds() { let sender = Address::generate(&env); let sender_bytes = sender.to_string_bytes(&env); let source_chain = client.its_hub_chain_name(); - let source_address = Address::generate(&env).to_string(); + let source_address = client.its_hub_address(); let token_id = BytesN::from_array(&env, &[1u8; 32]); let token_metadata = TokenMetadata { @@ -126,9 +188,13 @@ fn deploy_interchain_token_message_execute_succeeds() { symbol: String::from_str(&env, "TEST"), decimal: 18, }; + let original_source_chain = String::from_str(&env, "ethereum"); + client + .mock_all_auths() + .set_trusted_chain(&original_source_chain); let msg = HubMessage::ReceiveFromHub { - source_chain: String::from_str(&env, HUB_CHAIN), + source_chain: original_source_chain, message: Message::DeployInterchainToken(DeployInterchainToken { token_id: token_id.clone(), name: token_metadata.name.clone(), @@ -172,96 +238,83 @@ fn deploy_interchain_token_message_execute_succeeds() { } #[test] -#[should_panic(expected = "Error(Contract, #16)")] // ContractError::InvalidTokenMetadata -fn deploy_interchain_token_message_execute_fails_empty_token_name() { - let (env, client, gateway_client, _, signers) = setup_env(); - register_chains(&env, &client); - - let source_chain = client.its_hub_chain_name(); - let source_address = Address::generate(&env).to_string(); - let token_id = BytesN::from_array(&env, &[1u8; 32]); - - let msg_empty_name = HubMessage::ReceiveFromHub { - source_chain: String::from_str(&env, HUB_CHAIN), - message: Message::DeployInterchainToken(DeployInterchainToken { - token_id, - name: String::from_str(&env, ""), - symbol: String::from_str(&env, "TEST"), - decimals: 18, - minter: None, - }), - }; - let payload_empty_name = msg_empty_name.abi_encode(&env).unwrap(); - let payload_hash_empty_name: BytesN<32> = env.crypto().keccak256(&payload_empty_name).into(); - - let message_id_empty_name = String::from_str(&env, "no_name"); - - let messages = vec![ - &env, - GatewayMessage { - source_chain: source_chain.clone(), - message_id: message_id_empty_name.clone(), - source_address: source_address.clone(), - contract_address: client.address.clone(), - payload_hash: payload_hash_empty_name, - }, - ]; - - approve_gateway_messages(&env, &gateway_client, signers, messages); - - client.execute( - &source_chain, - &message_id_empty_name, - &source_address, - &payload_empty_name, - ); -} - -#[test] -#[should_panic(expected = "Error(Contract, #16)")] // ContractError::InvalidTokenMetadata -fn deploy_interchain_token_message_execute_fails_empty_token_symbol() { - let (env, client, gateway_client, _, signers) = setup_env(); - register_chains(&env, &client); - - let source_chain = client.its_hub_chain_name(); - let source_address = Address::generate(&env).to_string(); - let token_id = BytesN::from_array(&env, &[1u8; 32]); - - let msg_empty_symbol = HubMessage::ReceiveFromHub { - source_chain: String::from_str(&env, HUB_CHAIN), - message: Message::DeployInterchainToken(DeployInterchainToken { - token_id, - name: String::from_str(&env, "test"), - symbol: String::from_str(&env, ""), - decimals: 18, - minter: None, - }), - }; - let payload_empty_symbol = msg_empty_symbol.abi_encode(&env).unwrap(); - let payload_hash_empty_symbol: BytesN<32> = - env.crypto().keccak256(&payload_empty_symbol).into(); - - let message_id_empty_symbol = String::from_str(&env, "no_symbol"); - - let messages = vec![ - &env, - GatewayMessage { - source_chain: source_chain.clone(), - message_id: message_id_empty_symbol.clone(), - source_address: source_address.clone(), - contract_address: client.address.clone(), - payload_hash: payload_hash_empty_symbol, - }, +fn deploy_interchain_token_message_execute_fails_invalid_token_metadata() { + let env = Env::default(); + + let cases = [ + ( + TokenMetadata::new(&env, "", "symbol", 6), + ContractError::InvalidTokenName, + ), + ( + TokenMetadata::new(&env, "A".repeat(33).as_str(), "symbol", 6), + ContractError::InvalidTokenName, + ), + ( + TokenMetadata::new(&env, "name", "", 6), + ContractError::InvalidTokenSymbol, + ), + ( + TokenMetadata::new(&env, "name", "A".repeat(33).as_str(), 6), + ContractError::InvalidTokenSymbol, + ), ]; - approve_gateway_messages(&env, &gateway_client, signers, messages); - - client.execute( - &source_chain, - &message_id_empty_symbol, - &source_address, - &payload_empty_symbol, - ); + for ( + i, + ( + TokenMetadata { + name, + symbol, + decimal, + }, + expected_error, + ), + ) in cases.into_iter().enumerate() + { + let (env, client, gateway_client, _, signers) = setup_env(); + + let source_chain = client.its_hub_chain_name(); + let source_address = client.its_hub_address(); + let original_source_chain = String::from_str(&env, "ethereum"); + let message_id = String::from_str(&env, "message_id"); + + client + .mock_all_auths() + .set_trusted_chain(&original_source_chain); + + let token_id = BytesN::from_array(&env, &[i as u8; 32]); + let msg = HubMessage::ReceiveFromHub { + source_chain: original_source_chain.clone(), + message: Message::DeployInterchainToken(DeployInterchainToken { + token_id, + name, + symbol, + decimals: decimal as u8, + minter: None, + }), + }; + let payload = msg.abi_encode(&env).unwrap(); + let payload_hash: BytesN<32> = env.crypto().keccak256(&payload).into(); + + 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: payload_hash.clone(), + }, + ]; + + approve_gateway_messages(&env, &gateway_client, signers, messages); + + assert_contract_err!( + client.try_execute(&source_chain, &message_id, &source_address, &payload), + expected_error + ); + } } #[test] @@ -271,13 +324,16 @@ fn deploy_interchain_token_message_execute_fails_invalid_minter_address() { register_chains(&env, &client); let source_chain = client.its_hub_chain_name(); - let source_address = Address::generate(&env).to_string(); + let source_address = client.its_hub_address(); let token_id = BytesN::from_array(&env, &[1u8; 32]); - let invalid_minter = Bytes::from_array(&env, &[1u8; 32]); + let original_source_chain = String::from_str(&env, "ethereum"); + client + .mock_all_auths() + .set_trusted_chain(&original_source_chain); let msg_invalid_minter = HubMessage::ReceiveFromHub { - source_chain: String::from_str(&env, HUB_CHAIN), + source_chain: original_source_chain, message: Message::DeployInterchainToken(DeployInterchainToken { token_id, name: String::from_str(&env, "test"), @@ -321,7 +377,7 @@ fn deploy_interchain_token_message_execute_fails_token_already_deployed() { let sender = Address::generate(&env).to_string_bytes(&env); let source_chain = client.its_hub_chain_name(); - let source_address = Address::generate(&env).to_string(); + let source_address = client.its_hub_address(); let token_id = BytesN::from_array(&env, &[1u8; 32]); let token_metadata = TokenMetadata { @@ -329,9 +385,13 @@ fn deploy_interchain_token_message_execute_fails_token_already_deployed() { symbol: String::from_str(&env, "TEST"), decimal: 18, }; + let original_source_chain = String::from_str(&env, "ethereum"); + client + .mock_all_auths() + .set_trusted_chain(&original_source_chain); let msg = HubMessage::ReceiveFromHub { - source_chain: String::from_str(&env, HUB_CHAIN), + source_chain: original_source_chain, message: Message::DeployInterchainToken(DeployInterchainToken { token_id, name: token_metadata.name.clone(), diff --git a/contracts/interchain-token-service/tests/flow_limit.rs b/contracts/interchain-token-service/tests/flow_limit.rs index 6c653e83..205f24a8 100644 --- a/contracts/interchain-token-service/tests/flow_limit.rs +++ b/contracts/interchain-token-service/tests/flow_limit.rs @@ -1,8 +1,11 @@ mod utils; use soroban_sdk::testutils::{Address as _, Ledger as _}; -use soroban_sdk::{vec, Address, Bytes, BytesN, Env, String, Vec}; +use soroban_sdk::xdr::ToXdr; +use soroban_sdk::{vec, Address, Bytes, BytesN, Env, String}; +use stellar_axelar_gateway::testutils::TestSignerSet; use stellar_axelar_gateway::types::Message as GatewayMessage; +use stellar_axelar_gateway::AxelarGatewayClient; use stellar_axelar_std::address::AddressExt; use stellar_axelar_std::traits::BytesExt; use stellar_axelar_std::{assert_auth, assert_contract_err, events}; @@ -12,37 +15,85 @@ use stellar_interchain_token_service::types::{HubMessage, InterchainTransfer, Me use stellar_interchain_token_service::InterchainTokenServiceClient; use utils::{ approve_gateway_messages, register_chains, setup_env, setup_gas_token, setup_its_token, - HUB_CHAIN, }; -const TEST_FLOW_LIMIT: Option = Some(1000); +struct GatewayConfig<'a> { + client: AxelarGatewayClient<'a>, + signers: TestSignerSet, +} + +struct TokenConfig { + id: BytesN<32>, + deployer: Address, +} + +struct ApprovedMessage { + source_chain: String, + message_id: String, + source_address: String, + payload: Bytes, +} + const EPOCH_TIME: u64 = 6 * 60 * 60; -fn setup_flow_limit(env: &Env, client: &InterchainTokenServiceClient) -> (BytesN<32>, Address) { +const fn dummy_flow_limit() -> i128 { + 1000 +} + +fn dummy_transfer_params(env: &Env) -> (String, Bytes, Option) { + let destination_chain = String::from_str(env, "ethereum"); + let destination_address = Bytes::from_hex(env, "4F4495243837681061C4743b74B3eEdf548D56A5"); + let data = None; + + (destination_chain, destination_address, data) +} + +fn setup<'a>() -> ( + Env, + InterchainTokenServiceClient<'a>, + GatewayConfig<'a>, + TokenConfig, +) { + let (env, client, gateway_client, _, signers) = setup_env(); + + register_chains(&env, &client); + 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); client .mock_all_auths() - .set_flow_limit(&token_id, &TEST_FLOW_LIMIT); - - (token_id, deployer) + .set_flow_limit(&token_id, &Some(dummy_flow_limit())); + + ( + env, + client, + GatewayConfig { + client: gateway_client, + signers, + }, + TokenConfig { + id: token_id, + deployer, + }, + ) } -fn create_interchain_transfer_message( +fn approve_its_transfer( env: &Env, client: &InterchainTokenServiceClient, + gateway: &GatewayConfig, token_id: &BytesN<32>, amount: i128, -) -> (String, String, String, Bytes, Vec) { +) -> ApprovedMessage { let sender = Address::generate(env).to_string_bytes(env); let recipient = Address::generate(env).to_string_bytes(env); let source_chain = client.its_hub_chain_name(); - let source_address = Address::generate(env).to_string(); + let source_address = client.its_hub_address(); let msg = HubMessage::ReceiveFromHub { - source_chain: String::from_str(env, HUB_CHAIN), + source_chain: source_chain.clone(), message: Message::InterchainTransfer(InterchainTransfer { token_id: token_id.clone(), source_address: sender, @@ -67,7 +118,31 @@ fn create_interchain_transfer_message( }, ]; - (source_chain, message_id, source_address, payload, messages) + approve_gateway_messages(env, &gateway.client, gateway.signers.clone(), messages); + + ApprovedMessage { + source_chain, + message_id, + source_address, + payload, + } +} + +fn execute_its_transfer( + env: &Env, + client: &InterchainTokenServiceClient, + gateway: &GatewayConfig, + token_id: &BytesN<32>, + amount: i128, +) { + let msg = approve_its_transfer(env, client, gateway, token_id, amount); + + client.execute( + &msg.source_chain, + &msg.message_id, + &msg.source_address, + &msg.payload, + ) } #[test] @@ -79,33 +154,56 @@ fn set_flow_limit_succeeds() { assert_auth!( client.operator(), - client.set_flow_limit(&token_id, &TEST_FLOW_LIMIT) + client.set_flow_limit(&token_id, &Some(dummy_flow_limit())) ); - assert_eq!(client.flow_limit(&token_id), TEST_FLOW_LIMIT); + assert_eq!(client.flow_limit(&token_id), Some(dummy_flow_limit())); 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); +fn zero_flow_limit_effectively_freezes_token() { + let (env, client, gateway, token) = setup(); + let gas_token = setup_gas_token(&env, &token.deployer); - client.mock_all_auths().set_flow_limit(&token_id, &Some(0)); + 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); + let msg = approve_its_transfer(&env, &client, &gateway, &token.id, amount); - client.execute(&source_chain, &message_id, &source_address, &payload); + assert_contract_err!( + client.try_execute( + &msg.source_chain, + &msg.message_id, + &msg.source_address, + &msg.payload, + ), + ContractError::FlowLimitExceeded + ); + + let (destination_chain, destination_address, data) = dummy_transfer_params(&env); + + client + .mock_all_auths() + .set_trusted_chain(&destination_chain); + + assert_contract_err!( + client.mock_all_auths().try_interchain_transfer( + &token.deployer, + &token.id, + &destination_chain, + &destination_address, + &amount, + &data, + &gas_token, + ), + ContractError::FlowLimitExceeded + ); } #[test] -fn set_flow_limit_fails_invalid_amount() { +fn set_flow_limit_fails_on_negative_limit() { let (env, client, _, _, _) = setup_env(); let token_id = BytesN::from_array(&env, &[1; 32]); @@ -121,87 +219,75 @@ fn set_flow_limit_fails_invalid_amount() { #[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 (env, client, gateway, token) = setup(); + + let amount = dummy_flow_limit(); - let amount = TEST_FLOW_LIMIT.unwrap(); + execute_its_transfer(&env, &client, &gateway, &token.id, amount); - 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); + assert_eq!(client.flow_in_amount(&token.id), amount); - let current_timestamp = env.ledger().timestamp(); - env.ledger().set_timestamp(current_timestamp + EPOCH_TIME); + env.ledger() + .set_timestamp(env.ledger().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); + assert_eq!(client.flow_in_amount(&token.id), 0); } #[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 (env, client, gateway, token) = setup(); - 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); + let amount = dummy_flow_limit(); - assert_eq!(client.flow_in_amount(&token_id), 0); + assert_eq!(client.flow_in_amount(&token.id), 0); - client.execute(&source_chain, &message_id, &source_address, &payload); + execute_its_transfer(&env, &client, &gateway, &token.id, amount); - assert_eq!(client.flow_in_amount(&token_id), amount); + assert_eq!(client.flow_in_amount(&token.id), amount); + assert_eq!(client.flow_out_amount(&token.id), 0); } #[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); +fn add_flow_in_fails_on_exceeding_flow_limit() { + let (env, client, gateway, token) = setup(); - 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); + let amount = dummy_flow_limit(); - client.execute(&source_chain, &message_id, &source_address, &payload); + execute_its_transfer(&env, &client, &gateway, &token.id, amount); - 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); + assert_eq!(client.flow_in_amount(&token.id), amount); - client.execute(&source_chain, &message_id, &source_address, &payload); + let amount = 1; + let msg = approve_its_transfer(&env, &client, &gateway, &token.id, amount); + + assert_contract_err!( + client.try_execute( + &msg.source_chain, + &msg.message_id, + &msg.source_address, + &msg.payload + ), + ContractError::FlowLimitExceeded + ); } #[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 (env, client, _, token) = setup(); + let gas_token = setup_gas_token(&env, &token.deployer); - let amount = 1000; - let destination_chain = String::from_str(&env, "ethereum"); - let destination_address = Bytes::from_hex(&env, "4F4495243837681061C4743b74B3eEdf548D56A5"); - let data = None; + let amount = dummy_flow_limit(); + let (destination_chain, destination_address, data) = dummy_transfer_params(&env); client .mock_all_auths() .set_trusted_chain(&destination_chain); + assert_eq!(client.flow_out_amount(&token.id), 0); + client.mock_all_auths().interchain_transfer( - &sender, - &token_id, + &token.deployer, + &token.id, &destination_chain, &destination_address, &amount, @@ -209,29 +295,25 @@ fn add_flow_out_succeeds() { &gas_token, ); - assert_eq!(client.flow_out_amount(&token_id), amount); + assert_eq!(client.flow_out_amount(&token.id), amount); + assert_eq!(client.flow_in_amount(&token.id), 0); } #[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); +fn add_flow_out_fails_on_exceeding_flow_limit() { + let (env, client, _, token) = setup(); + let gas_token = setup_gas_token(&env, &token.deployer); - 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; + let amount = dummy_flow_limit(); + let (destination_chain, destination_address, data) = dummy_transfer_params(&env); client .mock_all_auths() .set_trusted_chain(&destination_chain); client.mock_all_auths().interchain_transfer( - &sender, - &token_id, + &token.deployer, + &token.id, &destination_chain, &destination_address, &amount, @@ -239,52 +321,94 @@ fn add_flow_out_fails_exceeds_flow_limit() { &gas_token, ); + assert_eq!(client.flow_out_amount(&token.id), amount); + let second_amount = 1; - client.mock_all_auths().interchain_transfer( - &sender, - &token_id, - &destination_chain, - &destination_address, - &second_amount, - &data, - &gas_token, + assert_contract_err!( + client.mock_all_auths().try_interchain_transfer( + &token.deployer, + &token.id, + &destination_chain, + &destination_address, + &second_amount, + &data, + &gas_token, + ), + ContractError::FlowLimitExceeded ); } #[test] -#[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); - let (token_id, sender) = setup_flow_limit(&env, &client); - let gas_token = setup_gas_token(&env, &sender); + let cases = std::vec![ + (i128::MAX - 50, i128::MAX - 51, 2), + (i128::MAX - 100, i128::MAX - 101, 2), + (i128::MAX / 2 + 1, i128::MAX / 2 + 1, 2), + ]; - client - .mock_all_auths() - .set_flow_limit(&token_id, &Some(i128::MAX - 50)); + for (flow_limit, flow_in, flow_out) in &cases { + let (env, client, gateway, token) = setup(); + let gas_token = setup_gas_token(&env, &token.deployer); - 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); + client + .mock_all_auths() + .set_flow_limit(&token.id, &Some(*flow_limit)); - let small_amount = 100; - let destination_chain = String::from_str(&env, "ethereum"); - let destination_address = Bytes::from_hex(&env, "4F4495243837681061C4743b74B3eEdf548D56A5"); + let (destination_chain, destination_address, data) = dummy_transfer_params(&env); + client + .mock_all_auths() + .set_trusted_chain(&destination_chain); + + execute_its_transfer(&env, &client, &gateway, &token.id, *flow_in); + + assert_contract_err!( + client.mock_all_auths().try_interchain_transfer( + &token.deployer, + &token.id, + &destination_chain, + &destination_address, + flow_out, + &data, + &gas_token + ), + ContractError::FlowAmountOverflow + ); + } + + for (flow_limit, flow_out, flow_in) in cases { + let (env, client, gateway, token) = setup(); + let gas_token = setup_gas_token(&env, &token.deployer); - client - .mock_all_auths() - .set_trusted_chain(&destination_chain); + client + .mock_all_auths() + .set_flow_limit(&token.id, &Some(flow_limit)); - client.mock_all_auths().interchain_transfer( - &sender, - &token_id, - &destination_chain, - &destination_address, - &small_amount, - &None, - &gas_token, - ); + let (destination_chain, destination_address, data) = dummy_transfer_params(&env); + client + .mock_all_auths() + .set_trusted_chain(&destination_chain); + + client.mock_all_auths().interchain_transfer( + &token.deployer, + &token.id, + &destination_chain, + &destination_address, + &flow_out, + &data, + &gas_token, + ); + + let msg = approve_its_transfer(&env, &client, &gateway, &token.id, flow_in); + + assert_contract_err!( + client.try_execute( + &msg.source_chain, + &msg.message_id, + &msg.source_address, + &msg.payload + ), + ContractError::FlowAmountOverflow + ); + } } 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 64ba4111..e80d81bf 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", - "b81a06139cc7eda856e5f063ea31759be22ace7d8b4532abe85c2dd28063776b", - "001664fa3a1ee48676821e0ecd0ae89705ac1983ba91cf432358e9f6e296946f" + "6b0638349aebaeb032da7b9ef6890abe42eca7a835b52bb232c04c8f81a26a82", + "6da6bda0ac5a04b5d83ddb7175cd63341673f2e8f2eddc2dd7d39aeb21aaa244" ] \ 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 fbb7c4e0..b261e9b0 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(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))) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5) +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(CBAWP2LID52I5JPA77XDY5EX3ZIL3S4SVHZCMIYLG47ZT6MQQ44V5VVR), String(Test), String(TEST), 18, Some(Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N))) 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 d0fe05bd..7e521e9f 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(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))) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5) +topics: (Symbol(interchain_token_deployed), BytesN<32>(125, 96, 86, 2, 12, 87, 185, 38, 24, 69, 45, 187, 113, 21, 59, 197, 31, 115, 71, 89, 174, 148, 246, 246, 88, 83, 233, 86, 107, 151, 39, 140), Contract(CCSKIPMIS4O5TMGE6IZWALEL5LEVNGLZ6KH5662WUXIVIVIBTOXZIZ6A), String(Test), String(TEST), 6, Some(Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5))) 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 e2c61045..c50f3d7f 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(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) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5) +topics: (Symbol(token_deployment_started), BytesN<32>(150, 51, 118, 124, 241, 96, 104, 207, 18, 228, 200, 242, 140, 228, 146, 184, 41, 136, 47, 124, 8, 80, 77, 143, 220, 116, 101, 155, 41, 153, 207, 129), Contract(CAWGXBFCJA6N64Z6HECE34OQ4YFA7WJPNP7DYDSRZNKXT474KE4IY3D5), String(ethereum), String(aaa), String(aaa), 7, None) data: () \ No newline at end of file diff --git a/contracts/interchain-token-service/tests/testdata/deploy_remote_canonical_token_succeeds_native_token.golden b/contracts/interchain-token-service/tests/testdata/deploy_remote_canonical_token_succeeds_native_token.golden new file mode 100644 index 00000000..05cda1bf --- /dev/null +++ b/contracts/interchain-token-service/tests/testdata/deploy_remote_canonical_token_succeeds_native_token.golden @@ -0,0 +1,3 @@ +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5) +topics: (Symbol(token_deployment_started), BytesN<32>(112, 235, 39, 66, 82, 16, 99, 2, 198, 174, 69, 174, 17, 11, 97, 52, 125, 86, 193, 214, 97, 52, 22, 151, 30, 226, 238, 227, 114, 120, 173, 103), Contract(CCFPZOCU33AWX2NKX47XD6W5JNYFP7MU57DTQFB5XOOQSJLSSC4PMX25), String(ethereum), String(Stellar), String(XLM), 7, None) +data: () \ No newline at end of file diff --git a/contracts/interchain-token-service/tests/testdata/deploy_remote_canonical_token_succeeds_without_name_truncation.golden b/contracts/interchain-token-service/tests/testdata/deploy_remote_canonical_token_succeeds_without_name_truncation.golden new file mode 100644 index 00000000..76a9a3b4 --- /dev/null +++ b/contracts/interchain-token-service/tests/testdata/deploy_remote_canonical_token_succeeds_without_name_truncation.golden @@ -0,0 +1,3 @@ +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5) +topics: (Symbol(token_deployment_started), BytesN<32>(247, 214, 168, 250, 162, 27, 103, 121, 206, 126, 141, 252, 22, 96, 17, 104, 179, 98, 104, 231, 160, 3, 162, 197, 137, 24, 18, 183, 248, 208, 163, 109), Contract(CCGZAZST4ZRJ6VF32SF3JQYKSDKDHJ3BITGC7SQNXCQW3K7MH7DJA42S), String(ethereum), String(name), String(symbol), 255, 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 fd5d82ad..19a88c7b 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(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) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5) +topics: (Symbol(token_deployment_started), BytesN<32>(125, 96, 86, 2, 12, 87, 185, 38, 24, 69, 45, 187, 113, 21, 59, 197, 31, 115, 71, 89, 174, 148, 246, 246, 88, 83, 233, 86, 107, 151, 39, 140), Contract(CCSKIPMIS4O5TMGE6IZWALEL5LEVNGLZ6KH5662WUXIVIVIBTOXZIZ6A), 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 1b13f72e..0de2fa39 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(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5) -topics: (Symbol(executed), String(axelar), String(test), Bytes(67, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 88, 73, 55, 78), 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) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N) +topics: (Symbol(executed), String(ethereum), String(test), Bytes(67, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 89, 82, 69, 53), 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(CBQXZFUKGANX4DCFGMAEZNEIZ6KEN5RRIUPP25SRMZ6WGLVGFAFT5XSD), 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 aab60cec..5701ef0a 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(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(67, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 86, 65, 88, 53), Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N), 1000) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5) +topics: (Symbol(interchain_transfer_received), String(ethereum), 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(67, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 88, 73, 55, 78), Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5), 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 9be88994..983509e4 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(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) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5) +topics: (Symbol(interchain_transfer_sent), BytesN<32>(125, 96, 86, 2, 12, 87, 185, 38, 24, 69, 45, 187, 113, 21, 59, 197, 31, 115, 71, 89, 174, 148, 246, 246, 88, 83, 233, 86, 107, 151, 39, 140), Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N), 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 b2387d3b..64c765cb 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(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)) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5) +topics: (Symbol(interchain_token_id_claimed), BytesN<32>(109, 166, 189, 160, 172, 90, 4, 181, 216, 61, 219, 113, 117, 205, 99, 52, 22, 115, 242, 232, 242, 237, 220, 45, 215, 211, 154, 235, 33, 170, 162, 68), AccountId(GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF), BytesN<32>(107, 6, 56, 52, 154, 235, 174, 176, 50, 218, 123, 158, 246, 137, 10, 190, 66, 236, 167, 168, 53, 181, 43, 178, 50, 192, 76, 143, 129, 162, 106, 130)) 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 4f4410d4..c4fc2872 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(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5) 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 index faaf4a6b..9c2caaa7 100644 --- a/contracts/interchain-token-service/tests/testdata/set_flow_limit_succeeds.golden +++ b/contracts/interchain-token-service/tests/testdata/set_flow_limit_succeeds.golden @@ -1,3 +1,3 @@ -contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5) 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 555d3462..c344f299 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(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON) +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5) 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 80b5b49d..2b3111ea 100644 --- a/contracts/interchain-token-service/tests/utils/mod.rs +++ b/contracts/interchain-token-service/tests/utils/mod.rs @@ -11,8 +11,6 @@ use stellar_axelar_gateway::AxelarGatewayClient; use stellar_axelar_std::types::Token; use stellar_interchain_token_service::{InterchainTokenService, InterchainTokenServiceClient}; -pub const HUB_CHAIN: &str = "axelar"; - const INTERCHAIN_TOKEN_WASM_HASH: &[u8] = include_bytes!("../testdata/interchain_token.wasm"); pub fn setup_gas_service<'a>(env: &Env) -> AxelarGasServiceClient<'a> { @@ -40,6 +38,8 @@ fn setup_its<'a>( .deployer() .upload_contract_wasm(INTERCHAIN_TOKEN_WASM_HASH); + let native_token_address = env.register_stellar_asset_contract_v2(owner.clone()); + let contract_id = env.register( InterchainTokenService, ( @@ -49,6 +49,7 @@ fn setup_its<'a>( &gas_service.address, its_hub_address, chain_name, + native_token_address.address(), interchain_token_wasm_hash, ), ); @@ -115,9 +116,10 @@ pub fn setup_its_token( } #[allow(dead_code)] -pub fn register_chains(env: &Env, client: &InterchainTokenServiceClient) { - let chain = String::from_str(env, HUB_CHAIN); - client.mock_all_auths().set_trusted_chain(&chain); +pub fn register_chains(_env: &Env, client: &InterchainTokenServiceClient) { + client + .mock_all_auths() + .set_trusted_chain(&client.its_hub_chain_name()); } #[allow(dead_code)] diff --git a/contracts/interchain-token/src/contract.rs b/contracts/interchain-token/src/contract.rs index ad847e1a..54167266 100644 --- a/contracts/interchain-token/src/contract.rs +++ b/contracts/interchain-token/src/contract.rs @@ -7,7 +7,6 @@ use soroban_token_sdk::event::Events as TokenEvents; use soroban_token_sdk::metadata::TokenMetadata; use soroban_token_sdk::TokenUtils; use stellar_axelar_std::interfaces::OwnableInterface; -use stellar_axelar_std::token::validate_token_metadata; use stellar_axelar_std::ttl::{extend_instance_ttl, extend_persistent_ttl}; use stellar_axelar_std::{ensure, interfaces, Upgradable}; @@ -31,10 +30,6 @@ impl InterchainToken { ) { interfaces::set_owner(&env, &owner); - if let Err(err) = validate_token_metadata(&token_metadata) { - panic_with_error!(env, err); - } - Self::write_metadata(&env, token_metadata); env.storage().instance().set(&DataKey::TokenId, &token_id); diff --git a/contracts/interchain-token/tests/test.rs b/contracts/interchain-token/tests/test.rs index 4c429e5c..b59646d0 100644 --- a/contracts/interchain-token/tests/test.rs +++ b/contracts/interchain-token/tests/test.rs @@ -30,42 +30,6 @@ fn setup_token<'a>(env: &Env) -> (InterchainTokenClient<'a>, Address, Address) { (token, owner, minter) } -#[test] -#[should_panic(expected = "HostError: Error(Context, InvalidAction)")] -fn register_token_with_invalid_decimals_fails() { - let env = Env::default(); - let owner = Address::generate(&env); - let minter = Address::generate(&env); - let token_id: BytesN<32> = BytesN::<32>::random(&env); - let token_metadata = setup_token_metadata(&env, "name", "symbol", u32::from(u8::MAX) + 1); - - env.register(InterchainToken, (owner, minter, &token_id, token_metadata)); -} - -#[test] -#[should_panic(expected = "HostError: Error(Context, InvalidAction)")] -fn register_token_with_invalid_name_fails() { - let env = Env::default(); - let owner = Address::generate(&env); - let minter = Address::generate(&env); - let token_id: BytesN<32> = BytesN::<32>::random(&env); - let token_metadata = setup_token_metadata(&env, "", "symbol", 1); - - env.register(InterchainToken, (owner, minter, &token_id, token_metadata)); -} - -#[test] -#[should_panic(expected = "HostError: Error(Context, InvalidAction)")] -fn register_token_with_invalid_symbol_fails() { - let env = Env::default(); - let owner = Address::generate(&env); - let minter = Address::generate(&env); - let token_id: BytesN<32> = BytesN::<32>::random(&env); - let token_metadata = setup_token_metadata(&env, "name", "", 1); - - env.register(InterchainToken, (owner, minter, &token_id, token_metadata)); -} - #[test] fn register_interchain_token() { let env = Env::default(); diff --git a/packages/axelar-std-derive/Cargo.toml b/packages/axelar-std-derive/Cargo.toml index fadd0771..4a0c7298 100644 --- a/packages/axelar-std-derive/Cargo.toml +++ b/packages/axelar-std-derive/Cargo.toml @@ -10,7 +10,7 @@ publish = true proc-macro = true [features] -testutils = [] +testutils = ["soroban-sdk/testutils"] [dependencies] heck = "0.5.0" diff --git a/packages/axelar-std/Cargo.toml b/packages/axelar-std/Cargo.toml index 9433fbf3..bed760f4 100644 --- a/packages/axelar-std/Cargo.toml +++ b/packages/axelar-std/Cargo.toml @@ -22,7 +22,7 @@ soroban-sdk = { workspace = true, features = ["testutils"] } stellar-axelar-std-derive = { workspace = true, features = ["testutils"] } [features] -testutils = ["soroban-sdk/testutils", "hex"] +testutils = ["soroban-sdk/testutils", "hex", "stellar-axelar-std-derive/testutils"] derive = ["dep:stellar-axelar-std-derive"] [lints] diff --git a/packages/axelar-std/src/error.rs b/packages/axelar-std/src/error.rs index dc490b64..de6a4b97 100644 --- a/packages/axelar-std/src/error.rs +++ b/packages/axelar-std/src/error.rs @@ -64,7 +64,7 @@ macro_rules! assert_err { /// `assert_contract_err(client.try_fun(...), ContractError);` #[macro_export] macro_rules! assert_contract_err { - ($given:expr, $expected:pat) => { + ($given:expr, $expected:expr) => { match $given { Ok(v) => panic!( "Expected error {:?}, got {:?} instead", @@ -72,16 +72,11 @@ macro_rules! assert_contract_err { v ), Err(e) => match e { - Ok(v) => { - if !matches!(v, $expected) { - panic!( - "Expected error {}, got {:?} instead", - stringify!($expected), - v - ) - } - } Err(e) => panic!("Unexpected error {e:?}"), + Ok(v) if v != $expected => { + panic!("Expected error {:?}, got {:?} instead", $expected, v) + } + _ => (), }, } }; diff --git a/packages/axelar-std/src/lib.rs b/packages/axelar-std/src/lib.rs index c7a9af36..e07bc637 100644 --- a/packages/axelar-std/src/lib.rs +++ b/packages/axelar-std/src/lib.rs @@ -24,7 +24,5 @@ pub mod interfaces; pub mod address; -pub mod token; - #[cfg(feature = "derive")] pub use stellar_axelar_std_derive::*; diff --git a/packages/axelar-std/src/token.rs b/packages/axelar-std/src/token.rs deleted file mode 100644 index 48d4625b..00000000 --- a/packages/axelar-std/src/token.rs +++ /dev/null @@ -1,29 +0,0 @@ -use soroban_sdk::contracterror; -use soroban_token_sdk::metadata::TokenMetadata; - -use crate::ensure; - -#[contracterror] -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -#[repr(u32)] -pub enum TokenError { - InvalidDecimal = 0, - InvalidTokenName = 1, - InvalidTokenSymbol = 2, -} - -pub fn validate_token_metadata(token_metadata: &TokenMetadata) -> Result<(), TokenError> { - ensure!( - token_metadata.decimal <= u8::MAX.into(), - TokenError::InvalidDecimal - ); - ensure!( - !token_metadata.name.is_empty(), - TokenError::InvalidTokenName - ); - ensure!( - !token_metadata.symbol.is_empty(), - TokenError::InvalidTokenSymbol - ); - Ok(()) -}