diff --git a/Cargo.lock b/Cargo.lock
index b097574e..e6ab265c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -404,6 +404,7 @@ dependencies = [
"goldie",
"hex",
"soroban-sdk",
+ "soroban-token-sdk",
]
[[package]]
diff --git a/contracts/interchain-token-service/src/contract.rs b/contracts/interchain-token-service/src/contract.rs
index 9a6cf4fd..952e87db 100644
--- a/contracts/interchain-token-service/src/contract.rs
+++ b/contracts/interchain-token-service/src/contract.rs
@@ -1,11 +1,13 @@
use axelar_gas_service::AxelarGasServiceClient;
use axelar_gateway::{executable::AxelarExecutableInterface, AxelarGatewayMessagingClient};
+use axelar_soroban_std::assert_ok;
use axelar_soroban_std::events::Event;
+use axelar_soroban_std::token::validate_token_metadata;
use axelar_soroban_std::{
address::AddressExt, ensure, interfaces, types::Token, Ownable, Upgradable,
};
use interchain_token::InterchainTokenClient;
-use soroban_sdk::token::StellarAssetClient;
+use soroban_sdk::token::{self, StellarAssetClient};
use soroban_sdk::xdr::{FromXdr, ToXdr};
use soroban_sdk::{contract, contractimpl, panic_with_error, Address, Bytes, BytesN, Env, String};
use soroban_token_sdk::metadata::TokenMetadata;
@@ -13,6 +15,7 @@ use soroban_token_sdk::metadata::TokenMetadata;
use crate::abi::{get_message_type, MessageType as EncodedMessageType};
use crate::error::ContractError;
use crate::event::{
+ InterchainTokenDeployedEvent, InterchainTokenDeploymentStartedEvent,
InterchainTokenIdClaimedEvent, InterchainTransferReceivedEvent, InterchainTransferSentEvent,
TrustedChainRemovedEvent, TrustedChainSetEvent,
};
@@ -20,7 +23,9 @@ use crate::executable::InterchainTokenExecutableClient;
use crate::interface::InterchainTokenServiceInterface;
use crate::storage_types::{DataKey, TokenIdConfigValue};
use crate::token_handler;
-use crate::types::{HubMessage, InterchainTransfer, Message, TokenManagerType};
+use crate::types::{
+ DeployInterchainToken, HubMessage, InterchainTransfer, Message, TokenManagerType,
+};
const ITS_HUB_CHAIN_NAME: &str = "axelar";
const PREFIX_INTERCHAIN_TOKEN_ID: &str = "its-interchain-token-id";
@@ -183,7 +188,7 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
env: &Env,
caller: Address,
salt: BytesN<32>,
- token_meta_data: TokenMetadata,
+ token_metadata: TokenMetadata,
initial_supply: i128,
minter: Option
,
) -> Result, ContractError> {
@@ -211,12 +216,22 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
Self::interchain_token_wasm_hash(env),
(
env.current_contract_address(),
- initial_minter,
+ initial_minter.clone(),
token_id.clone(),
- token_meta_data,
+ token_metadata.clone(),
),
);
+ InterchainTokenDeployedEvent {
+ token_id: token_id.clone(),
+ token_address: deployed_address.clone(),
+ name: token_metadata.name,
+ symbol: token_metadata.symbol,
+ decimals: token_metadata.decimal,
+ minter: initial_minter,
+ }
+ .emit(env);
+
if initial_supply > 0 {
StellarAssetClient::new(env, &deployed_address).mint(&caller, &initial_supply);
@@ -239,17 +254,74 @@ 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,
- _salt: BytesN<32>,
- _minter: Option,
- _destination_chain: String,
- _gas_token: Token,
+ env: &Env,
+ caller: Address,
+ salt: BytesN<32>,
+ destination_chain: String,
+ gas_token: Token,
) -> Result, ContractError> {
- // TODO: implementation
+ caller.require_auth();
- todo!()
+ 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);
+ let token_address = env
+ .storage()
+ .persistent()
+ .get::<_, TokenIdConfigValue>(&DataKey::TokenIdConfigKey(token_id.clone()))
+ .ok_or(ContractError::InvalidTokenId)?
+ .token_address;
+ let token = token::Client::new(env, &token_address);
+ let token_metadata = TokenMetadata {
+ name: token.name(),
+ decimal: token.decimals(),
+ symbol: token.symbol(),
+ };
+
+ assert_ok!(validate_token_metadata(token_metadata.clone()));
+
+ 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,
+ minter: None,
+ });
+
+ InterchainTokenDeploymentStartedEvent {
+ token_id: token_id.clone(),
+ token_address,
+ destination_chain: destination_chain.clone(),
+ name: token_metadata.name,
+ symbol: token_metadata.symbol,
+ decimals: token_metadata.decimal,
+ minter: None,
+ }
+ .emit(env);
+
+ Self::pay_gas_and_call_contract(env, caller, destination_chain, message, gas_token)?;
+
+ Ok(token_id)
}
fn interchain_transfer(
diff --git a/contracts/interchain-token-service/src/error.rs b/contracts/interchain-token-service/src/error.rs
index be8681bb..42018cfc 100644
--- a/contracts/interchain-token-service/src/error.rs
+++ b/contracts/interchain-token-service/src/error.rs
@@ -19,4 +19,6 @@ pub enum ContractError {
InvalidDestinationAddress = 13,
InvalidHubChain = 14,
TokenAlreadyRegistered = 15,
+ InvalidTokenMetaData = 16,
+ InvalidTokenId = 17,
}
diff --git a/contracts/interchain-token-service/src/event.rs b/contracts/interchain-token-service/src/event.rs
index 696fa031..88252c60 100644
--- a/contracts/interchain-token-service/src/event.rs
+++ b/contracts/interchain-token-service/src/event.rs
@@ -13,6 +13,27 @@ pub struct TrustedChainRemovedEvent {
pub chain: String,
}
+#[derive(Debug, PartialEq, Eq)]
+pub struct InterchainTokenDeployedEvent {
+ pub token_id: BytesN<32>,
+ pub token_address: Address,
+ pub name: String,
+ pub symbol: String,
+ pub decimals: u32,
+ pub minter: Option,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct InterchainTokenDeploymentStartedEvent {
+ pub token_id: BytesN<32>,
+ pub token_address: Address,
+ pub destination_chain: String,
+ pub name: String,
+ pub symbol: String,
+ pub decimals: u32,
+ pub minter: Option,
+}
+
#[derive(Debug, PartialEq, Eq)]
pub struct InterchainTokenIdClaimedEvent {
pub token_id: BytesN<32>,
@@ -63,6 +84,43 @@ impl Event for TrustedChainRemovedEvent {
}
}
+impl Event for InterchainTokenDeployedEvent {
+ fn topics(&self, env: &Env) -> impl Topics + Debug {
+ (
+ Symbol::new(env, "interchain_token_deployed"),
+ self.token_id.to_val(),
+ self.token_address.to_val(),
+ self.name.to_val(),
+ self.symbol.to_val(),
+ self.decimals,
+ self.minter.clone(),
+ )
+ }
+
+ fn data(&self, env: &Env) -> impl IntoVal + Debug {
+ Vec::::new(env)
+ }
+}
+
+impl Event for InterchainTokenDeploymentStartedEvent {
+ fn topics(&self, env: &Env) -> impl Topics + Debug {
+ (
+ Symbol::new(env, "token_deployment_started"),
+ self.token_id.to_val(),
+ self.token_address.to_val(),
+ self.destination_chain.to_val(),
+ self.name.to_val(),
+ self.symbol.to_val(),
+ self.decimals,
+ self.minter.clone(),
+ )
+ }
+
+ fn data(&self, env: &Env) -> impl IntoVal + Debug {
+ Vec::::new(env)
+ }
+}
+
impl Event for InterchainTokenIdClaimedEvent {
fn topics(&self, env: &Env) -> impl Topics + Debug {
(
@@ -121,6 +179,37 @@ impl_event_testutils!(TrustedChainSetEvent, (Symbol, String), ());
#[cfg(any(test, feature = "testutils"))]
impl_event_testutils!(TrustedChainRemovedEvent, (Symbol, String), ());
+#[cfg(any(test, feature = "testutils"))]
+impl_event_testutils!(
+ InterchainTokenDeployedEvent,
+ (
+ Symbol,
+ BytesN<32>,
+ Address,
+ String,
+ String,
+ u32,
+ Option
+ ),
+ ()
+);
+
+#[cfg(any(test, feature = "testutils"))]
+impl_event_testutils!(
+ InterchainTokenDeploymentStartedEvent,
+ (
+ Symbol,
+ BytesN<32>,
+ Address,
+ String,
+ String,
+ String,
+ u32,
+ Option
+ ),
+ ()
+);
+
#[cfg(any(test, feature = "testutils"))]
impl_event_testutils!(
InterchainTokenIdClaimedEvent,
diff --git a/contracts/interchain-token-service/src/interface.rs b/contracts/interchain-token-service/src/interface.rs
index 87f7f96d..6f881d0e 100644
--- a/contracts/interchain-token-service/src/interface.rs
+++ b/contracts/interchain-token-service/src/interface.rs
@@ -36,18 +36,17 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface {
env: &Env,
deployer: Address,
salt: BytesN<32>,
- token_meta_data: TokenMetadata,
+ token_metadata: TokenMetadata,
initial_supply: i128,
minter: Option,
) -> Result, ContractError>;
fn deploy_remote_interchain_token(
- _env: &Env,
+ env: &Env,
caller: Address,
- _salt: BytesN<32>,
- _minter: Option,
- _destination_chain: String,
- _gas_token: Token,
+ salt: BytesN<32>,
+ destination_chain: String,
+ gas_token: Token,
) -> Result, ContractError>;
fn interchain_transfer(
diff --git a/contracts/interchain-token-service/tests/canonical_token.rs b/contracts/interchain-token-service/tests/canonical_token.rs
index 4d998687..c864726b 100644
--- a/contracts/interchain-token-service/tests/canonical_token.rs
+++ b/contracts/interchain-token-service/tests/canonical_token.rs
@@ -11,7 +11,7 @@ const PREFIX_CANONICAL_TOKEN_SALT: &str = "canonical-token-salt";
#[test]
fn register_canonical_token_succeeds() {
- let (env, client, _, _) = setup_env();
+ let (env, client, _, _, _) = setup_env();
let token_address = Address::generate(&env);
let chain_name = client.chain_name();
@@ -45,7 +45,7 @@ fn register_canonical_token_succeeds() {
#[test]
fn register_canonical_token_fails_if_already_registered() {
- let (env, client, _, _) = setup_env();
+ let (env, client, _, _, _) = setup_env();
let token_address = Address::generate(&env);
client.register_canonical_token(&token_address);
@@ -58,7 +58,7 @@ fn register_canonical_token_fails_if_already_registered() {
#[test]
fn canonical_token_id_derivation() {
- let (env, client, _, _) = setup_env();
+ let (env, client, _, _, _) = setup_env();
let token_address = Address::generate(&env);
let chain_name = client.chain_name();
diff --git a/contracts/interchain-token-service/tests/deploy_interchain_token.rs b/contracts/interchain-token-service/tests/deploy_interchain_token.rs
index cc306d6a..9155b6c1 100644
--- a/contracts/interchain-token-service/tests/deploy_interchain_token.rs
+++ b/contracts/interchain-token-service/tests/deploy_interchain_token.rs
@@ -3,38 +3,59 @@ mod utils;
use axelar_soroban_std::address::AddressExt;
use axelar_soroban_std::assert_contract_err;
use axelar_soroban_std::assert_invoke_auth_err;
+use axelar_soroban_std::events;
use interchain_token::InterchainTokenClient;
use interchain_token_service::error::ContractError;
+use interchain_token_service::event::InterchainTokenDeployedEvent;
use interchain_token_service::types::TokenManagerType;
+use soroban_sdk::testutils::Address as _;
use soroban_sdk::Address;
use soroban_sdk::BytesN;
-use soroban_sdk::IntoVal;
-use soroban_sdk::{testutils::Address as _, Env};
use soroban_token_sdk::metadata::TokenMetadata;
use utils::setup_env;
+use utils::TokenMetadataExt;
-fn setup_token_metadata(env: &Env, name: &str, symbol: &str, decimal: u32) -> TokenMetadata {
- TokenMetadata {
- decimal,
- name: name.into_val(env),
- symbol: symbol.into_val(env),
- }
+#[test]
+fn deploy_interchain_token_succeeds() {
+ let (env, client, _, _, _) = setup_env();
+
+ 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 initial_supply = 100;
+
+ client.mock_all_auths().deploy_interchain_token(
+ &sender,
+ &salt,
+ &token_metadata,
+ &initial_supply,
+ &minter,
+ );
+
+ goldie::assert!(events::fmt_emitted_event_at_idx::<
+ InterchainTokenDeployedEvent,
+ >(&env, -2));
}
#[test]
fn deploy_interchain_token_with_initial_supply_no_minter() {
- let (env, client, _, _) = setup_env();
- env.mock_all_auths();
+ let (env, client, _, _, _) = setup_env();
let sender = Address::generate(&env);
let minter: Option = None;
let salt = BytesN::<32>::from_array(&env, &[1; 32]);
- let token_meta_data = setup_token_metadata(&env, "name", "symbol", 6);
+ let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6);
let initial_supply = 100;
- let token_id =
- client.deploy_interchain_token(&sender, &salt, &token_meta_data, &initial_supply, &minter);
+ let token_id = client.mock_all_auths().deploy_interchain_token(
+ &sender,
+ &salt,
+ &token_metadata,
+ &initial_supply,
+ &minter,
+ );
let token_address = client.token_address(&token_id);
let token = InterchainTokenClient::new(&env, &token_address);
@@ -46,19 +67,19 @@ fn deploy_interchain_token_with_initial_supply_no_minter() {
#[test]
fn deploy_interchain_token_with_initial_supply_valid_minter() {
- let (env, client, _, _) = setup_env();
+ let (env, client, _, _, _) = setup_env();
env.mock_all_auths();
let sender = Address::generate(&env);
let minter = Address::generate(&env);
let salt = BytesN::<32>::from_array(&env, &[1; 32]);
- let token_meta_data = setup_token_metadata(&env, "name", "symbol", 6);
+ let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6);
let initial_supply = 100;
let token_id = client.deploy_interchain_token(
&sender,
&salt,
- &token_meta_data,
+ &token_metadata,
&initial_supply,
&Some(minter.clone()),
);
@@ -74,13 +95,13 @@ fn deploy_interchain_token_with_initial_supply_valid_minter() {
#[test]
fn deploy_interchain_token_check_token_id_and_token_manager_type() {
- let (env, client, _, _) = setup_env();
+ let (env, client, _, _, _) = setup_env();
env.mock_all_auths();
let sender = Address::generate(&env);
let minter = Address::generate(&env);
let salt = BytesN::<32>::from_array(&env, &[1; 32]);
- let token_meta_data = setup_token_metadata(&env, "name", "symbol", 6);
+ let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6);
let initial_supply = 100;
let deploy_salt = client.interchain_token_deploy_salt(&sender, &salt);
@@ -89,7 +110,7 @@ fn deploy_interchain_token_check_token_id_and_token_manager_type() {
let token_id = client.deploy_interchain_token(
&sender,
&salt,
- &token_meta_data,
+ &token_metadata,
&initial_supply,
&Some(minter),
);
@@ -103,19 +124,19 @@ fn deploy_interchain_token_check_token_id_and_token_manager_type() {
#[test]
fn deploy_interchain_token_zero_initial_supply_and_valid_minter() {
- let (env, client, _, _) = setup_env();
+ let (env, client, _, _, _) = setup_env();
env.mock_all_auths();
let sender = Address::generate(&env);
let minter = Address::generate(&env);
let salt = BytesN::<32>::from_array(&env, &[1; 32]);
- let token_meta_data = setup_token_metadata(&env, "name", "symbol", 6);
+ let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6);
let initial_supply = 0;
let token_id = client.deploy_interchain_token(
&sender,
&salt,
- &token_meta_data,
+ &token_metadata,
&initial_supply,
&Some(minter.clone()),
);
@@ -132,20 +153,20 @@ fn deploy_interchain_token_zero_initial_supply_and_valid_minter() {
#[test]
fn deploy_interchain_token_falis_zero_initial_supply_and_invalid_minter() {
- let (env, client, _, _) = setup_env();
+ let (env, client, _, _, _) = setup_env();
env.mock_all_auths();
let sender = Address::generate(&env);
let minter: Option = Some(client.address.clone());
let salt = BytesN::<32>::from_array(&env, &[1; 32]);
- let token_meta_data = setup_token_metadata(&env, "name", "symbol", 6);
+ let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6);
let initial_supply = 0;
assert_contract_err!(
client.try_deploy_interchain_token(
&sender,
&salt,
- &token_meta_data,
+ &token_metadata,
&initial_supply,
&minter
),
@@ -155,17 +176,17 @@ fn deploy_interchain_token_falis_zero_initial_supply_and_invalid_minter() {
#[test]
fn deploy_interchain_token_zero_initial_supply_no_minter() {
- let (env, client, _, _) = setup_env();
+ 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 token_meta_data = setup_token_metadata(&env, "name", "symbol", 6);
+ let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6);
let initial_supply = 0;
let token_id =
- client.deploy_interchain_token(&sender, &salt, &token_meta_data, &initial_supply, &minter);
+ client.deploy_interchain_token(&sender, &salt, &token_metadata, &initial_supply, &minter);
let token_address = client.token_address(&token_id);
let token = InterchainTokenClient::new(&env, &token_address);
@@ -176,16 +197,33 @@ fn deploy_interchain_token_zero_initial_supply_no_minter() {
assert_eq!(token.balance(&sender), initial_supply);
}
+#[test]
+#[should_panic(expected = "HostError: Error(Context, InvalidAction)")]
+fn deploy_interchain_token_fails_with_invalid_decimals() {
+ 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);
+}
+
#[test]
fn deploy_interchain_token_fails_with_invalid_auth() {
- let (env, client, _, _) = setup_env();
+ 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_meta_data = setup_token_metadata(&env, "name", "symbol", 6);
+ let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6);
+
let initial_supply = 100;
assert_invoke_auth_err!(
@@ -193,7 +231,7 @@ fn deploy_interchain_token_fails_with_invalid_auth() {
client.try_deploy_interchain_token(
&sender,
&salt,
- &token_meta_data,
+ &token_metadata,
&initial_supply,
&minter,
)
diff --git a/contracts/interchain-token-service/tests/deploy_remote_interchain_token.rs b/contracts/interchain-token-service/tests/deploy_remote_interchain_token.rs
new file mode 100644
index 00000000..b1fdaaa2
--- /dev/null
+++ b/contracts/interchain-token-service/tests/deploy_remote_interchain_token.rs
@@ -0,0 +1,196 @@
+mod utils;
+
+use axelar_soroban_std::assert_contract_err;
+use axelar_soroban_std::auth_invocation;
+use axelar_soroban_std::events;
+use interchain_token_service::error::ContractError;
+use interchain_token_service::event::InterchainTokenDeploymentStartedEvent;
+use interchain_token_service::types::DeployInterchainToken;
+use interchain_token_service::types::HubMessage;
+use interchain_token_service::types::Message;
+use soroban_sdk::testutils::AuthorizedFunction;
+use soroban_sdk::testutils::AuthorizedInvocation;
+use soroban_sdk::Bytes;
+use soroban_sdk::IntoVal;
+use soroban_sdk::Symbol;
+use soroban_sdk::{testutils::Address as _, Address, BytesN, String};
+use soroban_token_sdk::metadata::TokenMetadata;
+use utils::setup_env;
+use utils::setup_gas_token;
+use utils::TokenMetadataExt;
+
+#[test]
+fn deploy_remote_interchain_token_succeeds() {
+ let (env, client, _, _, _) = setup_env();
+
+ let sender = Address::generate(&env);
+ let gas_token = setup_gas_token(&env, &sender);
+ let minter: Option = None;
+ let salt = BytesN::<32>::from_array(&env, &[1; 32]);
+ let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6);
+ let initial_supply = 1;
+
+ let token_id = client.mock_all_auths().deploy_interchain_token(
+ &sender,
+ &salt,
+ &token_metadata,
+ &initial_supply,
+ &minter,
+ );
+
+ let destination_chain = String::from_str(&env, "ethereum");
+ client
+ .mock_all_auths()
+ .set_trusted_chain(&destination_chain);
+
+ let deployed_token_id = client.mock_all_auths().deploy_remote_interchain_token(
+ &sender,
+ &salt,
+ &destination_chain,
+ &gas_token,
+ );
+ assert_eq!(token_id, deployed_token_id);
+
+ goldie::assert!(events::fmt_emitted_event_at_idx::<
+ InterchainTokenDeploymentStartedEvent,
+ >(&env, -4));
+}
+
+#[test]
+fn deploy_remote_interchain_token_auth_test() {
+ let (env, client, _, gas_service, _) = setup_env();
+
+ let sender = Address::generate(&env);
+ let gas_token = setup_gas_token(&env, &sender);
+ let minter: Option = None;
+ let salt = BytesN::<32>::from_array(&env, &[1; 32]);
+ let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6);
+ let initial_supply = 1;
+
+ let token_id = client.mock_all_auths().deploy_interchain_token(
+ &sender,
+ &salt,
+ &token_metadata,
+ &initial_supply,
+ &minter,
+ );
+
+ let destination_chain = String::from_str(&env, "ethereum");
+ let its_hub_chain = String::from_str(&env, "axelar");
+ let its_hub_address = String::from_str(&env, "its_hub_address");
+
+ client
+ .mock_all_auths()
+ .set_trusted_chain(&destination_chain);
+
+ let deployed_token_id = client.mock_all_auths().deploy_remote_interchain_token(
+ &sender,
+ &salt,
+ &destination_chain,
+ &gas_token,
+ );
+ assert_eq!(token_id, deployed_token_id);
+
+ let message = Message::DeployInterchainToken(DeployInterchainToken {
+ token_id,
+ name: token_metadata.name.clone(),
+ symbol: token_metadata.symbol.clone(),
+ decimals: token_metadata.decimal as u8,
+ minter: None,
+ });
+
+ let payload = HubMessage::SendToHub {
+ destination_chain: destination_chain.clone(),
+ message,
+ }
+ .abi_encode(&env);
+
+ let transfer_auth = auth_invocation!(&env,
+ "transfer",
+ gas_token.clone().address =>
+ (
+ &sender,
+ gas_service.address.clone(),
+ gas_token.amount
+ )
+ );
+
+ let pay_gas_auth = auth_invocation!(&env,
+ "pay_gas",
+ gas_service.address =>
+ (
+ client.address.clone(),
+ its_hub_chain,
+ its_hub_address,
+ payload,
+ &sender,
+ gas_token.clone(),
+ &Bytes::new(&env)
+ ),
+ transfer_auth
+ );
+
+ let deploy_remote_interchain_token_auth = auth_invocation!(&env,
+ sender,
+ "deploy_remote_interchain_token",
+ client.address =>
+ (
+ &sender,
+ salt,
+ destination_chain,
+ gas_token
+ ),
+ pay_gas_auth
+ );
+
+ assert_eq!(env.auths(), deploy_remote_interchain_token_auth);
+}
+
+#[test]
+fn deploy_remote_interchain_token_fails_untrusted_chain() {
+ let (env, client, _, _, _) = setup_env();
+
+ let sender = Address::generate(&env);
+ let gas_token = setup_gas_token(&env, &sender);
+ let minter: Option = None;
+ let salt = BytesN::<32>::from_array(&env, &[1; 32]);
+ let token_metadata = TokenMetadata::new(&env, "name", "symbol", 6);
+ let initial_supply = 1;
+
+ client.mock_all_auths().deploy_interchain_token(
+ &sender,
+ &salt,
+ &token_metadata,
+ &initial_supply,
+ &minter,
+ );
+
+ let destination_chain = String::from_str(&env, "ethereum");
+
+ assert_contract_err!(
+ client.mock_all_auths().try_deploy_remote_interchain_token(
+ &sender,
+ &salt,
+ &destination_chain,
+ &gas_token,
+ ),
+ ContractError::UntrustedChain
+ );
+}
+
+#[test]
+fn deploy_remote_interchain_token_fails_with_invalid_token_id() {
+ let (env, client, _, _, _) = setup_env();
+ env.mock_all_auths();
+
+ let sender = Address::generate(&env);
+ let gas_token = setup_gas_token(&env, &sender);
+ let salt = BytesN::<32>::from_array(&env, &[1; 32]);
+
+ let destination_chain = String::from_str(&env, "ethereum");
+
+ assert_contract_err!(
+ client.try_deploy_remote_interchain_token(&sender, &salt, &destination_chain, &gas_token),
+ ContractError::InvalidTokenId
+ );
+}
diff --git a/contracts/interchain-token-service/tests/executable.rs b/contracts/interchain-token-service/tests/executable.rs
index 1d986915..a676c235 100644
--- a/contracts/interchain-token-service/tests/executable.rs
+++ b/contracts/interchain-token-service/tests/executable.rs
@@ -118,7 +118,7 @@ mod test {
#[test]
fn interchain_transfer_execute_succeeds() {
- let (env, client, gateway_client, signers) = setup_env();
+ let (env, client, gateway_client, _, signers) = setup_env();
register_chains(&env, &client);
let executable_id = env.register(test::ExecutableContract, (client.address.clone(),));
@@ -175,7 +175,7 @@ fn interchain_transfer_execute_succeeds() {
#[test]
fn executable_fails_if_not_executed_from_its() {
- let (env, client, _, _) = setup_env();
+ let (env, client, _, _, _) = setup_env();
let executable_id = env.register(test::ExecutableContract, (client.address.clone(),));
let executable_client = test::ExecutableContractClient::new(&env, &executable_id);
diff --git a/contracts/interchain-token-service/tests/execute.rs b/contracts/interchain-token-service/tests/execute.rs
index 2581efb2..d9f99243 100644
--- a/contracts/interchain-token-service/tests/execute.rs
+++ b/contracts/interchain-token-service/tests/execute.rs
@@ -8,7 +8,7 @@ use utils::setup_env;
#[test]
#[should_panic(expected = "Error(Contract, #1)")] // ExecutableError::NotApproved
fn execute_fails_without_gateway_approval() {
- let (env, client, _, _) = setup_env();
+ let (env, client, _, _, _) = setup_env();
let source_chain = String::from_str(&env, "chain");
let message_id = String::from_str(&env, "test");
@@ -21,7 +21,7 @@ fn execute_fails_without_gateway_approval() {
#[test]
#[should_panic(expected = "Error(Contract, #8)")] // ContractError::InsufficientMessageLength
fn execute_fails_with_invalid_message() {
- let (env, client, gateway_client, signers) = setup_env();
+ let (env, client, gateway_client, _, signers) = setup_env();
let source_chain = client.its_hub_chain_name();
let message_id = String::from_str(&env, "test");
diff --git a/contracts/interchain-token-service/tests/interchain_transfer.rs b/contracts/interchain-token-service/tests/interchain_transfer.rs
index 6cbd5673..3e64eadd 100644
--- a/contracts/interchain-token-service/tests/interchain_transfer.rs
+++ b/contracts/interchain-token-service/tests/interchain_transfer.rs
@@ -14,7 +14,7 @@ use utils::{register_chains, setup_env, setup_gas_token, setup_its_token, HUB_CH
#[test]
fn interchain_transfer_send_succeeds() {
- let (env, client, _, _) = setup_env();
+ let (env, client, _, _, _) = setup_env();
let sender: Address = Address::generate(&env);
let gas_token = setup_gas_token(&env, &sender);
@@ -47,7 +47,7 @@ fn interchain_transfer_send_succeeds() {
#[test]
#[should_panic(expected = "burn, Error(Contract, #9)")]
fn interchain_transfer_send_fails_on_insufficient_balance() {
- let (env, client, _, _) = setup_env();
+ let (env, client, _, _, _) = setup_env();
register_chains(&env, &client);
let sender: Address = Address::generate(&env);
@@ -72,7 +72,7 @@ fn interchain_transfer_send_fails_on_insufficient_balance() {
#[test]
fn interchain_transfer_receive_succeeds() {
- let (env, client, gateway_client, signers) = setup_env();
+ let (env, client, gateway_client, _, signers) = setup_env();
register_chains(&env, &client);
let sender = Address::generate(&env).to_xdr(&env);
diff --git a/contracts/interchain-token-service/tests/message_routing.rs b/contracts/interchain-token-service/tests/message_routing.rs
index b3187f19..86424548 100644
--- a/contracts/interchain-token-service/tests/message_routing.rs
+++ b/contracts/interchain-token-service/tests/message_routing.rs
@@ -6,7 +6,7 @@ use utils::{register_chains, setup_env, setup_gas_token, setup_its_token};
#[test]
fn send_directly_to_hub_chain_fails() {
- let (env, client, _gateway_client, _) = setup_env();
+ let (env, client, _gateway_client, _, _) = setup_env();
let sender: Address = Address::generate(&env);
let amount = 1;
@@ -29,7 +29,7 @@ fn send_directly_to_hub_chain_fails() {
#[test]
fn send_to_untrusted_chain_fails() {
- let (env, client, _gateway_client, _) = setup_env();
+ let (env, client, _gateway_client, _, _) = setup_env();
register_chains(&env, &client);
let sender: Address = Address::generate(&env);
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
new file mode 100644
index 00000000..1f090518
--- /dev/null
+++ b/contracts/interchain-token-service/tests/testdata/deploy_interchain_token_succeeds.golden
@@ -0,0 +1,3 @@
+contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5)
+topics: (Symbol(interchain_token_deployed), BytesN<32>(127, 197, 48, 155, 232, 144, 233, 5, 104, 135, 130, 118, 76, 97, 230, 164, 117, 144, 45, 242, 69, 90, 124, 72, 93, 71, 115, 62, 188, 31, 2, 167), Contract(CBXOPW23I3THDRTZG2QKYC3WFGQ2Y5BEU2OAPTNLCLFFXQE3BEUJKBOQ), String(name), String(symbol), 6, Some(Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5)))
+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
new file mode 100644
index 00000000..23dcca6d
--- /dev/null
+++ b/contracts/interchain-token-service/tests/testdata/deploy_remote_interchain_token_succeeds.golden
@@ -0,0 +1,3 @@
+contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5)
+topics: (Symbol(token_deployment_started), BytesN<32>(127, 197, 48, 155, 232, 144, 233, 5, 104, 135, 130, 118, 76, 97, 230, 164, 117, 144, 45, 242, 69, 90, 124, 72, 93, 71, 115, 62, 188, 31, 2, 167), Contract(CBXOPW23I3THDRTZG2QKYC3WFGQ2Y5BEU2OAPTNLCLFFXQE3BEUJKBOQ), String(ethereum), String(name), String(symbol), 6, None)
+data: ()
\ No newline at end of file
diff --git a/contracts/interchain-token-service/tests/testdata/interchain_token.wasm b/contracts/interchain-token-service/tests/testdata/interchain_token.wasm
index b7e0927a..20da8008 100755
Binary files a/contracts/interchain-token-service/tests/testdata/interchain_token.wasm and b/contracts/interchain-token-service/tests/testdata/interchain_token.wasm differ
diff --git a/contracts/interchain-token-service/tests/trusted_chain.rs b/contracts/interchain-token-service/tests/trusted_chain.rs
index 1a7ad828..6faf8939 100644
--- a/contracts/interchain-token-service/tests/trusted_chain.rs
+++ b/contracts/interchain-token-service/tests/trusted_chain.rs
@@ -12,7 +12,7 @@ use soroban_sdk::{Address, String};
#[test]
fn set_trusted_address() {
- let (env, client, _, _) = setup_env();
+ let (env, client, _, _, _) = setup_env();
let chain = String::from_str(&env, "chain");
@@ -25,7 +25,7 @@ fn set_trusted_address() {
#[test]
fn set_trusted_chain_fails_if_not_owner() {
- let (env, client, _, _) = setup_env();
+ let (env, client, _, _, _) = setup_env();
let not_owner = Address::generate(&env);
let chain = String::from_str(&env, "chain");
@@ -35,7 +35,7 @@ fn set_trusted_chain_fails_if_not_owner() {
#[test]
fn set_trusted_chain_fails_if_already_set() {
- let (env, client, _, _) = setup_env();
+ let (env, client, _, _, _) = setup_env();
env.mock_all_auths();
let chain = String::from_str(&env, "chain");
@@ -49,7 +49,7 @@ fn set_trusted_chain_fails_if_already_set() {
#[test]
fn remove_trusted_chain() {
- let (env, client, _, _) = setup_env();
+ let (env, client, _, _, _) = setup_env();
let chain = String::from_str(&env, "chain");
@@ -66,7 +66,7 @@ fn remove_trusted_chain() {
#[test]
fn remove_trusted_chain_fails_if_not_set() {
- let (env, client, _, _) = setup_env();
+ let (env, client, _, _, _) = setup_env();
env.mock_all_auths();
let chain = String::from_str(&env, "chain");
diff --git a/contracts/interchain-token-service/tests/utils/mod.rs b/contracts/interchain-token-service/tests/utils/mod.rs
index 86b39b27..c60b97c0 100644
--- a/contracts/interchain-token-service/tests/utils/mod.rs
+++ b/contracts/interchain-token-service/tests/utils/mod.rs
@@ -3,8 +3,8 @@ use axelar_gateway::testutils::{setup_gateway, TestSignerSet};
use axelar_gateway::AxelarGatewayClient;
use axelar_soroban_std::types::Token;
use interchain_token_service::{InterchainTokenService, InterchainTokenServiceClient};
-use soroban_sdk::BytesN;
use soroban_sdk::{testutils::Address as _, token::StellarAssetClient, Address, Env, String};
+use soroban_sdk::{BytesN, IntoVal};
use soroban_token_sdk::metadata::TokenMetadata;
pub const HUB_CHAIN: &str = "axelar";
@@ -54,16 +54,17 @@ pub fn setup_env<'a>() -> (
Env,
InterchainTokenServiceClient<'a>,
AxelarGatewayClient<'a>,
+ AxelarGasServiceClient<'a>,
TestSignerSet,
) {
let env = Env::default();
let (signers, gateway_client) = setup_gateway(&env, 0, 5);
- let gas_service_client = setup_gas_service(&env);
+ let gas_service_client: AxelarGasServiceClient<'_> = setup_gas_service(&env);
let client = setup_its(&env, &gateway_client, &gas_service_client);
- (env, client, gateway_client, signers)
+ (env, client, gateway_client, gas_service_client, signers)
}
#[allow(dead_code)]
@@ -90,7 +91,7 @@ pub fn setup_its_token(
supply: i128,
) -> BytesN<32> {
let salt = BytesN::from_array(env, &[1u8; 32]);
- let token_meta_data = TokenMetadata {
+ let token_metadata = TokenMetadata {
name: String::from_str(env, "Test"),
symbol: String::from_str(env, "TEST"),
decimal: 18,
@@ -99,7 +100,7 @@ pub fn setup_its_token(
let token_id = client.mock_all_auths().deploy_interchain_token(
sender,
&salt,
- &token_meta_data,
+ &token_metadata,
&supply,
&None,
);
@@ -112,3 +113,18 @@ pub fn register_chains(env: &Env, client: &InterchainTokenServiceClient) {
let chain = String::from_str(env, HUB_CHAIN);
client.mock_all_auths().set_trusted_chain(&chain);
}
+
+#[allow(dead_code)]
+pub trait TokenMetadataExt {
+ fn new(env: &Env, name: &str, symbol: &str, decimal: u32) -> Self;
+}
+
+impl TokenMetadataExt for TokenMetadata {
+ fn new(env: &Env, name: &str, symbol: &str, decimal: u32) -> Self {
+ Self {
+ decimal,
+ name: name.into_val(env),
+ symbol: symbol.into_val(env),
+ }
+ }
+}
diff --git a/contracts/interchain-token/src/contract.rs b/contracts/interchain-token/src/contract.rs
index 5acbb0bf..81f82844 100644
--- a/contracts/interchain-token/src/contract.rs
+++ b/contracts/interchain-token/src/contract.rs
@@ -1,3 +1,4 @@
+use axelar_soroban_std::token::validate_token_metadata;
use axelar_soroban_std::ttl::{
extend_instance_ttl, INSTANCE_TTL_EXTEND_TO, INSTANCE_TTL_THRESHOLD,
};
@@ -31,15 +32,15 @@ impl InterchainToken {
owner: Address,
minter: Option,
token_id: BytesN<32>,
- token_meta_data: TokenMetadata,
+ token_metadata: TokenMetadata,
) {
interfaces::set_owner(&env, &owner);
- if let Err(err) = Self::validate_token_metadata(token_meta_data.clone()) {
+ if let Err(err) = validate_token_metadata(token_metadata.clone()) {
panic_with_error!(env, err);
}
- Self::write_metadata(&env, token_meta_data);
+ Self::write_metadata(&env, token_metadata);
env.storage().instance().set(&DataKey::TokenId, &token_id);
@@ -234,19 +235,6 @@ impl InterchainToken {
assert_with_error!(env, amount >= 0, ContractError::InvalidAmount);
}
- fn validate_token_metadata(
- TokenMetadata {
- decimal,
- name,
- symbol,
- }: TokenMetadata,
- ) -> Result<(), ContractError> {
- ensure!(decimal <= u8::MAX.into(), ContractError::InvalidDecimal);
- ensure!(!name.is_empty(), ContractError::InvalidTokenName);
- ensure!(!symbol.is_empty(), ContractError::InvalidTokenSymbol);
- Ok(())
- }
-
fn extend_balance_ttl(env: &Env, key: &DataKey) {
env.storage()
.persistent()
diff --git a/contracts/interchain-token/tests/test.rs b/contracts/interchain-token/tests/test.rs
index 371857ca..8f05dce2 100644
--- a/contracts/interchain-token/tests/test.rs
+++ b/contracts/interchain-token/tests/test.rs
@@ -24,11 +24,11 @@ fn setup_token<'a>(env: &Env) -> (InterchainTokenClient<'a>, Address, Address) {
let owner = Address::generate(env);
let minter = Address::generate(env);
let token_id: BytesN<32> = BytesN::<32>::random(env);
- let token_meta_data = setup_token_metadata(env, "name", "symbol", 6);
+ let token_metadata = setup_token_metadata(env, "name", "symbol", 6);
let contract_id = env.register(
InterchainToken,
- (owner.clone(), minter.clone(), &token_id, token_meta_data),
+ (owner.clone(), minter.clone(), &token_id, token_metadata),
);
let token = InterchainTokenClient::new(env, &contract_id);
@@ -42,9 +42,9 @@ fn register_token_with_invalid_decimals_fails() {
let owner = Address::generate(&env);
let minter = Address::generate(&env);
let token_id: BytesN<32> = BytesN::<32>::random(&env);
- let token_meta_data = setup_token_metadata(&env, "name", "symbol", u32::from(u8::MAX) + 1);
+ let token_metadata = setup_token_metadata(&env, "name", "symbol", u32::from(u8::MAX) + 1);
- env.register(InterchainToken, (owner, minter, &token_id, token_meta_data));
+ env.register(InterchainToken, (owner, minter, &token_id, token_metadata));
}
#[test]
@@ -54,9 +54,9 @@ fn register_token_with_invalid_name_fails() {
let owner = Address::generate(&env);
let minter = Address::generate(&env);
let token_id: BytesN<32> = BytesN::<32>::random(&env);
- let token_meta_data = setup_token_metadata(&env, "", "symbol", 1);
+ let token_metadata = setup_token_metadata(&env, "", "symbol", 1);
- env.register(InterchainToken, (owner, minter, &token_id, token_meta_data));
+ env.register(InterchainToken, (owner, minter, &token_id, token_metadata));
}
#[test]
@@ -66,9 +66,9 @@ fn register_token_with_invalid_symbol_fails() {
let owner = Address::generate(&env);
let minter = Address::generate(&env);
let token_id: BytesN<32> = BytesN::<32>::random(&env);
- let token_meta_data = setup_token_metadata(&env, "name", "", 1);
+ let token_metadata = setup_token_metadata(&env, "name", "", 1);
- env.register(InterchainToken, (owner, minter, &token_id, token_meta_data));
+ env.register(InterchainToken, (owner, minter, &token_id, token_metadata));
}
#[test]
@@ -88,12 +88,12 @@ fn register_interchain_token_without_minter() {
let owner = Address::generate(&env);
let token_id: BytesN<32> = BytesN::<32>::random(&env);
- let token_meta_data = setup_token_metadata(&env, "name", "symbol", 6);
+ let token_metadata = setup_token_metadata(&env, "name", "symbol", 6);
let minter: Option = None;
let contract_id = env.register(
InterchainToken,
- (owner.clone(), minter, &token_id, token_meta_data),
+ (owner.clone(), minter, &token_id, token_metadata),
);
let token = InterchainTokenClient::new(&env, &contract_id);
diff --git a/packages/axelar-soroban-std/Cargo.toml b/packages/axelar-soroban-std/Cargo.toml
index 62568a3b..9f80c7c9 100644
--- a/packages/axelar-soroban-std/Cargo.toml
+++ b/packages/axelar-soroban-std/Cargo.toml
@@ -13,6 +13,7 @@ crate-type = ["rlib"]
axelar-soroban-std-derive = { workspace = true, optional = true }
hex = { workspace = true, optional = true }
soroban-sdk = { workspace = true }
+soroban-token-sdk = { workspace = true }
[dev-dependencies]
axelar-soroban-std-derive = { workspace = true }
diff --git a/packages/axelar-soroban-std/src/lib.rs b/packages/axelar-soroban-std/src/lib.rs
index ee0ec193..ef13850d 100644
--- a/packages/axelar-soroban-std/src/lib.rs
+++ b/packages/axelar-soroban-std/src/lib.rs
@@ -24,5 +24,7 @@ pub mod interfaces;
pub mod address;
+pub mod token;
+
#[cfg(feature = "derive")]
pub use axelar_soroban_std_derive::*;
diff --git a/packages/axelar-soroban-std/src/token.rs b/packages/axelar-soroban-std/src/token.rs
new file mode 100644
index 00000000..dca3b01d
--- /dev/null
+++ b/packages/axelar-soroban-std/src/token.rs
@@ -0,0 +1,28 @@
+use crate::ensure;
+use soroban_sdk::contracterror;
+use soroban_token_sdk::metadata::TokenMetadata;
+
+#[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(())
+}