From b7784a70ef4811b613b8a0f6263f63e864e159bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20B=2E=20=F0=9F=A5=82?= <15343884+nbayindirli@users.noreply.github.com> Date: Tue, 21 Jan 2025 12:55:41 -0800 Subject: [PATCH] feat(example): add interchain-token-service (#149) Co-authored-by: Milap Sheth --- Cargo.lock | 4 + Cargo.toml | 1 + contracts/example/Cargo.toml | 9 + contracts/example/src/contract.rs | 133 ++++++++- contracts/example/src/event.rs | 47 ++- contracts/example/src/lib.rs | 5 +- contracts/example/src/storage_types.rs | 1 + contracts/example/tests/test.rs | 277 ++++++++++++++---- .../example/tests/testdata/gmp_example.golden | 11 + .../tests/testdata/interchain_token.wasm | Bin .../example/tests/testdata/its_example.golden | 11 + contracts/interchain-token-service/src/lib.rs | 3 + .../src/testdata/interchain_token.wasm | Bin 0 -> 11825 bytes .../interchain-token-service/src/testutils.rs | 2 + .../tests/utils/mod.rs | 3 +- packages/axelar-std-derive/src/event.rs | 15 +- 16 files changed, 430 insertions(+), 92 deletions(-) create mode 100644 contracts/example/tests/testdata/gmp_example.golden rename contracts/{interchain-token-service => example}/tests/testdata/interchain_token.wasm (100%) create mode 100644 contracts/example/tests/testdata/its_example.golden create mode 100755 contracts/interchain-token-service/src/testdata/interchain_token.wasm create mode 100644 contracts/interchain-token-service/src/testutils.rs diff --git a/Cargo.lock b/Cargo.lock index 470a4c8d..dadfcb52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -846,10 +846,14 @@ checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" name = "example" version = "0.1.0" dependencies = [ + "example", + "goldie", "soroban-sdk", + "soroban-token-sdk", "stellar-axelar-gas-service", "stellar-axelar-gateway", "stellar-axelar-std", + "stellar-interchain-token-service", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d1fc1a83..2119b377 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } diff --git a/contracts/example/Cargo.toml b/contracts/example/Cargo.toml index 613ac5ca..f0e5e211 100644 --- a/contracts/example/Cargo.toml +++ b/contracts/example/Cargo.toml @@ -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 diff --git a/contracts/example/src/contract.rs b/contracts/example/src/contract.rs index 449f7ddd..e98208f2 100644 --- a/contracts/example/src/contract.rs +++ b/contracts/example/src/contract.rs @@ -1,13 +1,20 @@ -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] @@ -15,6 +22,8 @@ pub struct Example; #[repr(u32)] pub enum ExampleError { NotApproved = 1, + InvalidItsAddress = 2, + InvalidAmount = 3, } impl_not_approved_error!(ExampleError); @@ -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( @@ -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(); @@ -74,7 +150,7 @@ impl Example { &message, &caller, &gas_token, - &Bytes::new(&env), + &Bytes::new(env), ); gateway.call_contract( @@ -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, + 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(()) + } } diff --git a/contracts/example/src/event.rs b/contracts/example/src/event.rs index 2f91bddc..6c20c0cb 100644 --- a/contracts/example/src/event.rs +++ b/contracts/example/src/event.rs @@ -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, } diff --git a/contracts/example/src/lib.rs b/contracts/example/src/lib.rs index 1f9a6d2d..0b027741 100644 --- a/contracts/example/src/lib.rs +++ b/contracts/example/src/lib.rs @@ -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}; diff --git a/contracts/example/src/storage_types.rs b/contracts/example/src/storage_types.rs index ce1b9923..46075902 100644 --- a/contracts/example/src/storage_types.rs +++ b/contracts/example/src/storage_types.rs @@ -5,4 +5,5 @@ use soroban_sdk::contracttype; pub enum DataKey { Gateway, GasService, + InterchainTokenService, } diff --git a/contracts/example/tests/test.rs b/contracts/example/tests/test.rs index 7172b1c4..9e5e4b34 100644 --- a/contracts/example/tests/test.rs +++ b/contracts/example/tests/test.rs @@ -1,69 +1,160 @@ -#![cfg(test)] -extern crate std; - +use example::event::{ExecutedEvent, TokenReceivedEvent}; use example::{Example, ExampleClient}; -use soroban_sdk::testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation, BytesN as _}; -use soroban_sdk::token::StellarAssetClient; +use soroban_sdk::testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation}; +use soroban_sdk::token::{self, StellarAssetClient}; use soroban_sdk::{vec, Address, Bytes, BytesN, Env, IntoVal, String, Symbol}; +use soroban_token_sdk::metadata::TokenMetadata; use stellar_axelar_gas_service::{AxelarGasService, AxelarGasServiceClient}; +use stellar_axelar_gateway::event::{ContractCalledEvent, MessageApprovedEvent}; use stellar_axelar_gateway::testutils::{self, generate_proof, get_approve_hash, TestSignerSet}; use stellar_axelar_gateway::types::Message; use stellar_axelar_gateway::AxelarGatewayClient; +use stellar_axelar_std::address::AddressExt; +use stellar_axelar_std::traits::BytesExt; use stellar_axelar_std::types::Token; -use stellar_axelar_std::{assert_last_emitted_event, auth_invocation}; +use stellar_axelar_std::{auth_invocation, events}; +use stellar_interchain_token_service::event::TrustedChainSetEvent; +use stellar_interchain_token_service::testutils::INTERCHAIN_TOKEN_WASM_HASH; +use stellar_interchain_token_service::{InterchainTokenService, InterchainTokenServiceClient}; + +const ITS_HUB_ADDRESS: &str = "hub_address"; +const SOURCE_CHAIN_NAME: &str = "source"; +const DESTINATION_CHAIN_NAME: &str = "destination"; fn setup_gateway<'a>(env: &Env) -> (TestSignerSet, AxelarGatewayClient<'a>) { let (signers, client) = testutils::setup_gateway(env, 0, 5); (signers, client) } -fn setup_gas_service<'a>(env: &Env) -> (AxelarGasServiceClient<'a>, Address, Address) { +fn setup_gas_service<'a>(env: &Env) -> AxelarGasServiceClient<'a> { let owner: Address = Address::generate(env); let gas_collector: Address = Address::generate(env); - let gas_service_id = env.register(AxelarGasService, (&owner, &gas_collector)); - let gas_service_client = AxelarGasServiceClient::new(env, &gas_service_id); + let gas_service = env.register(AxelarGasService, (&owner, &gas_collector)); + let gas_service_client = AxelarGasServiceClient::new(env, &gas_service); + + gas_service_client +} + +struct TestConfig<'a> { + signers: TestSignerSet, + gateway_client: AxelarGatewayClient<'a>, + gas_service_client: AxelarGasServiceClient<'a>, + its_client: InterchainTokenServiceClient<'a>, + app: ExampleClient<'a>, +} + +fn setup_app<'a>(env: &Env, chain_name: &String) -> TestConfig<'a> { + let (signers, gateway_client) = setup_gateway(env); + let gas_service_client = setup_gas_service(env); + let its_client = setup_its( + env, + &gateway_client.address, + &gas_service_client.address, + chain_name, + ); + let app = env.register( + Example, + ( + &gateway_client.address, + &gas_service_client.address, + &its_client.address, + ), + ); + let app = ExampleClient::new(env, &app); + + TestConfig { + signers, + gateway_client, + gas_service_client, + its_client, + app, + } +} + +fn setup_its<'a>( + env: &Env, + gateway: &Address, + gas_service: &Address, + chain_name: &String, +) -> InterchainTokenServiceClient<'a> { + let owner = Address::generate(env); + let operator = Address::generate(env); + let its_hub_address = String::from_str(env, ITS_HUB_ADDRESS); + let native_token = Address::generate(env); + let interchain_token_wasm_hash = env + .deployer() + .upload_contract_wasm(INTERCHAIN_TOKEN_WASM_HASH); + + let its = env.register( + InterchainTokenService, + ( + &owner, + &operator, + gateway, + gas_service, + its_hub_address, + chain_name.clone(), + native_token, + interchain_token_wasm_hash, + ), + ); - (gas_service_client, gas_collector, gas_service_id) + let its_client = InterchainTokenServiceClient::new(env, &its); + + its_client } -fn setup_app<'a>(env: &Env, gateway: &Address, gas_service: &Address) -> ExampleClient<'a> { - let id = env.register(Example, (gateway, gas_service)); - let client = ExampleClient::new(env, &id); +fn setup_its_token( + env: &Env, + client: &InterchainTokenServiceClient, + sender: &Address, + supply: i128, +) -> BytesN<32> { + let salt = BytesN::from_array(env, &[1u8; 32]); + let token_metadata = TokenMetadata { + name: String::from_str(env, "Test"), + symbol: String::from_str(env, "TEST"), + decimal: 18, + }; - client + let token_id = client.mock_all_auths().deploy_interchain_token( + sender, + &salt, + &token_metadata, + &supply, + &None, + ); + + token_id } #[test] fn gmp_example() { let env = Env::default(); - env.mock_all_auths(); let user: Address = Address::generate(&env); // Setup source Axelar gateway - let source_chain = String::from_str(&env, "source"); - let (_, source_gateway_client) = setup_gateway(&env); - let source_gateway_id = source_gateway_client.address; - let (source_gas_service_client, _source_gas_collector, source_gas_service_id) = - setup_gas_service(&env); - let source_app = setup_app(&env, &source_gateway_id, &source_gas_service_id); + let source_chain = String::from_str(&env, SOURCE_CHAIN_NAME); + let TestConfig { + gas_service_client: source_gas_service_client, + app: source_app, + .. + } = setup_app(&env, &source_chain); // Setup destination Axelar gateway - let destination_chain = String::from_str(&env, "destination"); - let (signers, destination_gateway_client) = setup_gateway(&env); - - let (_destination_gas_service_client, _destination_gas_collector, destination_gas_service_id) = - setup_gas_service(&env); - let destination_app = setup_app( - &env, - &destination_gateway_client.address, - &destination_gas_service_id, - ); + let destination_chain = String::from_str(&env, DESTINATION_CHAIN_NAME); + let TestConfig { + signers: destination_signers, + gateway_client: destination_gateway_client, + app: destination_app, + .. + } = setup_app(&env, &destination_chain); // Set cross-chain message params let source_address = source_app.address.to_string(); let destination_address = destination_app.address.to_string(); - let payload: Bytes = BytesN::<20>::random(&env).into(); + let payload = Bytes::from_hex(&env, "dead"); let payload_hash: BytesN<32> = env.crypto().keccak256(&payload).into(); // Initiate cross-chain contract call, sending message from source to destination @@ -75,9 +166,9 @@ fn gmp_example() { amount: gas_amount, }; - asset_client.mint(&user, &gas_amount); + asset_client.mock_all_auths().mint(&user, &gas_amount); - source_app.send( + source_app.mock_all_auths().send( &user, &destination_chain, &destination_address, @@ -88,9 +179,11 @@ fn gmp_example() { let transfer_auth = auth_invocation!( &env, user, - asset_client.transfer(&user, source_gas_service_id, gas_token.amount) + asset_client.transfer(&user, &source_gas_service_client.address, gas_token.amount) ); + let source_gas_service_client = source_gas_service_client; + let pay_gas_auth = auth_invocation!( &env, user, @@ -106,13 +199,15 @@ fn gmp_example() { transfer_auth ); + let source_app = source_app; + let send_auth = auth_invocation!( &env, user, source_app.send( &user, - destination_chain.clone(), - destination_address.clone(), + destination_chain, + destination_address, payload.clone(), gas_token ), @@ -125,18 +220,7 @@ fn gmp_example() { let message_id = String::from_str(&env, "test"); // Confirming message from source Axelar gateway - assert_last_emitted_event( - &env, - &source_gateway_id, - ( - Symbol::new(&env, "contract_called"), - source_app.address, - destination_chain, - destination_address, - payload_hash.clone(), - ), - (payload.to_val(),), - ); + let contract_call_event = events::fmt_last_emitted_event::(&env); // Axelar hub signs the message approval, Signing message approval for destination let messages = vec![ @@ -150,23 +234,96 @@ fn gmp_example() { }, ]; let data_hash = get_approve_hash(&env, messages.clone()); - let proof = generate_proof(&env, data_hash, signers); + let proof = generate_proof(&env, data_hash, destination_signers); // Submitting signed message approval to destination Axelar gateway destination_gateway_client.approve_messages(&messages, &proof); + let message_approved_event = events::fmt_last_emitted_event::(&env); + // Executing message on destination app destination_app.execute(&source_chain, &message_id, &source_address, &payload); - assert_last_emitted_event( - &env, - &destination_app.address, - ( - Symbol::new(&env, "executed"), - source_chain, - message_id, - source_address, + let executed_event = events::fmt_last_emitted_event::(&env); + + goldie::assert!([contract_call_event, message_approved_event, executed_event].join("\n\n")); +} + +#[test] +fn its_example() { + let env = Env::default(); + + let user = Address::generate(&env).to_string_bytes(); + + let chain_name = String::from_str(&env, "chain_name"); + let TestConfig { + signers, + gateway_client, + its_client, + app: example_app, + .. + } = setup_app(&env, &chain_name); + let source_chain = its_client.its_hub_chain_name(); + let source_address: String = its_client.its_hub_address(); + + let amount = 1000; + let token_id = setup_its_token(&env, &its_client, &Address::generate(&env), amount); + + let original_source_chain = String::from_str(&env, "ethereum"); + its_client + .mock_all_auths() + .set_trusted_chain(&original_source_chain); + + let trusted_chain_set_event = events::fmt_last_emitted_event::(&env); + + let data = Some(Address::generate(&env).to_string_bytes()); + + let msg = stellar_interchain_token_service::types::HubMessage::ReceiveFromHub { + source_chain: original_source_chain, + message: stellar_interchain_token_service::types::Message::InterchainTransfer( + stellar_interchain_token_service::types::InterchainTransfer { + token_id: token_id.clone(), + source_address: user, + destination_address: example_app.address.to_string_bytes(), + amount, + data: data.clone(), + }, ), - (payload,), - ); + }; + let payload = msg.abi_encode(&env).unwrap(); + + let message_id = String::from_str(&env, "message-id"); + + let messages = vec![ + &env, + Message { + source_chain: source_chain.clone(), + message_id: message_id.clone(), + source_address: source_address.clone(), + contract_address: its_client.address.clone(), + payload_hash: env.crypto().keccak256(&payload).into(), + }, + ]; + let proof = generate_proof(&env, get_approve_hash(&env, messages.clone()), signers); + + gateway_client.approve_messages(&messages, &proof); + + let message_approved_event = events::fmt_last_emitted_event::(&env); + + its_client.execute(&source_chain, &message_id, &source_address, &payload); + + let token_received_event = events::fmt_last_emitted_event::(&env); + + goldie::assert!([ + trusted_chain_set_event, + message_approved_event, + token_received_event + ] + .join("\n\n")); + + let token = token::TokenClient::new(&env, &its_client.token_address(&token_id)); + assert_eq!(token.balance(&example_app.address), 0); + + let recipient = Address::from_string_bytes(&data.unwrap()); + assert_eq!(token.balance(&recipient), amount); } diff --git a/contracts/example/tests/testdata/gmp_example.golden b/contracts/example/tests/testdata/gmp_example.golden new file mode 100644 index 00000000..95080c26 --- /dev/null +++ b/contracts/example/tests/testdata/gmp_example.golden @@ -0,0 +1,11 @@ +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4) +topics: (Symbol(contract_called), Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5), String(destination), String(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABOHR6), BytesN<32>(57, 5, 211, 68, 113, 126, 253, 86, 36, 71, 164, 150, 14, 234, 148, 28, 18, 68, 173, 195, 31, 83, 82, 93, 14, 193, 57, 127, 246, 149, 28, 156)) +data: (Bytes(222, 173)) + +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6J5N) +topics: (Symbol(message_approved), Message { source_chain: String(source), message_id: String(test), source_address: String(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5), contract_address: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABOHR6), payload_hash: BytesN<32>(57, 5, 211, 68, 113, 126, 253, 86, 36, 71, 164, 150, 14, 234, 148, 28, 18, 68, 173, 195, 31, 83, 82, 93, 14, 193, 57, 127, 246, 149, 28, 156) }) +data: () + +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABOHR6) +topics: (Symbol(executed), String(source), String(test), String(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5)) +data: (Bytes(222, 173)) \ No newline at end of file diff --git a/contracts/interchain-token-service/tests/testdata/interchain_token.wasm b/contracts/example/tests/testdata/interchain_token.wasm similarity index 100% rename from contracts/interchain-token-service/tests/testdata/interchain_token.wasm rename to contracts/example/tests/testdata/interchain_token.wasm diff --git a/contracts/example/tests/testdata/its_example.golden b/contracts/example/tests/testdata/its_example.golden new file mode 100644 index 00000000..82a1cde8 --- /dev/null +++ b/contracts/example/tests/testdata/its_example.golden @@ -0,0 +1,11 @@ +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N) +topics: (Symbol(trusted_chain_set), String(ethereum)) +data: () + +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4) +topics: (Symbol(message_approved), Message { source_chain: String(axelar), message_id: String(message-id), source_address: String(hub_address), contract_address: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N), payload_hash: BytesN<32>(237, 149, 219, 250, 141, 232, 32, 82, 24, 198, 205, 234, 25, 38, 169, 223, 11, 147, 254, 36, 100, 116, 38, 89, 4, 52, 132, 200, 218, 56, 83, 209) }) +data: () + +contract: Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5) +topics: (Symbol(token_received), String(ethereum), String(message-id), Bytes(67, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 68, 50, 75, 77), BytesN<32>(19, 164, 112, 128, 76, 247, 2, 236, 196, 20, 87, 206, 58, 2, 208, 102, 225, 238, 175, 151, 45, 250, 47, 204, 76, 253, 158, 193, 68, 73, 208, 149), Contract(CBBOW4DL4KLP3B46GBQKYO7R4EE3XIT4GX5D2ETGQ43BOPF2CPVYI5KR), 1000) +data: (Bytes(67, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 52, 66, 86, 53)) \ No newline at end of file diff --git a/contracts/interchain-token-service/src/lib.rs b/contracts/interchain-token-service/src/lib.rs index c285da87..5bab0fdc 100644 --- a/contracts/interchain-token-service/src/lib.rs +++ b/contracts/interchain-token-service/src/lib.rs @@ -8,6 +8,9 @@ pub mod executable; mod interface; pub mod types; +#[cfg(any(test, feature = "testutils"))] +pub mod testutils; + cfg_if::cfg_if! { if #[cfg(all(feature = "library", not(feature = "testutils")))] { pub use interface::{InterchainTokenServiceClient, InterchainTokenServiceInterface}; diff --git a/contracts/interchain-token-service/src/testdata/interchain_token.wasm b/contracts/interchain-token-service/src/testdata/interchain_token.wasm new file mode 100755 index 0000000000000000000000000000000000000000..20da80083061b02381f621abf3424ea8a4d6f9f4 GIT binary patch literal 11825 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: &Env) -> AxelarGasServiceClient<'a> { let owner: Address = Address::generate(env); let gas_collector: Address = Address::generate(env); diff --git a/packages/axelar-std-derive/src/event.rs b/packages/axelar-std-derive/src/event.rs index e6b1fbd1..c913fa4f 100644 --- a/packages/axelar-std-derive/src/event.rs +++ b/packages/axelar-std-derive/src/event.rs @@ -8,6 +8,16 @@ pub fn derive_event_impl(input: &DeriveInput) -> proc_macro2::TokenStream { let event_name = event_name_snake_case(input); let ((topic_idents, _), (data_idents, _)) = event_struct_fields(input); + let data_impl = quote! { + fn data(&self, env: &soroban_sdk::Env) -> impl soroban_sdk::IntoVal + core::fmt::Debug { + let data: soroban_sdk::Vec = soroban_sdk::vec![ + env + #(, soroban_sdk::IntoVal::<_, soroban_sdk::Val>::into_val(&self.#data_idents, env))* + ]; + data + } + }; + quote! { impl stellar_axelar_std::events::Event for #name { fn topics(&self, env: &soroban_sdk::Env) -> impl soroban_sdk::Topics + core::fmt::Debug { @@ -17,10 +27,7 @@ pub fn derive_event_impl(input: &DeriveInput) -> proc_macro2::TokenStream { ) } - fn data(&self, env: &soroban_sdk::Env) -> impl soroban_sdk::IntoVal + core::fmt::Debug { - let vec: soroban_sdk::Vec = soroban_sdk::vec![env, #(soroban_sdk::IntoVal::<_, soroban_sdk::Val>::into_val(&self.#data_idents, env))*]; - vec - } + #data_impl fn emit(self, env: &soroban_sdk::Env) { env.events().publish(self.topics(env), self.data(env));