Skip to content

Commit

Permalink
refactor(interchain-token-service): cleanup its execute handlers (#157)
Browse files Browse the repository at this point in the history
Co-authored-by: Attiss Ngo <[email protected]>
Co-authored-by: ahramy <[email protected]>
  • Loading branch information
3 people authored Jan 18, 2025
1 parent 87cb759 commit 1a5876d
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 296 deletions.
193 changes: 99 additions & 94 deletions contracts/interchain-token-service/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ 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};
Expand All @@ -23,6 +22,7 @@ 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,
};
Expand Down Expand Up @@ -240,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,
Expand Down Expand Up @@ -474,100 +476,11 @@ impl InterchainTokenService {
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_xdr(env, &destination_address)
.map_err(|_| ContractError::InvalidDestinationAddress)?;

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::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: 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_xdr(env, &m))
.transpose()
.map_err(|_| ContractError::InvalidMinter)?;

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::InterchainTransfer(message) => {
Self::execute_transfer_message(env, &source_chain, message_id, message)
}
};
Message::DeployInterchainToken(message) => Self::execute_deploy_message(env, message),
}?;

extend_persistent_ttl(env, &DataKey::TrustedChain(source_chain));
extend_instance_ttl(env);
Expand Down Expand Up @@ -748,4 +661,96 @@ 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_xdr(env, &destination_address)
.map_err(|_| ContractError::InvalidDestinationAddress)?;

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_xdr(env, &m))
.transpose()
.map_err(|_| ContractError::InvalidMinter)?;

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(())
}
}
5 changes: 4 additions & 1 deletion contracts/interchain-token-service/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ pub enum ContractError {
NotApproved = 22,
InvalidDestinationChain = 23,
InvalidData = 24,
TokenAlreadyRegistered = 25,
InvalidTokenName = 25,
InvalidTokenSymbol = 26,
InvalidTokenDecimals = 27,
TokenAlreadyRegistered = 28,
}

impl_not_approved_error!(ContractError);
53 changes: 40 additions & 13 deletions contracts/interchain-token-service/src/token_metadata.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,51 @@
use soroban_sdk::{token, Address, Env, String};
use soroban_token_sdk::metadata::TokenMetadata;
use stellar_axelar_std::ensure;
use stellar_axelar_std::token::validate_token_metadata;

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<Self, ContractError>;

fn validate(&self) -> Result<(), ContractError>;
}

impl TokenMetadataExt for TokenMetadata {
fn new(name: String, symbol: String, decimals: u32) -> Result<Self, ContractError> {
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,
Expand All @@ -32,16 +70,5 @@ pub fn token_metadata(
(name, symbol)
};

let token_metadata = TokenMetadata {
name,
symbol,
decimal: decimals,
};

ensure!(
validate_token_metadata(&token_metadata).is_ok(),
ContractError::InvalidTokenMetaData
);

Ok(token_metadata)
TokenMetadata::new(name, symbol, decimals)
}
Original file line number Diff line number Diff line change
Expand Up @@ -193,25 +193,58 @@ 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<Address> = None;
let salt = BytesN::<32>::from_array(&env, &[1; 32]);
let invalid_decimals = (u8::MAX) as u32 + 1;
let token_metadata = TokenMetadata::new(&env, "Test", "TEST", 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);
Expand All @@ -223,6 +256,6 @@ fn deploy_interchain_token_fails_with_invalid_auth() {

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)
);
}
Loading

0 comments on commit 1a5876d

Please sign in to comment.