diff --git a/contracts/interchain-token-service/src/contract.rs b/contracts/interchain-token-service/src/contract.rs index 5ecbaaa6..a950a21c 100644 --- a/contracts/interchain-token-service/src/contract.rs +++ b/contracts/interchain-token-service/src/contract.rs @@ -164,6 +164,25 @@ impl InterchainTokenServiceInterface for InterchainTokenService { .into() } + fn canonical_token_deploy_salt(env: &Env, token_address: Address) -> BytesN<32> { + let chain_name_hash = Self::chain_name_hash(env); + env.crypto() + .keccak256(&(PREFIX_CANONICAL_TOKEN_SALT, chain_name_hash, token_address).to_xdr(env)) + .into() + } + + fn token_address(env: &Env, token_id: BytesN<32>) -> Address { + Self::token_id_config(env, token_id) + .expect("token id config not found") + .token_address + } + + fn token_manager_type(env: &Env, token_id: BytesN<32>) -> TokenManagerType { + Self::token_id_config(env, token_id) + .expect("token id config not found") + .token_manager_type + } + fn flow_limit(env: &Env, token_id: BytesN<32>) -> Option { flow_limit::flow_limit(env, token_id) } @@ -186,40 +205,6 @@ impl InterchainTokenServiceInterface for InterchainTokenService { flow_limit::set_flow_limit(env, token_id, flow_limit) } - /// 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, - /// and the token address. This ensures uniqueness and consistency for the deployment - /// of canonical tokens across chains. - /// - /// # Parameters - /// - `env`: A reference to the current environment, used for accessing chain-specific - /// utilities such as cryptographic functions. - /// - `token_address`: The address of the token for which the deployment salt is being generated. - /// - /// # Returns - /// - A `BytesN<32>` value representing the computed deployment salt. - fn canonical_token_deploy_salt(env: &Env, token_address: Address) -> BytesN<32> { - let chain_name_hash = Self::chain_name_hash(env); - env.crypto() - .keccak256(&(PREFIX_CANONICAL_TOKEN_SALT, chain_name_hash, token_address).to_xdr(env)) - .into() - } - - /// Retrieves the address of the token associated with the specified token ID. - fn token_address(env: &Env, token_id: BytesN<32>) -> Address { - Self::token_id_config(env, token_id) - .expect("token id config not found") - .token_address - } - - /// Retrieves the type of the token manager type associated with the specified token ID. - fn token_manager_type(env: &Env, token_id: BytesN<32>) -> TokenManagerType { - Self::token_id_config(env, token_id) - .expect("token id config not found") - .token_manager_type - } - fn deploy_interchain_token( env: &Env, caller: Address, @@ -274,26 +259,6 @@ impl InterchainTokenServiceInterface for InterchainTokenService { Ok(token_id) } - /// Deploys an interchain token to a remote chain. - /// - /// This function initiates the deployment of an interchain token to a specified - /// destination chain. It validates the token metadata, emits a deployment event, - /// and triggers the necessary cross-chain call. - /// - /// # Arguments - /// - `env`: Reference to the contract environment. - /// - `caller`: Address of the caller initiating the deployment. The caller must authenticate. - /// - `salt`: A 32-byte unique salt used for token deployment. - /// - `destination_chain`: The name of the destination chain where the token will be deployed. - /// - `gas_token`: The token used to pay for the gas cost of the cross-chain call. - /// - /// # Returns - /// - `Result, ContractError>`: On success, returns the token ID (`BytesN<32>`). - /// On failure, returns a `ContractError`. - /// - /// # Errors - /// - `ContractError::InvalidTokenId`: If the token ID does not exist in the persistent storage. - /// - Any error propagated from `pay_gas_and_call_contract`. fn deploy_remote_interchain_token( env: &Env, caller: Address, @@ -308,25 +273,39 @@ impl InterchainTokenServiceInterface for InterchainTokenService { Self::deploy_remote_token(env, caller, deploy_salt, destination_chain, gas_token) } - /// Deploys a remote canonical token on a specified destination chain. - /// - /// This function computes a deployment salt and uses it to deploy a canonical - /// representation of a token on the remote chain. It retrieves the token metadata - /// from the token address and ensures the metadata is valid before initiating - /// the deployment. - /// - /// # Arguments - /// * `env` - Reference to the environment object. - /// * `token_address` - The address of the token to be deployed. - /// * `destination_chain` - The name of the destination chain where the token will be deployed. - /// * `spender` - The spender of the cross-chain gas. - /// * `gas_token` - The token used to pay for gas during the deployment. - /// - /// # Returns - /// Returns the token ID of the deployed token on the remote chain, or an error if the deployment fails. - /// - /// # Errors - /// Returns `ContractError` if the deployment fails or if token metadata is invalid. + fn register_canonical_token( + env: &Env, + token_address: Address, + ) -> Result, ContractError> { + let deploy_salt = Self::canonical_token_deploy_salt(env, token_address.clone()); + let token_id = Self::interchain_token_id(env, Address::zero(env), deploy_salt.clone()); + + ensure!( + !env.storage() + .persistent() + .has(&DataKey::TokenIdConfigKey(token_id.clone())), + ContractError::TokenAlreadyRegistered + ); + + InterchainTokenIdClaimedEvent { + token_id: token_id.clone(), + deployer: Address::zero(env), + salt: deploy_salt, + } + .emit(env); + + Self::set_token_id_config( + env, + token_id.clone(), + TokenIdConfigValue { + token_address, + token_manager_type: TokenManagerType::LockUnlock, + }, + ); + + Ok(token_id) + } + fn deploy_remote_canonical_token( env: &Env, token_address: Address, @@ -396,50 +375,6 @@ impl InterchainTokenServiceInterface for InterchainTokenService { Ok(()) } - - /// Registers a canonical token as an interchain token. - /// - /// # Arguments - /// * `env` - A reference to the environment in which the function operates. - /// * `token_address` - The address of the canonical token. - /// - /// # Returns - /// * `Result, ContractError>` - The token ID assigned to this canonical token if successful. - /// - /// # Errors - /// * `ContractError::TokenAlreadyRegistered` - If the token ID is already registered. - fn register_canonical_token( - env: &Env, - token_address: Address, - ) -> Result, ContractError> { - let deploy_salt = Self::canonical_token_deploy_salt(env, token_address.clone()); - let token_id = Self::interchain_token_id(env, Address::zero(env), deploy_salt.clone()); - - ensure!( - !env.storage() - .persistent() - .has(&DataKey::TokenIdConfigKey(token_id.clone())), - ContractError::TokenAlreadyRegistered - ); - - InterchainTokenIdClaimedEvent { - token_id: token_id.clone(), - deployer: Address::zero(env), - salt: deploy_salt, - } - .emit(env); - - Self::set_token_id_config( - env, - token_id.clone(), - TokenIdConfigValue { - token_address, - token_manager_type: TokenManagerType::LockUnlock, - }, - ); - - Ok(token_id) - } } #[contractimpl] diff --git a/contracts/interchain-token-service/src/interface.rs b/contracts/interchain-token-service/src/interface.rs index 8f934081..b51d354e 100644 --- a/contracts/interchain-token-service/src/interface.rs +++ b/contracts/interchain-token-service/src/interface.rs @@ -9,37 +9,80 @@ 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 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 whether the specified chain is trusted for cross-chain messaging. fn is_trusted_chain(env: &Env, chain: String) -> bool; + /// Sets the specified chain as trusted for cross-chain messaging. + /// # Authorization + /// - Must be called by [`Self::owner`]. fn set_trusted_chain(env: &Env, chain: String) -> Result<(), ContractError>; + /// Removes the specified chain from trusted chains. + /// # Authorization + /// - Must be called by the [`Self::owner`]. fn remove_trusted_chain(env: &Env, chain: String) -> Result<(), ContractError>; + /// Computes a 32-byte deployment salt for a new interchain token. + /// + /// The deployment salt is derived uniquely for the given chain, deployer, and salt combination. + /// + /// # Parameters + /// - `deployer`: The address of the token deployer. + /// - `salt`: A unique value provided by the deployer. + /// + /// # Returns + /// - A `BytesN<32>` value representing the computed deployment salt. fn interchain_token_deploy_salt(env: &Env, deployer: Address, salt: BytesN<32>) -> BytesN<32>; + /// Computes the unique identifier for an interchain token. + /// + /// The token ID is derived uniquely from the sender's address and the provided salt. + /// + /// # Parameters + /// - `sender`: The address of the token deployer. In the case of tokens deployed by this contract, it will be Stellar's "dead" address. + /// - `salt`: A unique value used to generate the token ID. + /// + /// # Returns + /// - A `BytesN<32>` value representing the token's unique ID. fn interchain_token_id(env: &Env, sender: Address, salt: BytesN<32>) -> BytesN<32>; + /// Computes a 32-byte deployment salt for a canonical token. + /// + /// The salt is derived uniquely from the chain name hash and token address. + /// + /// # Parameters + /// - `token_address`: The address of the token for which the deployment salt is being generated. + /// + /// # Returns + /// - A `BytesN<32>` value representing the computed deployment salt. fn canonical_token_deploy_salt(env: &Env, token_address: Address) -> BytesN<32>; + /// Returns the address of the token associated with the specified token ID. fn token_address(env: &Env, token_id: BytesN<32>) -> Address; + /// Returns the type of the token manager associated with the specified token ID. 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 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 chain to other chains during the current epoch + /// Returns 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; @@ -57,20 +100,36 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface { /// - `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`]. + /// - Must be called by [`Self::operator`]. fn set_flow_limit( env: &Env, token_id: BytesN<32>, flow_limit: Option, ) -> Result<(), ContractError>; + /// Deploys a new interchain token on the current chain with specified metadata and optional + /// initial supply. If initial supply is provided, it is minted to the caller. The + /// caller can also specify an optional minter address for the interchain token. + /// + /// # Arguments + /// - `caller`: Address of the caller initiating the deployment. + /// - `salt`: A 32-byte unique salt used for token deployment. + /// - `token_metadata`: Metadata for the new token (name, symbol, decimals). + /// - `initial_supply`: Initial amount to mint to caller, if greater than 0. + /// - `minter`: Optional address that will have a minter role for the deployed interchain token. + /// + /// # Returns + /// - `Ok(BytesN<32>)`: Returns the token ID. + /// + /// # Errors + /// - `ContractError::InvalidMinter`: If the minter address is invalid. + /// + /// # Authorization + /// - The caller must authenticate. fn deploy_interchain_token( env: &Env, deployer: Address, @@ -80,6 +139,23 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface { minter: Option
, ) -> Result, ContractError>; + /// Initiates the deployment of an interchain token to a specified destination chain. + /// + /// # Arguments + /// - `caller`: Address of the caller initiating the deployment. + /// - `salt`: A 32-byte unique salt used for token deployment. + /// - `destination_chain`: The name of the destination chain where the token will be deployed. + /// - `gas_token`: The token used to pay for the gas cost of the cross-chain call. + /// + /// # Returns + /// - `Ok(BytesN<32>)`: Returns the token ID. + /// + /// # Errors + /// - `ContractError::InvalidTokenId`: If the token ID does not exist in the persistent storage. + /// - Any error propagated from `pay_gas_and_call_contract`. + /// + /// # Authorization + /// - The caller must authenticate. fn deploy_remote_interchain_token( env: &Env, caller: Address, @@ -88,6 +164,40 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface { gas_token: Token, ) -> Result, ContractError>; + /// Registers a canonical token as an interchain token. + /// + /// # Arguments + /// - `token_address` - The address of the canonical token. + /// + /// # Returns + /// - `Ok(BytesN<32>)`: Returns the token ID. + /// + /// # Errors + /// - `ContractError::TokenAlreadyRegistered`: If the token ID is already registered. + fn register_canonical_token( + env: &Env, + token_address: Address, + ) -> Result, ContractError>; + + /// 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. + /// + /// # Arguments + /// * `token_address` - The address of the token to be deployed. + /// * `destination_chain` - The name of the destination chain where the token will be deployed. + /// * `spender` - The spender of the cross-chain gas. + /// * `gas_token` - The token used to pay for gas during the deployment. + /// + /// # Returns + /// - `Ok(BytesN<32>)`: Returns the token ID. + /// + /// # Errors + /// - `ContractError::InvalidTokenId`: If the token ID does not exist in the persistent storage. + /// - Any error propagated from `pay_gas_and_call_contract`. + /// + /// # Authorization + /// - Gas Service requires authorization for spender. fn deploy_remote_canonical_token( env: &Env, token_address: Address, @@ -96,6 +206,34 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface { gas_token: Token, ) -> Result, ContractError>; + /// Initiates a cross-chain token transfer. + /// + /// Takes tokens from the caller on the source chain and initiates a transfer + /// to the specified destination chain. The tokens will be transferred to the destination address + /// when the message is executed on the destination chain. + /// + /// If `data` is provided, the `destination_address` will also be executed as a contract with the + /// `data` to allow arbitrary processing of the transferred tokens. + /// + /// # Arguments + /// - `caller`: The address initiating the transfer. + /// - `token_id`: The unique identifier of the token being transferred. + /// - `destination_chain`: The chain to which tokens will be transferred. + /// - `destination_address`: The recipient address on the destination chain. + /// - `amount`: The amount of tokens to transfer. Must be greater than 0. + /// - `data`: Optional data to be handled by the destination address if it's a contract. + /// - `gas_token`: The token used to pay for cross-chain message execution. + /// + /// # Returns + /// - `Ok(())` + /// + /// # Errors + /// - `ContractError::InvalidAmount`: If amount is not greater than 0. + /// - `ContractError::FlowLimitExceeded`: If transfer would exceed flow limits. + /// - Any error propagated from `pay_gas_and_call_contract`. + /// + /// # Authorization + /// - The caller must authenticate. fn interchain_transfer( env: &Env, caller: Address, @@ -106,9 +244,4 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface { metadata: Option, gas_token: Token, ) -> Result<(), ContractError>; - - fn register_canonical_token( - env: &Env, - token_address: Address, - ) -> Result, ContractError>; }