Skip to content

Commit

Permalink
feat(interchain-token-service): register canonical interchain token (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
AttissNgo authored Dec 16, 2024
1 parent daf70ec commit 7cce18f
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 5 deletions.
64 changes: 60 additions & 4 deletions contracts/interchain-token-service/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use soroban_token_sdk::metadata::TokenMetadata;
use crate::abi::{get_message_type, MessageType as EncodedMessageType};
use crate::error::ContractError;
use crate::event::{
InterchainTransferReceivedEvent, InterchainTransferSentEvent, TrustedChainRemovedEvent,
TrustedChainSetEvent,
InterchainTokenIdClaimedEvent, InterchainTransferReceivedEvent, InterchainTransferSentEvent,
TrustedChainRemovedEvent, TrustedChainSetEvent,
};
use crate::executable::InterchainTokenExecutableClient;
use crate::interface::InterchainTokenServiceInterface;
Expand All @@ -25,6 +25,7 @@ use crate::types::{HubMessage, InterchainTransfer, Message, TokenManagerType};
const ITS_HUB_CHAIN_NAME: &str = "axelar";
const PREFIX_INTERCHAIN_TOKEN_ID: &str = "its-interchain-token-id";
const PREFIX_INTERCHAIN_TOKEN_SALT: &str = "interchain-token-salt";
const PREFIX_CANONICAL_TOKEN_SALT: &str = "canonical-token-salt";

#[contract]
#[derive(Ownable, Upgradable)]
Expand Down Expand Up @@ -134,8 +135,7 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
}

