Skip to content

Commit

Permalink
feat(example): add interchain-token-service (#149)
Browse files Browse the repository at this point in the history
Co-authored-by: Milap Sheth <[email protected]>
  • Loading branch information
nbayindirli and milapsheth authored Jan 21, 2025
1 parent d753e51 commit b7784a7
Show file tree
Hide file tree
Showing 16 changed files with 430 additions and 92 deletions.
4 changes: 4 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 @@ -15,6 +15,7 @@ alloy-sol-types = { version = "0.8.19", default-features = false, features = [
"std",
] }
cfg-if = { version = "1.0", default-features = false }
example = { version = "^0.1.0", path = "contracts/example" }
goldie = { version = "0.5.0", default-features = false }
hex = { version = "0.4", default-features = false }
paste = { version = "1.0", default-features = false }
Expand Down
9 changes: 9 additions & 0 deletions contracts/example/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,26 @@ edition = { workspace = true }
[lib]
crate-type = ["cdylib", "rlib"]

[features]
testutils = ["stellar-axelar-std/testutils"]

[dependencies]
soroban-sdk = { workspace = true }
soroban-token-sdk = { workspace = true }
stellar-axelar-gas-service = { workspace = true, features = ["library"] }
stellar-axelar-gateway = { workspace = true, features = ["library"] }
stellar-axelar-std = { workspace = true }
stellar-interchain-token-service = { workspace = true, features = ["library"] }

[dev-dependencies]
example = { workspace = true, features = ["testutils"] }
goldie = { workspace = true }
soroban-sdk = { workspace = true, features = ["testutils"] }
soroban-token-sdk = { workspace = true }
stellar-axelar-gas-service = { workspace = true, features = ["testutils"] }
stellar-axelar-gateway = { workspace = true, features = ["testutils"] }
stellar-axelar-std = { workspace = true, features = ["testutils"] }
stellar-interchain-token-service = { workspace = true, features = ["testutils"] }

[lints]
workspace = true
133 changes: 123 additions & 10 deletions contracts/example/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
use soroban_sdk::{contract, contracterror, contractimpl, Address, Bytes, Env, String};
use soroban_sdk::{
contract, contracterror, contractimpl, token, Address, Bytes, BytesN, Env, String,
};
use stellar_axelar_gas_service::AxelarGasServiceClient;
use stellar_axelar_gateway::executable::{AxelarExecutableInterface, NotApprovedError};
use stellar_axelar_gateway::{impl_not_approved_error, AxelarGatewayMessagingClient};
use stellar_axelar_std::events::Event;
use stellar_axelar_std::types::Token;
use stellar_axelar_std::{ensure, InterchainTokenExecutable};
use stellar_interchain_token_service::executable::CustomInterchainTokenExecutable;
use stellar_interchain_token_service::InterchainTokenServiceClient;

use crate::event;
use crate::event::{ExecutedEvent, TokenReceivedEvent, TokenSentEvent};
use crate::storage_types::DataKey;

#[contract]
#[derive(InterchainTokenExecutable)]
pub struct Example;

#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum ExampleError {
NotApproved = 1,
InvalidItsAddress = 2,
InvalidAmount = 3,
}

