Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(token-manager): add token manager for ITS #215

Merged
merged 12 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ stellar-axelar-std-derive = { version = "^0.2.1", path = "packages/stellar-axela
stellar-example = { version = "^0.1.0", path = "contracts/stellar-example" }
stellar-interchain-token = { version = "^0.2.2", path = "contracts/stellar-interchain-token" }
stellar-interchain-token-service = { version = "^0.2.2", path = "contracts/stellar-interchain-token-service" }
stellar-token-manager = { version = "^0.1.0", path = "contracts/stellar-token-manager" }

[workspace.lints.clippy]
nursery = { level = "warn", priority = -1 }
Expand Down
1 change: 1 addition & 0 deletions contracts/stellar-interchain-token-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ stellar-axelar-gas-service = { workspace = true, features = ["library"] }
stellar-axelar-gateway = { workspace = true, features = ["library"] }
stellar-axelar-std = { workspace = true }
stellar-interchain-token = { workspace = true, features = ["library"] }
stellar-token-manager = { workspace = true, features = ["library"] }

[dev-dependencies]
goldie = { workspace = true }
Expand Down
182 changes: 132 additions & 50 deletions contracts/stellar-interchain-token-service/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::error::ContractError;
use crate::event::{
InterchainTokenDeployedEvent, InterchainTokenDeploymentStartedEvent,
InterchainTokenIdClaimedEvent, InterchainTransferReceivedEvent, InterchainTransferSentEvent,
TrustedChainRemovedEvent, TrustedChainSetEvent,
TokenManagerDeployedEvent, TrustedChainRemovedEvent, TrustedChainSetEvent,
};
use crate::flow_limit::FlowDirection;
use crate::interface::InterchainTokenServiceInterface;
Expand All @@ -35,6 +35,7 @@ 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";
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
const PREFIX_CANONICAL_TOKEN_SALT: &str = "canonical-token-salt";
const PREFIX_TOKEN_MANAGER: &str = "token-manager-id";
ahramy marked this conversation as resolved.
Show resolved Hide resolved
const EXECUTE_WITH_TOKEN: &str = "execute_with_interchain_token";
milapsheth marked this conversation as resolved.
Show resolved Hide resolved

#[contract]
Expand All @@ -53,6 +54,7 @@ impl InterchainTokenService {
chain_name: String,
native_token_address: Address,
interchain_token_wasm_hash: BytesN<32>,
token_manager_wasm_hash: BytesN<32>,
) {
interfaces::set_owner(&env, &owner);
interfaces::set_operator(&env, &operator);
Expand All @@ -73,6 +75,9 @@ impl InterchainTokenService {
&DataKey::InterchainTokenWasmHash,
&interchain_token_wasm_hash,
);
env.storage()
.instance()
.set(&DataKey::TokenManagerWasmHash, &token_manager_wasm_hash);
}
}

Expand Down Expand Up @@ -117,6 +122,13 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
.expect("interchain token wasm hash not found")
}

fn token_manager_wasm_hash(env: &Env) -> BytesN<32> {
env.storage()
.instance()
.get(&DataKey::TokenManagerWasmHash)
.expect("token manager wasm hash not found")
}

fn is_trusted_chain(env: &Env, chain: String) -> bool {
env.storage()
.persistent()
Expand Down Expand Up @@ -191,6 +203,12 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
.token_address
}

fn token_manager(env: &Env, token_id: BytesN<32>) -> Address {
Self::token_id_config(env, token_id)
.expect("token id config not found")
.token_manager
}