fn interchain_token_deploy_salt(env: &Env, deployer: Address, salt: BytesN<32>) -> BytesN<32> {
let chain_name = Self::chain_name(env);
let chain_name_hash: BytesN<32> = env.crypto().keccak256(&(chain_name).to_xdr(env)).into();
let chain_name_hash = Self::chain_name_hash(env);
env.crypto()
.keccak256(
&(
Expand Down Expand Up @@ -295,6 +295,50 @@ 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<BytesN<32>, 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<BytesN<32>, 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]
Expand Down Expand Up @@ -478,4 +522,16 @@ impl InterchainTokenService {
.get(&DataKey::TokenIdConfigKey(token_id))
.expect("token id config not found")
}

fn chain_name_hash(env: &Env) -> BytesN<32> {
let chain_name = Self::chain_name(env);
env.crypto().keccak256(&chain_name.to_xdr(env)).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()
}
}
1 change: 1 addition & 0 deletions contracts/interchain-token-service/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ pub enum ContractError {
InvalidMinter = 12,
InvalidDestinationAddress = 13,
InvalidHubChain = 14,
TokenAlreadyRegistered = 15,
}
31 changes: 30 additions & 1 deletion contracts/interchain-token-service/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ pub struct TrustedChainRemovedEvent {
pub chain: String,
}

#[derive(Debug, PartialEq, Eq)]
pub struct InterchainTokenIdClaimedEvent {
pub token_id: BytesN<32>,
pub deployer: Address,
pub salt: BytesN<32>,
}

#[derive(Debug, PartialEq, Eq)]
pub struct InterchainTransferSentEvent {
pub token_id: BytesN<32>,
Expand Down Expand Up @@ -56,6 +63,21 @@ impl Event for TrustedChainRemovedEvent {
}
}

impl Event for InterchainTokenIdClaimedEvent {
fn topics(&self, env: &Env) -> impl Topics + Debug {
(
Symbol::new(env, "interchain_token_id_claimed"),
self.token_id.to_val(),
self.deployer.to_val(),
self.salt.to_val(),
)
}

fn data(&self, env: &Env) -> impl IntoVal<Env, Val> + Debug {
Vec::<Val>::new(env)
}
}

impl Event for InterchainTransferSentEvent {
fn topics(&self, env: &Env) -> impl Topics + Debug {
(
Expand All @@ -77,7 +99,7 @@ impl Event for InterchainTransferReceivedEvent {
fn topics(&self, env: &Env) -> impl Topics + Debug {
(
Symbol::new(env, "interchain_transfer_received"),
self.source_chain.as_val(),
self.source_chain.to_val(),
self.token_id.to_val(),
self.source_address.to_val(),
self.destination_address.to_val(),
Expand All @@ -99,6 +121,13 @@ 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!(
InterchainTokenIdClaimedEvent,
(Symbol, BytesN<32>, Address, BytesN<32>),
()
);

#[cfg(any(test, feature = "testutils"))]
impl_event_testutils!(
InterchainTransferSentEvent,
Expand Down
5 changes: 5 additions & 0 deletions contracts/interchain-token-service/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,9 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface {
metadata: Option<Bytes>,
gas_token: Token,
) -> Result<(), ContractError>;

fn register_canonical_token(
env: &Env,
token_address: Address,
) -> Result<BytesN<32>, ContractError>;
}
86 changes: 86 additions & 0 deletions contracts/interchain-token-service/tests/canonical_token.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
mod utils;

use axelar_soroban_std::{address::AddressExt, assert_contract_err, events};
use interchain_token_service::{
error::ContractError, event::InterchainTokenIdClaimedEvent, types::TokenManagerType,
};
use soroban_sdk::{testutils::Address as _, xdr::ToXdr, Address, BytesN};
use utils::setup_env;

const PREFIX_CANONICAL_TOKEN_SALT: &str = "canonical-token-salt";

#[test]
fn register_canonical_token_succeeds() {
let (env, client, _, _) = setup_env();
let token_address = Address::generate(&env);

let chain_name = client.chain_name();
let chain_name_hash: BytesN<32> = env.crypto().keccak256(&(chain_name).to_xdr(&env)).into();
let expected_deploy_salt = env
.crypto()
.keccak256(
&(
PREFIX_CANONICAL_TOKEN_SALT,
chain_name_hash,
token_address.clone(),
)
.to_xdr(&env),
)
.into();
let expected_id = client.interchain_token_id(&Address::zero(&env), &expected_deploy_salt);

assert_eq!(client.register_canonical_token(&token_address), expected_id);

assert_eq!(client.token_address(&expected_id), token_address);

assert_eq!(
client.token_manager_type(&expected_id),
TokenManagerType::LockUnlock
);

goldie::assert!(events::fmt_last_emitted_event::<
InterchainTokenIdClaimedEvent,
>(&env));
}

#[test]
fn register_canonical_token_fails_if_already_registered() {
let (env, client, _, _) = setup_env();
let token_address = Address::generate(&env);

client.register_canonical_token(&token_address);

assert_contract_err!(
client.try_register_canonical_token(&token_address),
ContractError::TokenAlreadyRegistered
);
}

#[test]
fn canonical_token_id_derivation() {
let (env, client, _, _) = setup_env();
let token_address = Address::generate(&env);

let chain_name = client.chain_name();
let chain_name_hash: BytesN<32> = env.crypto().keccak256(&(chain_name).to_xdr(&env)).into();

let deploy_salt = env
.crypto()
.keccak256(
&(
PREFIX_CANONICAL_TOKEN_SALT,
chain_name_hash.clone(),
token_address,
)
.to_xdr(&env),
)
.into();

let token_id = client.interchain_token_id(&Address::zero(&env), &deploy_salt);

goldie::assert_json!(vec![
hex::encode(chain_name_hash.to_array()),
hex::encode(deploy_salt.to_array()),
hex::encode(token_id.to_array())
]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
"bdfb629dd56a9581bfba5ac25009cef0ee7646acbbd1880e99d7d26ea68f0885",
"007a506f6c21cb0e84f844a86f9a8a86be868f99f35015c78ba868a7b66afc6d",
"25c162306962b7a6d3ca7e65974ca39afa329e1cea9ed3d3cdaa70e38475a73a"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5)
topics: (Symbol(interchain_token_id_claimed), BytesN<32>(37, 193, 98, 48, 105, 98, 183, 166, 211, 202, 126, 101, 151, 76, 163, 154, 250, 50, 158, 28, 234, 158, 211, 211, 205, 170, 112, 227, 132, 117, 167, 58), AccountId(GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF), BytesN<32>(0, 122, 80, 111, 108, 33, 203, 14, 132, 248, 68, 168, 111, 154, 138, 134, 190, 134, 143, 153, 243, 80, 21, 199, 139, 168, 104, 167, 182, 106, 252, 109))
data: ()

0 comments on commit 7cce18f

Please sign in to comment.