impl_not_approved_error!(ExampleError);
Expand All @@ -24,7 +33,10 @@ impl AxelarExecutableInterface for Example {
type Error = ExampleError;

fn gateway(env: &Env) -> Address {
env.storage().instance().get(&DataKey::Gateway).unwrap()
env.storage()
.instance()
.get(&DataKey::Gateway)
.expect("gateway not found")
}

fn execute(
Expand All @@ -36,34 +48,98 @@ impl AxelarExecutableInterface for Example {
) -> Result<(), ExampleError> {
Self::validate_message(&env, &source_chain, &message_id, &source_address, &payload)?;

event::executed(&env, source_chain, message_id, source_address, payload);
ExecutedEvent {
source_chain,
message_id,
source_address,
payload,
}
.emit(&env);

Ok(())
}
}

impl CustomInterchainTokenExecutable for Example {
type Error = ExampleError;

fn __interchain_token_service(env: &Env) -> Address {
env.storage()
.instance()
.get(&DataKey::InterchainTokenService)
.expect("ITS not found")
}

fn __authorized_execute_with_token(
env: &Env,
source_chain: String,
message_id: String,
source_address: Bytes,
payload: Bytes,
token_id: BytesN<32>,
token_address: Address,
amount: i128,
) -> Result<(), Self::Error> {
ensure!(amount >= 0, ExampleError::InvalidAmount);

let destination_address = Address::from_string_bytes(&payload);

let token = token::TokenClient::new(env, &token_address);
token.transfer(
&env.current_contract_address(),
&destination_address,
&amount,
);

TokenReceivedEvent {
source_chain,
message_id,
source_address,
token_id,
token_address,
amount,
payload,
}
.emit(env);

Ok(())
}
}

#[contractimpl]
impl Example {
pub fn __constructor(env: Env, gateway: Address, gas_service: Address) {
pub fn __constructor(
env: &Env,
gateway: Address,
gas_service: Address,
interchain_token_service: Address,
) {
env.storage().instance().set(&DataKey::Gateway, &gateway);
env.storage()
.instance()
.set(&DataKey::GasService, &gas_service);
env.storage()
.instance()
.set(&DataKey::InterchainTokenService, &interchain_token_service);
}

pub fn gas_service(env: &Env) -> Address {
env.storage().instance().get(&DataKey::GasService).unwrap()
env.storage()
.instance()
.get(&DataKey::GasService)
.expect("gas service not found")
}

pub fn send(
env: Env,
env: &Env,
caller: Address,
destination_chain: String,
destination_address: String,
message: Bytes,
gas_token: Token,
) {
let gateway = AxelarGatewayMessagingClient::new(&env, &Self::gateway(&env));
let gas_service = AxelarGasServiceClient::new(&env, &Self::gas_service(&env));
let gateway = AxelarGatewayMessagingClient::new(env, &Self::gateway(env));
let gas_service = AxelarGasServiceClient::new(env, &Self::gas_service(env));

caller.require_auth();

Expand All @@ -74,7 +150,7 @@ impl Example {
&message,
&caller,
&gas_token,
&Bytes::new(&env),
&Bytes::new(env),
);

gateway.call_contract(
Expand All @@ -84,4 +160,41 @@ impl Example {
&message,
);
}

pub fn send_token(
env: &Env,
caller: Address,
token_id: BytesN<32>,
destination_chain: String,
destination_app_contract: Bytes,
amount: i128,
recipient: Option<Bytes>,
gas_token: Token,
) -> Result<(), ExampleError> {
caller.require_auth();

let client = InterchainTokenServiceClient::new(env, &Self::interchain_token_service(env));

client.interchain_transfer(
&caller,
&token_id,
&destination_chain,
&destination_app_contract,
&amount,
&recipient,
&gas_token,
);

TokenSentEvent {
sender: caller,
token_id,
destination_chain,
destination_app_contract,
amount,
recipient,
}
.emit(env);

Ok(())
}
}
47 changes: 32 additions & 15 deletions contracts/example/src/event.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,34 @@
use soroban_sdk::{Bytes, Env, String, Symbol};
use soroban_sdk::{Address, Bytes, BytesN, String};
use stellar_axelar_std::IntoEvent;

pub fn executed(
env: &Env,
source_chain: String,
message_id: String,
source_address: String,
payload: Bytes,
) {
let topics = (
Symbol::new(env, "executed"),
source_chain,
message_id,
source_address,
);
env.events().publish(topics, (payload,));
#[derive(Debug, PartialEq, Eq, IntoEvent)]
pub struct ExecutedEvent {
pub source_chain: String,
pub message_id: String,
pub source_address: String,
#[data]
pub payload: Bytes,
}

#[derive(Debug, PartialEq, Eq, IntoEvent)]
pub struct TokenReceivedEvent {
pub source_chain: String,
pub message_id: String,
pub source_address: Bytes,
pub token_id: BytesN<32>,
pub token_address: Address,
pub amount: i128,
#[data]
pub payload: Bytes,
}

#[derive(Debug, PartialEq, Eq, IntoEvent)]
pub struct TokenSentEvent {
pub sender: Address,
pub token_id: BytesN<32>,
pub destination_chain: String,
pub destination_app_contract: Bytes,
pub amount: i128,
#[data]
pub recipient: Option<Bytes>,
}
5 changes: 4 additions & 1 deletion contracts/example/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#![no_std]

#[cfg(any(test, feature = "testutils"))]
extern crate std;

mod contract;
mod event;
pub mod event;
mod storage_types;

pub use contract::{Example, ExampleClient};
1 change: 1 addition & 0 deletions contracts/example/src/storage_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ use soroban_sdk::contracttype;
pub enum DataKey {
Gateway,
GasService,
InterchainTokenService,
}
Loading

0 comments on commit b7784a7

Please sign in to comment.