fn token_manager_type(env: &Env, token_id: BytesN<32>) -> TokenManagerType {
Self::token_id_config(env, token_id)
.expect("token id config not found")
Expand Down Expand Up @@ -230,46 +248,38 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
) -> Result<BytesN<32>, ContractError> {
caller.require_auth();

let initial_minter = if initial_supply > 0 {
Some(env.current_contract_address())
} else if let Some(ref minter) = minter {
ensure!(
*minter != env.current_contract_address(),
ContractError::InvalidMinter
);
Some(minter.clone())
} else {
None
};

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(
let token_address =
Self::deploy_interchain_token_contract(env, minter, token_id.clone(), token_metadata);

let token_manager_type = TokenManagerType::NativeInterchainToken;
let token_manager_address = Self::deploy_token_manager_contract(
env,
initial_minter,
token_id.clone(),
token_metadata,
token_address.clone(),
token_manager_type,
);
let interchain_token_client = InterchainTokenClient::new(env, &token_address);

if initial_supply > 0 {
StellarAssetClient::new(env, &deployed_address).mint(&caller, &initial_supply);

if let Some(minter) = minter {
let token = InterchainTokenClient::new(env, &deployed_address);
token.remove_minter(&env.current_contract_address());
token.add_minter(&minter);
}
StellarAssetClient::new(env, &token_address).mint(&caller, &initial_supply);
}

// Transfer minter role to token manager
interchain_token_client.add_minter(&token_manager_address);
interchain_token_client.remove_minter(&env.current_contract_address());

Self::set_token_id_config(
env,
token_id.clone(),
TokenIdConfigValue {
token_address: deployed_address,
token_manager_type: TokenManagerType::NativeInterchainToken,
token_address,
token_manager: token_manager_address,
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
token_manager_type,
},
);

Expand Down Expand Up @@ -306,6 +316,14 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
ContractError::TokenAlreadyRegistered
);

let token_manager_type = TokenManagerType::LockUnlock;
let token_manager_address = Self::deploy_token_manager_contract(
env,
token_id.clone(),
token_address.clone(),
token_manager_type,
);

InterchainTokenIdClaimedEvent {
token_id: token_id.clone(),
deployer: Address::zero(env),
Expand All @@ -318,7 +336,8 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
token_id.clone(),
TokenIdConfigValue {
token_address,
token_manager_type: TokenManagerType::LockUnlock,
token_manager: token_manager_address,
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
token_manager_type,
},
);

Expand Down Expand Up @@ -577,6 +596,12 @@ impl InterchainTokenService {
env.crypto().keccak256(&chain_name.to_xdr(env)).into()
}

fn token_manager_salt(env: &Env, token_id: BytesN<32>) -> BytesN<32> {
env.crypto()
.keccak256(&(PREFIX_TOKEN_MANAGER, token_id).to_xdr(env))
.into()
}

/// Deploys a remote token on a specified destination chain.
///
/// This function authorizes the caller, retrieves the token's metadata,
Expand Down Expand Up @@ -672,6 +697,34 @@ impl InterchainTokenService {
deployed_address
}

fn deploy_token_manager_contract(
env: &Env,
token_id: BytesN<32>,
token_address: Address,
token_manager_type: TokenManagerType,
) -> Address {
let deployed_address = env
.deployer()
.with_address(
env.current_contract_address(),
Self::token_manager_salt(env, token_id.clone()),
)
.deploy_v2(
Self::token_manager_wasm_hash(env),
(env.current_contract_address(),),
);

TokenManagerDeployedEvent {
token_id,
token_manager_address: deployed_address.clone(),
token_address,
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
token_manager_type,
}
.emit(env);

deployed_address
}

fn execute_transfer_message(
env: &Env,
source_chain: &String,
Expand All @@ -687,15 +740,11 @@ impl InterchainTokenService {
let destination_address = Address::from_string_bytes(&destination_address);

let token_config_value = Self::token_id_config_with_extended_ttl(env, token_id.clone())?;
let token_address = token_config_value.token_address.clone();

FlowDirection::In.add_flow(env, token_id.clone(), amount)?;

token_handler::give_token(
env,
&destination_address,
token_config_value.clone(),
amount,
)?;
token_handler::give_token(env, &destination_address, token_config_value, amount)?;

InterchainTransferReceivedEvent {
source_chain: source_chain.clone(),
Expand All @@ -707,30 +756,50 @@ impl InterchainTokenService {
}
.emit(env);

let token_address = token_config_value.token_address;

if let Some(payload) = data {
let call_data = vec![
&env,
Self::execute_contract_with_token(
env,
destination_address,
source_chain,
message_id,
source_address,
payload,
token_id,
token_address,
amount,
);
}

Ok(())
}

fn execute_contract_with_token(
env: &Env,
destination_address: Address,
source_chain: &String,
message_id: String,
source_address: Bytes,
payload: Bytes,
token_id: BytesN<32>,
token_address: Address,
amount: i128,
) {
// Due to limitations of the soroban-sdk, there is no type-safe client for contract execution.
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
// The invocation will panic on error, so we can safely cast the return value to `()` and discard it.
env.invoke_contract::<()>(
&destination_address,
&Symbol::new(env, EXECUTE_WITH_TOKEN),
vec![
env,
source_chain.to_val(),
message_id.to_val(),
source_address.to_val(),
payload.to_val(),
token_id.to_val(),
token_address.to_val(),
amount.into_val(env),
];

// Due to limitations of the soroban-sdk, there is no type-safe client for contract execution.
// The invocation will panic on error, so we can safely cast the return value to `()` and discard it.
env.invoke_contract::<()>(
&destination_address,
&Symbol::new(env, EXECUTE_WITH_TOKEN),
call_data,
);
}

Ok(())
],
);
}

fn execute_deploy_message(
Expand All @@ -753,14 +822,27 @@ impl InterchainTokenService {
// 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 =
let token_address =
Self::deploy_interchain_token_contract(env, minter, token_id.clone(), token_metadata);

let token_manager_address = Self::deploy_token_manager_contract(
env,
token_id.clone(),
token_address.clone(),
TokenManagerType::NativeInterchainToken,
);

// Transfer minter role to token manager
let interchain_token_client = InterchainTokenClient::new(env, &token_address);
interchain_token_client.add_minter(&token_manager_address);
interchain_token_client.remove_minter(&env.current_contract_address());

Self::set_token_id_config(
env,
token_id,
TokenIdConfigValue {
token_address: deployed_address,
token_address,
token_manager: token_manager_address,
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
token_manager_type: TokenManagerType::NativeInterchainToken,
},
);
Expand Down
10 changes: 10 additions & 0 deletions contracts/stellar-interchain-token-service/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use core::fmt::Debug;
use soroban_sdk::{Address, Bytes, BytesN, String};
use stellar_axelar_std::IntoEvent;

use crate::types::TokenManagerType;

#[derive(Debug, PartialEq, Eq, IntoEvent)]
pub struct TrustedChainSetEvent {
pub chain: String,
Expand Down Expand Up @@ -30,6 +32,14 @@ pub struct InterchainTokenDeployedEvent {
pub minter: Option<Address>,
}

#[derive(Debug, PartialEq, Eq, IntoEvent)]
pub struct TokenManagerDeployedEvent {
pub token_id: BytesN<32>,
pub token_manager_address: Address,
pub token_address: Address,
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
pub token_manager_type: TokenManagerType,
}

#[derive(Debug, PartialEq, Eq, IntoEvent)]
#[event_name("token_deployment_started")]
pub struct InterchainTokenDeploymentStartedEvent {
Expand Down
6 changes: 6 additions & 0 deletions contracts/stellar-interchain-token-service/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ pub trait InterchainTokenServiceInterface:
/// Returns the WASM hash of the token contract used for deploying interchain tokens.
fn interchain_token_wasm_hash(env: &Env) -> BytesN<32>;

/// Returns the WASM hash of the token manager contract used for deploying token managers.
fn token_manager_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;

Expand Down Expand Up @@ -87,6 +90,9 @@ pub trait InterchainTokenServiceInterface:
/// Returns the address of the token associated with the specified token ID.
fn token_address(env: &Env, token_id: BytesN<32>) -> Address;

/// Returns the address of the token manager associated with the specified token ID.
fn token_manager(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;

Expand Down
Loading
Loading