diff --git a/.changeset/violet-bugs-count.md b/.changeset/violet-bugs-count.md new file mode 100644 index 00000000..d21bab58 --- /dev/null +++ b/.changeset/violet-bugs-count.md @@ -0,0 +1,5 @@ +--- +'@axelar-network/axelar-cgp-sui': minor +--- + +Added events file and move all logic to versioned. diff --git a/move/its/sources/discovery.move b/move/its/sources/discovery.move index 22ec8928..20711086 100644 --- a/move/its/sources/discovery.move +++ b/move/its/sources/discovery.move @@ -211,7 +211,9 @@ fun test_discovery_initial() { register_transaction(&mut its, &mut discovery); let value = its.package_value(); - assert!(discovery.get_transaction(value.channel_id()) == initial_tx(&its)); + assert!( + discovery.get_transaction(object::id_from_address(value.channel_address())) == initial_tx(&its), + ); assert!(value.relayer_discovery_id() == object::id(&discovery)); sui::test_utils::destroy(its); @@ -283,7 +285,7 @@ fun test_discovery_interchain_transfer_with_data() { register_transaction(&mut its, &mut discovery); assert!( - discovery.get_transaction(its.package_value().channel_id()) == initial_tx(&its), + discovery.get_transaction(object::id_from_address(its.package_value().channel_address())) == initial_tx(&its), ); let token_id = @0x1234; diff --git a/move/its/sources/events.move b/move/its/sources/events.move new file mode 100644 index 00000000..3f44911f --- /dev/null +++ b/move/its/sources/events.move @@ -0,0 +1,20 @@ +module its::events; + +use its::token_id::TokenId; +use sui::event; + +// ----- +// Types +// ----- +public struct CoinRegistered has copy, drop { + token_id: TokenId, +} + +// ----------------- +// Package Functions +// ----------------- +public(package) fun coin_registered(token_id: TokenId) { + event::emit(CoinRegistered { + token_id, + }); +} diff --git a/move/its/sources/its.move b/move/its/sources/its.move index 584df24a..30122ae5 100644 --- a/move/its/sources/its.move +++ b/move/its/sources/its.move @@ -1,26 +1,20 @@ module its::its; -use abi::abi; use axelar_gateway::channel::{ApprovedMessage, Channel}; -use axelar_gateway::gateway; use axelar_gateway::message_ticket::MessageTicket; -use its::coin_info::{Self, CoinInfo}; -use its::coin_management::{Self, CoinManagement}; +use its::coin_info::CoinInfo; +use its::coin_management::CoinManagement; use its::interchain_transfer_ticket::{Self, InterchainTransferTicket}; use its::its_v0::{Self, ITS_v0}; use its::owner_cap::{Self, OwnerCap}; -use its::token_id::{Self, TokenId}; +use its::token_id::TokenId; use its::trusted_addresses::TrustedAddresses; -use its::utils as its_utils; use relayer_discovery::discovery::RelayerDiscovery; use relayer_discovery::transaction::Transaction; use std::ascii::{Self, String}; -use std::string; -use std::type_name::{Self, TypeName}; -use sui::address; +use std::type_name::TypeName; use sui::clock::Clock; -use sui::coin::{Self, Coin, TreasuryCap, CoinMetadata}; -use sui::event; +use sui::coin::{Coin, TreasuryCap, CoinMetadata}; use sui::versioned::{Self, Versioned}; use version_control::version_control::{Self, VersionControl}; @@ -30,66 +24,6 @@ use version_control::version_control::{Self, VersionControl}; const VERSION: u64 = 0; const DATA_VERSION: u64 = 0; -// === MESSAGE TYPES === -const MESSAGE_TYPE_INTERCHAIN_TRANSFER: u256 = 0; -const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN: u256 = 1; -// onst MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER: u256 = 2; -const MESSAGE_TYPE_SEND_TO_HUB: u256 = 3; -const MESSAGE_TYPE_RECEIVE_FROM_HUB: u256 = 4; -// address::to_u256(address::from_bytes(keccak256(b"sui-set-trusted-addresses"))); - -// === HUB CONSTANTS === -// Chain name for Axelar. This is used for routing ITS calls via ITS hub on -// Axelar. -const ITS_HUB_CHAIN_NAME: vector = b"Axelarnet"; -// Identifier to be used as destination address for chains that route to hub. -// For Sui this will probably be every supported chain. -const ITS_HUB_ROUTING_IDENTIFIER: vector = b"hub"; - -// === The maximum number of decimals allowed === -const DECIMALS_CAP: u8 = 9; - -// ------ -// Errors -// ------ -#[error] -const EUntrustedAddress: vector = - b"the sender that sent this message is not trusted."; -#[error] -const EInvalidMessageType: vector = - b"the message type received is not supported."; -#[error] -const EWrongDestination: vector = - b"the channel trying to receive this call is not the destination."; -#[error] -const EInterchainTransferHasData: vector = - b"interchain transfer with data trying to be processed as an interchain transfer."; -#[error] -const EInterchainTransferHasNoData: vector = - b"interchain transfer trying to be proccessed as an interchain transfer"; -#[error] -const EModuleNameDoesNotMatchSymbol: vector = - b"the module name does not match the symbol."; -#[error] -const ENotDistributor: vector = b"only the distributor can mint."; -#[error] -const ENonZeroTotalSupply: vector = - b"trying to give a token that has had some supply already minted."; -#[error] -const EUnregisteredCoinHasUrl: vector = - b"the interchain token that is being registered has a URL."; -#[error] -const EUntrustedChain: vector = b"the chain is not trusted."; -#[error] -const ENewerTicket: vector = b"cannot proccess newer tickets."; - -// ------ -// Events -// ------ -public struct CoinRegistered has copy, drop { - token_id: TokenId, -} - // ------- // Structs // ------- @@ -152,40 +86,17 @@ public fun register_coin( ): TokenId { let value = self.value_mut!(b"register_coin"); - let token_id = token_id::from_coin_data(&coin_info, &coin_management); - - value.add_registered_coin(token_id, coin_management, coin_info); - - event::emit(CoinRegistered { - token_id, - }); - - token_id + value.register_coin(coin_info, coin_management) } public fun deploy_remote_interchain_token( - self: &mut ITS, + self: &ITS, token_id: TokenId, destination_chain: String, ): MessageTicket { let value = self.value!(b"deploy_remote_interchain_token"); - let coin_info = value.coin_info(token_id); - - let name = coin_info.name(); - let symbol = coin_info.symbol(); - let decimals = coin_info.decimals(); - - let mut writer = abi::new_writer(6); - writer - .write_u256(MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) - .write_u256(token_id.to_u256()) - .write_bytes(*name.as_bytes()) - .write_bytes(*symbol.as_bytes()) - .write_u256((decimals as u256)) - .write_bytes(vector::empty()); - - prepare_message(value, destination_chain, writer.into_bytes()) + value.deploy_remote_interchain_token(token_id, destination_chain) } public fun prepare_interchain_transfer( @@ -212,34 +123,13 @@ public fun send_interchain_transfer( ticket: InterchainTransferTicket, clock: &Clock, ): MessageTicket { - let ( - token_id, - balance, - source_address, - destination_chain, - destination_address, - metadata, - version, - ) = ticket.destroy(); - assert!(version <= VERSION, ENewerTicket); - let value = self.value_mut!(b"send_interchain_transfer"); - let amount = value - .coin_management_mut(token_id) - .take_balance(balance, clock); - let (_version, data) = its_utils::decode_metadata(metadata); - let mut writer = abi::new_writer(6); - - writer - .write_u256(MESSAGE_TYPE_INTERCHAIN_TRANSFER) - .write_u256(token_id.to_u256()) - .write_bytes(source_address.to_bytes()) - .write_bytes(destination_address) - .write_u256(amount) - .write_bytes(data); - - prepare_message(value, destination_chain, writer.into_bytes()) + value.send_interchain_transfer( + ticket, + VERSION, + clock, + ) } public fun receive_interchain_transfer( @@ -250,26 +140,7 @@ public fun receive_interchain_transfer( ) { let value = self.value_mut!(b"receive_interchain_transfer"); - let (_, payload) = decode_approved_message(value, approved_message); - let mut reader = abi::new_reader(payload); - assert!( - reader.read_u256() == MESSAGE_TYPE_INTERCHAIN_TRANSFER, - EInvalidMessageType, - ); - - let token_id = token_id::from_u256(reader.read_u256()); - reader.skip_slot(); // skip source_address - let destination_address = address::from_bytes(reader.read_bytes()); - let amount = reader.read_u256(); - let data = reader.read_bytes(); - - assert!(data.is_empty(), EInterchainTransferHasData); - - let coin = value - .coin_management_mut(token_id) - .give_coin(amount, clock, ctx); - - transfer::public_transfer(coin, destination_address) + value.receive_interchain_transfer(approved_message, clock, ctx); } public fun receive_interchain_transfer_with_data( @@ -281,34 +152,12 @@ public fun receive_interchain_transfer_with_data( ): (String, vector, vector, Coin) { let value = self.value_mut!(b"receive_interchain_transfer_with_data"); - let (source_chain, payload) = decode_approved_message( - value, + value.receive_interchain_transfer_with_data( approved_message, - ); - let mut reader = abi::new_reader(payload); - assert!( - reader.read_u256() == MESSAGE_TYPE_INTERCHAIN_TRANSFER, - EInvalidMessageType, - ); - - let token_id = token_id::from_u256(reader.read_u256()); - - let source_address = reader.read_bytes(); - let destination_address = reader.read_bytes(); - let amount = reader.read_u256(); - let data = reader.read_bytes(); - - assert!( - address::from_bytes(destination_address) == channel.to_address(), - EWrongDestination, - ); - assert!(!data.is_empty(), EInterchainTransferHasNoData); - - let coin = value - .coin_management_mut(token_id) - .give_coin(amount, clock, ctx); - - (source_chain, source_address, data, coin) + channel, + clock, + ctx, + ) } public fun receive_deploy_interchain_token( @@ -317,35 +166,7 @@ public fun receive_deploy_interchain_token( ) { let value = self.value_mut!(b"receive_deploy_interchain_token"); - let (_, payload) = decode_approved_message(value, approved_message); - let mut reader = abi::new_reader(payload); - assert!( - reader.read_u256() == MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN, - EInvalidMessageType, - ); - - let token_id = token_id::from_u256(reader.read_u256()); - let name = string::utf8(reader.read_bytes()); - let symbol = ascii::string(reader.read_bytes()); - let remote_decimals = (reader.read_u256() as u8); - let distributor_bytes = reader.read_bytes(); - let decimals = if (remote_decimals > DECIMALS_CAP) DECIMALS_CAP - else remote_decimals; - let (treasury_cap, mut coin_metadata) = value.remove_unregistered_coin( - token_id::unregistered_token_id(&symbol, decimals), - ); - - treasury_cap.update_name(&mut coin_metadata, name); - - let mut coin_management = coin_management::new_with_cap(treasury_cap); - let coin_info = coin_info::from_metadata(coin_metadata, remote_decimals); - - if (distributor_bytes.length() > 0) { - let distributor = address::from_bytes(distributor_bytes); - coin_management.add_distributor(distributor); - }; - - value.add_registered_coin(token_id, coin_management, coin_info); + value.receive_deploy_interchain_token(approved_message); } // We need an coin with zero supply that has the proper decimals and typing, and @@ -353,30 +174,11 @@ public fun receive_deploy_interchain_token( public fun give_unregistered_coin( self: &mut ITS, treasury_cap: TreasuryCap, - mut coin_metadata: CoinMetadata, + coin_metadata: CoinMetadata, ) { let value = self.value_mut!(b"give_unregistered_coin"); - assert!(treasury_cap.total_supply() == 0, ENonZeroTotalSupply); - assert!( - coin::get_icon_url(&coin_metadata).is_none(), - EUnregisteredCoinHasUrl, - ); - - treasury_cap.update_description(&mut coin_metadata, string::utf8(b"")); - - let decimals = coin_metadata.get_decimals(); - let symbol = coin_metadata.get_symbol(); - - let module_name = type_name::get_module(&type_name::get()); - assert!( - &module_name == &its_utils::module_from_symbol(&symbol), - EModuleNameDoesNotMatchSymbol, - ); - - let token_id = token_id::unregistered_token_id(&symbol, decimals); - - value.add_unregistered_coin(token_id, treasury_cap, coin_metadata); + value.give_unregistered_coin(treasury_cap, coin_metadata); } public fun mint_as_distributor( @@ -388,12 +190,12 @@ public fun mint_as_distributor( ): Coin { let value = self.value_mut!(b"mint_as_distributor"); - let coin_management = value.coin_management_mut(token_id); - let distributor = channel.to_address(); - - assert!(coin_management.is_distributor(distributor), ENotDistributor); - - coin_management.mint(amount, ctx) + value.mint_as_distributor( + channel, + token_id, + amount, + ctx, + ) } public fun mint_to_as_distributor( @@ -406,14 +208,13 @@ public fun mint_to_as_distributor( ) { let value = self.value_mut!(b"mint_to_as_distributor"); - let coin_management = value.coin_management_mut(token_id); - let distributor = channel.to_address(); - - assert!(coin_management.is_distributor(distributor), ENotDistributor); - - let coin = coin_management.mint(amount, ctx); - - transfer::public_transfer(coin, to); + value.mint_to_as_distributor( + channel, + token_id, + to, + amount, + ctx, + ); } public fun burn_as_distributor( @@ -424,12 +225,11 @@ public fun burn_as_distributor( ) { let value = self.value_mut!(b"mint_to_as_distributor"); - let coin_management = value.coin_management_mut(token_id); - let distributor = channel.to_address(); - - assert!(coin_management.is_distributor(distributor), ENotDistributor); - - coin_management.burn(coin.into_balance()); + value.burn_as_distributor( + channel, + token_id, + coin, + ); } // --------------- @@ -491,80 +291,6 @@ public(package) fun register_transaction( // ----------------- // Private Functions // ----------------- -/// Decode an approved call and check that the source chain is trusted. -fun decode_approved_message( - value: &mut ITS_v0, - approved_message: ApprovedMessage, -): (String, vector) { - let (mut source_chain, _, source_address, mut payload) = value - .channel_mut() - .consume_approved_message(approved_message); - - assert!( - value.is_trusted_address(source_chain, source_address), - EUntrustedAddress, - ); - - let mut reader = abi::new_reader(payload); - if (reader.read_u256() == MESSAGE_TYPE_RECEIVE_FROM_HUB) { - assert!( - source_chain.into_bytes() == ITS_HUB_CHAIN_NAME, - EUntrustedChain, - ); - - source_chain = ascii::string(reader.read_bytes()); - payload = reader.read_bytes(); - - assert!( - value.trusted_address(source_chain).into_bytes() == ITS_HUB_ROUTING_IDENTIFIER, - EUntrustedChain, - ); - } else { - assert!( - source_chain.into_bytes() != ITS_HUB_CHAIN_NAME, - EUntrustedChain, - ); - }; - - (source_chain, payload) -} - -/// Send a payload to a destination chain. The destination chain needs to have a -/// trusted address. -fun prepare_message( - value: &ITS_v0, - mut destination_chain: String, - mut payload: vector, -): MessageTicket { - let mut destination_address = value.trusted_address(destination_chain); - - // Prevent sending directly to the ITS Hub chain. This is not supported yet, - // so fail early to prevent the user from having their funds stuck. - assert!( - destination_chain.into_bytes() != ITS_HUB_CHAIN_NAME, - EUntrustedChain, - ); - - // Check whether the ITS call should be routed via ITS hub for this - // destination chain - if (destination_address.into_bytes() == ITS_HUB_ROUTING_IDENTIFIER) { - let mut writer = abi::new_writer(3); - writer.write_u256(MESSAGE_TYPE_SEND_TO_HUB); - writer.write_bytes(destination_chain.into_bytes()); - writer.write_bytes(payload); - payload = writer.into_bytes(); - destination_chain = ascii::string(ITS_HUB_CHAIN_NAME); - destination_address = value.trusted_address(destination_chain); - }; - - gateway::prepare_message( - value.channel(), - destination_chain, - destination_address, - payload, - ) -} - fun version_control(): VersionControl { version_control::new(vector[ // Version 0 @@ -592,6 +318,27 @@ fun version_control(): VersionControl { use its::coin::COIN; #[test_only] use axelar_gateway::channel; +#[test_only] +use std::string; +#[test_only] +use abi::abi; + +// === MESSAGE TYPES === +#[test_only] +const MESSAGE_TYPE_INTERCHAIN_TRANSFER: u256 = 0; +#[test_only] +const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN: u256 = 1; +// onst MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER: u256 = 2; + +// === HUB CONSTANTS === +// Identifier to be used as destination address for chains that route to hub. +// For Sui this will probably be every supported chain. +#[test_only] +const ITS_HUB_ROUTING_IDENTIFIER: vector = b"hub"; + +// === The maximum number of decimals allowed === +#[test_only] +const DECIMALS_CAP: u8 = 9; #[test_only] public fun create_for_testing(ctx: &mut TxContext): ITS { @@ -619,28 +366,6 @@ public fun create_for_testing(ctx: &mut TxContext): ITS { } } -#[test_only] -public fun create_unregistered_coin( - self: &mut ITS, - symbol: vector, - decimals: u8, - ctx: &mut TxContext, -) { - let (treasury_cap, coin_metadata) = its::coin::create_treasury_and_metadata( - symbol, - decimals, - ctx, - ); - let token_id = token_id::unregistered_token_id( - &ascii::string(symbol), - decimals, - ); - - self - .value_mut!(b"") - .add_unregistered_coin(token_id, treasury_cap, coin_metadata); -} - #[test_only] public(package) fun add_unregistered_coin_type_for_testing( self: &mut ITS, @@ -720,7 +445,7 @@ fun test_deploy_remote_interchain_token() { let token_id = register_coin(&mut its, coin_info, coin_management); let destination_chain = ascii::string(b"Chain Name"); let message_ticket = deploy_remote_interchain_token( - &mut its, + &its, token_id, destination_chain, ); @@ -740,7 +465,7 @@ fun test_deploy_remote_interchain_token() { ); assert!(message_ticket.destination_chain() == destination_chain); assert!( - message_ticket.destination_address() == its.value!(b"").trusted_address(destination_chain), + message_ticket.destination_address() == its.value!(b"").trusted_address_for_testing(destination_chain), ); assert!(message_ticket.payload() == writer.into_bytes()); assert!(message_ticket.version() == 0); @@ -800,7 +525,7 @@ fun test_deploy_interchain_token() { ); assert!(message_ticket.destination_chain() == destination_chain); assert!( - message_ticket.destination_address() == its.value!(b"").trusted_address(destination_chain), + message_ticket.destination_address() == its.value!(b"").trusted_address_for_testing(destination_chain), ); assert!(message_ticket.payload() == writer.into_bytes()); assert!(message_ticket.version() == 0); @@ -861,8 +586,7 @@ fun test_receive_interchain_transfer() { } #[test] -#[expected_failure(abort_code = EInvalidMessageType)] -fun test_receive_interchain_transfer_invalid_message_type() { +fun test_receive_interchain_transfer_with_data() { let ctx = &mut tx_context::dummy(); let clock = sui::clock::create_for_testing(ctx); let mut its = create_for_testing(ctx); @@ -873,8 +597,10 @@ fun test_receive_interchain_transfer_invalid_message_type() { 10, 12, ); + let scaling = coin_info.scaling(); let amount = 1234; + let data = b"some_data"; let mut coin_management = its::coin_management::new_locked(); let coin = sui::coin::mint_for_testing(amount, ctx); coin_management.take_balance(coin.into_balance(), &clock); @@ -884,16 +610,17 @@ fun test_receive_interchain_transfer_invalid_message_type() { let message_id = ascii::string(b"Message Id"); let message_source_address = ascii::string(b"Address"); let its_source_address = b"Source Address"; - let destination_address = @0x1; + let channel = channel::new(ctx); + let destination_address = channel.to_address(); let mut writer = abi::new_writer(6); writer - .write_u256(MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) + .write_u256(MESSAGE_TYPE_INTERCHAIN_TRANSFER) .write_u256(token_id.to_u256()) .write_bytes(its_source_address) .write_bytes(destination_address.to_bytes()) .write_u256((amount as u256)) - .write_bytes(b""); + .write_bytes(data); let payload = writer.into_bytes(); let approved_message = channel::new_approved_message( @@ -904,511 +631,82 @@ fun test_receive_interchain_transfer_invalid_message_type() { payload, ); - receive_interchain_transfer(&mut its, approved_message, &clock, ctx); + let ( + received_source_chain, + received_source_address, + received_data, + received_coin, + ) = its.receive_interchain_transfer_with_data( + approved_message, + &channel, + &clock, + ctx, + ); + + assert!(received_source_chain == source_chain); + assert!(received_source_address == its_source_address); + assert!(received_data == data); + assert!(received_coin.value() == amount / (scaling as u64)); clock.destroy_for_testing(); + channel.destroy(); sui::test_utils::destroy(its); + sui::test_utils::destroy(received_coin); } #[test] -#[expected_failure(abort_code = EInterchainTransferHasData)] -fun test_receive_interchain_transfer_passed_data() { +fun test_receive_deploy_interchain_token() { let ctx = &mut tx_context::dummy(); let clock = sui::clock::create_for_testing(ctx); let mut its = create_for_testing(ctx); - let coin_info = its::coin_info::from_info( - string::utf8(b"Name"), - ascii::string(b"Symbol"), - 10, - 12, - ); - - let amount = 1234; - let mut coin_management = its::coin_management::new_locked(); - let coin = sui::coin::mint_for_testing(amount, ctx); - coin_management.take_balance(coin.into_balance(), &clock); - - let token_id = its.register_coin(coin_info, coin_management); let source_chain = ascii::string(b"Chain Name"); let message_id = ascii::string(b"Message Id"); - let message_source_address = ascii::string(b"Address"); - let its_source_address = b"Source Address"; - let destination_address = @0x1; + let source_address = ascii::string(b"Address"); + let name = b"Token Name"; + let symbol = b"Symbol"; + let remote_decimals = 12; + let decimals = if (remote_decimals > DECIMALS_CAP) DECIMALS_CAP + else remote_decimals; + let token_id: u256 = 1234; + + its.value_mut!(b"").create_unregistered_coin(symbol, decimals, ctx); let mut writer = abi::new_writer(6); writer - .write_u256(MESSAGE_TYPE_INTERCHAIN_TRANSFER) - .write_u256(token_id.to_u256()) - .write_bytes(its_source_address) - .write_bytes(destination_address.to_bytes()) - .write_u256((amount as u256)) - .write_bytes(b"some data"); + .write_u256(MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) + .write_u256(token_id) + .write_bytes(name) + .write_bytes(symbol) + .write_u256((remote_decimals as u256)) + .write_bytes(vector::empty()); let payload = writer.into_bytes(); let approved_message = channel::new_approved_message( source_chain, message_id, - message_source_address, + source_address, its.value!(b"").channel().to_address(), payload, ); - receive_interchain_transfer(&mut its, approved_message, &clock, ctx); + receive_deploy_interchain_token(&mut its, approved_message); clock.destroy_for_testing(); sui::test_utils::destroy(its); } #[test] -fun test_receive_interchain_transfer_with_data() { +fun test_give_unregistered_coin() { + let symbol = b"COIN"; + let decimals = 12; let ctx = &mut tx_context::dummy(); - let clock = sui::clock::create_for_testing(ctx); let mut its = create_for_testing(ctx); - let coin_info = its::coin_info::from_info( - string::utf8(b"Name"), - ascii::string(b"Symbol"), - 10, - 12, - ); - let scaling = coin_info.scaling(); - - let amount = 1234; - let data = b"some_data"; - let mut coin_management = its::coin_management::new_locked(); - let coin = sui::coin::mint_for_testing(amount, ctx); - coin_management.take_balance(coin.into_balance(), &clock); - - let token_id = its.register_coin(coin_info, coin_management); - let source_chain = ascii::string(b"Chain Name"); - let message_id = ascii::string(b"Message Id"); - let message_source_address = ascii::string(b"Address"); - let its_source_address = b"Source Address"; - let channel = channel::new(ctx); - let destination_address = channel.to_address(); - - let mut writer = abi::new_writer(6); - writer - .write_u256(MESSAGE_TYPE_INTERCHAIN_TRANSFER) - .write_u256(token_id.to_u256()) - .write_bytes(its_source_address) - .write_bytes(destination_address.to_bytes()) - .write_u256((amount as u256)) - .write_bytes(data); - let payload = writer.into_bytes(); - - let approved_message = channel::new_approved_message( - source_chain, - message_id, - message_source_address, - its.value!(b"").channel().to_address(), - payload, - ); - - let ( - received_source_chain, - received_source_address, - received_data, - received_coin, - ) = its.receive_interchain_transfer_with_data( - approved_message, - &channel, - &clock, - ctx, - ); - - assert!(received_source_chain == source_chain); - assert!(received_source_address == its_source_address); - assert!(received_data == data); - assert!(received_coin.value() == amount / (scaling as u64)); - - clock.destroy_for_testing(); - channel.destroy(); - sui::test_utils::destroy(its); - sui::test_utils::destroy(received_coin); -} - -#[test] -#[expected_failure(abort_code = EInvalidMessageType)] -fun test_receive_interchain_transfer_with_data_invalid_message_type() { - let ctx = &mut tx_context::dummy(); - let clock = sui::clock::create_for_testing(ctx); - let mut its = create_for_testing(ctx); - let coin_info = its::coin_info::from_info( - string::utf8(b"Name"), - ascii::string(b"Symbol"), - 10, - 12, - ); - - let amount = 1234; - let mut coin_management = its::coin_management::new_locked(); - let coin = sui::coin::mint_for_testing(amount, ctx); - coin_management.take_balance(coin.into_balance(), &clock); - - let token_id = its.register_coin(coin_info, coin_management); - let source_chain = ascii::string(b"Chain Name"); - let message_id = ascii::string(b"Message Id"); - let message_source_address = ascii::string(b"Address"); - let its_source_address = b"Source Address"; - let channel = channel::new(ctx); - let destination_address = channel.to_address(); - - let mut writer = abi::new_writer(6); - writer - .write_u256(MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) - .write_u256(token_id.to_u256()) - .write_bytes(its_source_address) - .write_bytes(destination_address.to_bytes()) - .write_u256((amount as u256)) - .write_bytes(b"some_data"); - let payload = writer.into_bytes(); - - let approved_message = channel::new_approved_message( - source_chain, - message_id, - message_source_address, - its.value!(b"").channel().to_address(), - payload, - ); - - let (_, _, _, received_coin) = receive_interchain_transfer_with_data( - &mut its, - approved_message, - &channel, - &clock, - ctx, - ); - - clock.destroy_for_testing(); - channel.destroy(); - sui::test_utils::destroy(its); - sui::test_utils::destroy(received_coin); -} - -#[test] -#[expected_failure(abort_code = EWrongDestination)] -fun test_receive_interchain_transfer_with_data_wrong_destination() { - let ctx = &mut tx_context::dummy(); - let clock = sui::clock::create_for_testing(ctx); - let mut its = create_for_testing(ctx); - - let coin_info = its::coin_info::from_info( - string::utf8(b"Name"), - ascii::string(b"Symbol"), - 10, - 12, - ); - - let amount = 1234; - let mut coin_management = its::coin_management::new_locked(); - let coin = sui::coin::mint_for_testing(amount, ctx); - coin_management.take_balance(coin.into_balance(), &clock); - - let token_id = register_coin(&mut its, coin_info, coin_management); - let source_chain = ascii::string(b"Chain Name"); - let message_id = ascii::string(b"Message Id"); - let message_source_address = ascii::string(b"Address"); - let its_source_address = b"Source Address"; - let channel = channel::new(ctx); - let destination_address = @0x1; - - let mut writer = abi::new_writer(6); - writer - .write_u256(MESSAGE_TYPE_INTERCHAIN_TRANSFER) - .write_u256(token_id.to_u256()) - .write_bytes(its_source_address) - .write_bytes(destination_address.to_bytes()) - .write_u256((amount as u256)) - .write_bytes(b"some_data"); - let payload = writer.into_bytes(); - - let approved_message = channel::new_approved_message( - source_chain, - message_id, - message_source_address, - its.value!(b"").channel().to_address(), - payload, - ); - - let (_, _, _, received_coin) = receive_interchain_transfer_with_data( - &mut its, - approved_message, - &channel, - &clock, - ctx, - ); - - clock.destroy_for_testing(); - channel.destroy(); - sui::test_utils::destroy(its); - sui::test_utils::destroy(received_coin); -} - -#[test] -#[expected_failure(abort_code = EInterchainTransferHasNoData)] -fun test_receive_interchain_transfer_with_data_no_data() { - let ctx = &mut tx_context::dummy(); - let clock = sui::clock::create_for_testing(ctx); - let mut its = create_for_testing(ctx); - - let coin_info = its::coin_info::from_info( - string::utf8(b"Name"), - ascii::string(b"Symbol"), - 10, - 12, - ); - - let amount = 1234; - let mut coin_management = its::coin_management::new_locked(); - let coin = sui::coin::mint_for_testing(amount, ctx); - coin_management.take_balance(coin.into_balance(), &clock); - - let token_id = register_coin(&mut its, coin_info, coin_management); - let source_chain = ascii::string(b"Chain Name"); - let message_id = ascii::string(b"Message Id"); - let message_source_address = ascii::string(b"Address"); - let its_source_address = b"Source Address"; - let channel = channel::new(ctx); - let destination_address = channel.to_address(); - - let mut writer = abi::new_writer(6); - writer - .write_u256(MESSAGE_TYPE_INTERCHAIN_TRANSFER) - .write_u256(token_id.to_u256()) - .write_bytes(its_source_address) - .write_bytes(destination_address.to_bytes()) - .write_u256((amount as u256)) - .write_bytes(b""); - let payload = writer.into_bytes(); - - let approved_message = channel::new_approved_message( - source_chain, - message_id, - message_source_address, - its.value!(b"").channel().to_address(), - payload, - ); - - let (_, _, _, received_coin) = receive_interchain_transfer_with_data( - &mut its, - approved_message, - &channel, - &clock, - ctx, - ); - - clock.destroy_for_testing(); - channel.destroy(); - sui::test_utils::destroy(its); - sui::test_utils::destroy(received_coin); -} - -#[test] -fun test_receive_deploy_interchain_token() { - let ctx = &mut tx_context::dummy(); - let clock = sui::clock::create_for_testing(ctx); - let mut its = create_for_testing(ctx); - - let source_chain = ascii::string(b"Chain Name"); - let message_id = ascii::string(b"Message Id"); - let source_address = ascii::string(b"Address"); - let name = b"Token Name"; - let symbol = b"Symbol"; - let remote_decimals = 12; - let decimals = if (remote_decimals > DECIMALS_CAP) DECIMALS_CAP - else remote_decimals; - let token_id: u256 = 1234; - - create_unregistered_coin(&mut its, symbol, decimals, ctx); - - let mut writer = abi::new_writer(6); - writer - .write_u256(MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) - .write_u256(token_id) - .write_bytes(name) - .write_bytes(symbol) - .write_u256((remote_decimals as u256)) - .write_bytes(vector::empty()); - let payload = writer.into_bytes(); - - let approved_message = channel::new_approved_message( - source_chain, - message_id, - source_address, - its.value!(b"").channel().to_address(), - payload, - ); - - receive_deploy_interchain_token(&mut its, approved_message); - - clock.destroy_for_testing(); - sui::test_utils::destroy(its); -} - -#[test] -fun test_receive_deploy_interchain_token_with_distributor() { - let ctx = &mut tx_context::dummy(); - let clock = sui::clock::create_for_testing(ctx); - let mut its = create_for_testing(ctx); - - let source_chain = ascii::string(b"Chain Name"); - let message_id = ascii::string(b"Message Id"); - let source_address = ascii::string(b"Address"); - let name = b"Token Name"; - let symbol = b"Symbol"; - let remote_decimals = 8; - let decimals = if (remote_decimals > DECIMALS_CAP) DECIMALS_CAP - else remote_decimals; - let token_id: u256 = 1234; - let distributor = @0x1; - - create_unregistered_coin(&mut its, symbol, decimals, ctx); - - let mut writer = abi::new_writer(6); - writer - .write_u256(MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) - .write_u256(token_id) - .write_bytes(name) - .write_bytes(symbol) - .write_u256((remote_decimals as u256)) - .write_bytes(distributor.to_bytes()); - let payload = writer.into_bytes(); - - let approved_message = channel::new_approved_message( - source_chain, - message_id, - source_address, - its.value!(b"").channel().to_address(), - payload, - ); - - receive_deploy_interchain_token(&mut its, approved_message); - - clock.destroy_for_testing(); - sui::test_utils::destroy(its); -} - -#[test] -#[expected_failure(abort_code = EInvalidMessageType)] -fun test_receive_deploy_interchain_token_invalid_message_type() { - let ctx = &mut tx_context::dummy(); - let clock = sui::clock::create_for_testing(ctx); - let mut its = create_for_testing(ctx); - - let source_chain = ascii::string(b"Chain Name"); - let message_id = ascii::string(b"Message Id"); - let source_address = ascii::string(b"Address"); - let name = b"Token Name"; - let symbol = b"Symbol"; - let remote_decimals = 8; - let decimals = if (remote_decimals > DECIMALS_CAP) DECIMALS_CAP - else remote_decimals; - let token_id: u256 = 1234; - - create_unregistered_coin(&mut its, symbol, decimals, ctx); - - let mut writer = abi::new_writer(6); - writer - .write_u256(MESSAGE_TYPE_INTERCHAIN_TRANSFER) - .write_u256(token_id) - .write_bytes(name) - .write_bytes(symbol) - .write_u256((remote_decimals as u256)) - .write_bytes(b""); - let payload = writer.into_bytes(); - - let approved_message = channel::new_approved_message( - source_chain, - message_id, - source_address, - its.value!(b"").channel().to_address(), - payload, - ); - - receive_deploy_interchain_token(&mut its, approved_message); - - clock.destroy_for_testing(); - sui::test_utils::destroy(its); -} - -#[test] -fun test_give_unregistered_coin() { - let symbol = b"COIN"; - let decimals = 12; - let ctx = &mut tx_context::dummy(); - let mut its = create_for_testing(ctx); - - let (treasury_cap, coin_metadata) = its::coin::create_treasury_and_metadata( - symbol, - decimals, - ctx, - ); - - give_unregistered_coin(&mut its, treasury_cap, coin_metadata); - - sui::test_utils::destroy(its); -} - -#[test] -#[expected_failure(abort_code = ENonZeroTotalSupply)] -fun test_give_unregistered_coin_not_zero_total_supply() { - let symbol = b"COIN"; - let decimals = 12; - let ctx = &mut tx_context::dummy(); - let mut its = create_for_testing(ctx); - - let ( - mut treasury_cap, - coin_metadata, - ) = its::coin::create_treasury_and_metadata(symbol, decimals, ctx); - let coin = treasury_cap.mint(1, ctx); - - give_unregistered_coin(&mut its, treasury_cap, coin_metadata); - - sui::test_utils::destroy(its); - sui::test_utils::destroy(coin); -} - -#[test] -#[expected_failure(abort_code = EUnregisteredCoinHasUrl)] -fun test_give_unregistered_coin_with_url() { - let name = b"Coin"; - let symbol = b"COIN"; - let decimals = 12; - let ctx = &mut tx_context::dummy(); - let mut its = create_for_testing(ctx); - let url = sui::url::new_unsafe_from_bytes(b"url"); - - let ( - treasury_cap, - coin_metadata, - ) = its::coin::create_treasury_and_metadata_custom( - name, - symbol, - decimals, - option::some(url), - ctx, - ); - - give_unregistered_coin(&mut its, treasury_cap, coin_metadata); - - sui::test_utils::destroy(its); -} - -#[test] -#[expected_failure(abort_code = EModuleNameDoesNotMatchSymbol)] -fun test_give_unregistered_coin_module_name_missmatch() { - let symbol = b"SYMBOL"; - let decimals = 12; - let ctx = &mut tx_context::dummy(); - let mut its = create_for_testing(ctx); - - let (treasury_cap, coin_metadata) = its::coin::create_treasury_and_metadata( - symbol, - decimals, - ctx, + let (treasury_cap, coin_metadata) = its::coin::create_treasury_and_metadata( + symbol, + decimals, + ctx, ); give_unregistered_coin(&mut its, treasury_cap, coin_metadata); @@ -1455,46 +753,6 @@ fun test_mint_as_distributor() { channel.destroy(); } -#[test] -#[expected_failure(abort_code = ENotDistributor)] -fun test_mint_as_distributor_not_distributor() { - let ctx = &mut tx_context::dummy(); - let mut its = create_for_testing(ctx); - let symbol = b"COIN"; - let decimals = 9; - let remote_decimals = 18; - - let (treasury_cap, coin_metadata) = its::coin::create_treasury_and_metadata( - symbol, - decimals, - ctx, - ); - let coin_info = its::coin_info::from_metadata( - coin_metadata, - remote_decimals, - ); - let mut coin_management = its::coin_management::new_with_cap(treasury_cap); - - let channel = channel::new(ctx); - coin_management.add_distributor(@0x1); - let amount = 1234; - - let token_id = register_coin(&mut its, coin_info, coin_management); - let coin = mint_as_distributor( - &mut its, - &channel, - token_id, - amount, - ctx, - ); - - assert!(coin.value() == amount); - - sui::test_utils::destroy(its); - sui::test_utils::destroy(coin); - channel.destroy(); -} - #[test] fun test_mint_to_as_distributor() { let ctx = &mut tx_context::dummy(); @@ -1562,37 +820,6 @@ fun test_burn_as_distributor() { channel.destroy(); } -#[test] -#[expected_failure(abort_code = ENotDistributor)] -fun test_burn_as_distributor_not_distributor() { - let ctx = &mut tx_context::dummy(); - let mut its = create_for_testing(ctx); - let symbol = b"COIN"; - let decimals = 9; - let remote_decimals = 18; - let amount = 1234; - - let ( - mut treasury_cap, - coin_metadata, - ) = its::coin::create_treasury_and_metadata(symbol, decimals, ctx); - let coin = treasury_cap.mint(amount, ctx); - let coin_info = its::coin_info::from_metadata( - coin_metadata, - remote_decimals, - ); - let mut coin_management = its::coin_management::new_with_cap(treasury_cap); - - let channel = channel::new(ctx); - coin_management.add_distributor(@0x1); - - let token_id = register_coin(&mut its, coin_info, coin_management); - burn_as_distributor(&mut its, &channel, token_id, coin); - - sui::test_utils::destroy(its); - channel.destroy(); -} - #[test] fun test_set_trusted_address() { let ctx = &mut tx_context::dummy(); @@ -1620,134 +847,3 @@ fun test_set_trusted_address() { sui::test_utils::destroy(its); sui::test_utils::destroy(owner_cap); } - -#[test] -fun test_decode_approved_message_axelar_hub_sender() { - let ctx = &mut tx_context::dummy(); - let mut its = create_for_testing(ctx); - - let source_chain = ascii::string(ITS_HUB_CHAIN_NAME); - let source_address = ascii::string(b"Address"); - let message_id = ascii::string(b"message_id"); - let origin_chain = ascii::string(b"Source Chain"); - let payload = b"payload"; - - let mut writer = abi::new_writer(3); - writer.write_u256(MESSAGE_TYPE_RECEIVE_FROM_HUB); - writer.write_bytes(origin_chain.into_bytes()); - writer.write_bytes(payload); - let payload = writer.into_bytes(); - - let value = its.value_mut!(b""); - value.set_trusted_address(source_chain, source_address); - value.set_trusted_address( - origin_chain, - ascii::string(ITS_HUB_ROUTING_IDENTIFIER), - ); - - let approved_message = channel::new_approved_message( - source_chain, - message_id, - source_address, - value.channel().to_address(), - payload, - ); - - decode_approved_message(value, approved_message); - - sui::test_utils::destroy(its); -} - -#[test] -#[expected_failure(abort_code = EUntrustedChain)] -fun test_decode_approved_message_sender_not_hub() { - let ctx = &mut tx_context::dummy(); - let mut its = create_for_testing(ctx); - - let source_chain = ascii::string(b"Chain Name"); - let source_address = ascii::string(b"Address"); - let message_id = ascii::string(b"message_id"); - - let mut writer = abi::new_writer(3); - writer.write_u256(MESSAGE_TYPE_RECEIVE_FROM_HUB); - writer.write_bytes(b"Source Chain"); - writer.write_bytes(b"payload"); - let payload = writer.into_bytes(); - - let value = its.value_mut!(b""); - let approved_message = channel::new_approved_message( - source_chain, - message_id, - source_address, - value.channel().to_address(), - payload, - ); - - decode_approved_message(value, approved_message); - - sui::test_utils::destroy(its); -} - -#[test] -#[expected_failure(abort_code = EUntrustedChain)] -fun test_decode_approved_message_origin_not_hub_routed() { - let ctx = &mut tx_context::dummy(); - let mut its = create_for_testing(ctx); - - let source_chain = ascii::string(ITS_HUB_CHAIN_NAME); - let source_address = ascii::string(b"Address"); - let message_id = ascii::string(b"message_id"); - let origin_chain = ascii::string(b"Source Chain"); - let origin_trusted_address = ascii::string(b"Origin Trusted Address"); - let payload = b"payload"; - - let mut writer = abi::new_writer(3); - writer.write_u256(MESSAGE_TYPE_RECEIVE_FROM_HUB); - writer.write_bytes(origin_chain.into_bytes()); - writer.write_bytes(payload); - let payload = writer.into_bytes(); - - let value = its.value_mut!(b""); - value.set_trusted_address(source_chain, source_address); - value.set_trusted_address(origin_chain, origin_trusted_address); - - let approved_message = channel::new_approved_message( - source_chain, - message_id, - source_address, - value.channel().to_address(), - payload, - ); - - decode_approved_message(value, approved_message); - - sui::test_utils::destroy(its); -} - -#[test] -fun test_prepare_message_to_hub() { - let ctx = &mut tx_context::dummy(); - let mut its = create_for_testing(ctx); - - let destination_chain = ascii::string(b"Destination Chain"); - let hub_address = ascii::string(b"Address"); - - let payload = b"payload"; - - let value = its.value_mut!(b""); - value.set_trusted_address(ascii::string(ITS_HUB_CHAIN_NAME), hub_address); - value.set_trusted_address( - destination_chain, - ascii::string(ITS_HUB_ROUTING_IDENTIFIER), - ); - - let message_ticket = prepare_message(value, destination_chain, payload); - - assert!( - message_ticket.destination_chain() == ascii::string(ITS_HUB_CHAIN_NAME), - ); - assert!(message_ticket.destination_address() == hub_address); - - sui::test_utils::destroy(its); - sui::test_utils::destroy(message_ticket); -} diff --git a/move/its/sources/versioned/its_v0.move b/move/its/sources/versioned/its_v0.move index ffb66693..785ecbd8 100644 --- a/move/its/sources/versioned/its_v0.move +++ b/move/its/sources/versioned/its_v0.move @@ -1,19 +1,27 @@ module its::its_v0; -use axelar_gateway::channel::Channel; +use abi::abi; +use axelar_gateway::channel::{Channel, ApprovedMessage}; +use axelar_gateway::gateway; +use axelar_gateway::message_ticket::MessageTicket; use its::address_tracker::{Self, InterchainAddressTracker}; use its::coin_data::{Self, CoinData}; -use its::coin_info::CoinInfo; -use its::coin_management::CoinManagement; +use its::coin_info::{Self, CoinInfo}; +use its::coin_management::{Self, CoinManagement}; +use its::events; +use its::interchain_transfer_ticket::InterchainTransferTicket; use its::token_id::{Self, TokenId, UnregisteredTokenId}; use its::trusted_addresses::TrustedAddresses; use its::unregistered_coin_data::{Self, UnregisteredCoinData}; +use its::utils as its_utils; use relayer_discovery::discovery::RelayerDiscovery; -use std::ascii::String; +use std::ascii::{Self, String}; use std::string; use std::type_name::{Self, TypeName}; +use sui::address; use sui::bag::{Self, Bag}; -use sui::coin::{TreasuryCap, CoinMetadata}; +use sui::clock::Clock; +use sui::coin::{Self, TreasuryCap, CoinMetadata, Coin}; use sui::table::{Self, Table}; use version_control::version_control::VersionControl; @@ -21,8 +29,60 @@ use version_control::version_control::VersionControl; // Errors // ------ #[error] -const EUnregisteredCoin: vector = b"Trying to find a coin that doesn't exist."; +const EUnregisteredCoin: vector = + b"Trying to find a coin that doesn't exist."; +#[error] +const EUntrustedAddress: vector = + b"the sender that sent this message is not trusted."; +#[error] +const EInvalidMessageType: vector = + b"the message type received is not supported."; +#[error] +const EWrongDestination: vector = + b"the channel trying to receive this call is not the destination."; +#[error] +const EInterchainTransferHasData: vector = + b"interchain transfer with data trying to be processed as an interchain transfer."; +#[error] +const EInterchainTransferHasNoData: vector = + b"interchain transfer trying to be proccessed as an interchain transfer"; +#[error] +const EModuleNameDoesNotMatchSymbol: vector = + b"the module name does not match the symbol."; +#[error] +const ENotDistributor: vector = b"only the distributor can mint."; +#[error] +const ENonZeroTotalSupply: vector = + b"trying to give a token that has had some supply already minted."; +#[error] +const EUnregisteredCoinHasUrl: vector = + b"the interchain token that is being registered has a URL."; +#[error] +const EUntrustedChain: vector = b"the chain is not trusted."; +#[error] +const ENewerTicket: vector = b"cannot proccess newer tickets."; +// === MESSAGE TYPES === +const MESSAGE_TYPE_INTERCHAIN_TRANSFER: u256 = 0; +const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN: u256 = 1; +// onst MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER: u256 = 2; +const MESSAGE_TYPE_SEND_TO_HUB: u256 = 3; +const MESSAGE_TYPE_RECEIVE_FROM_HUB: u256 = 4; + +// === HUB CONSTANTS === +// Chain name for Axelar. This is used for routing ITS calls via ITS hub on +// Axelar. +const ITS_HUB_CHAIN_NAME: vector = b"Axelarnet"; +// Identifier to be used as destination address for chains that route to hub. +// For Sui this will probably be every supported chain. +const ITS_HUB_ROUTING_IDENTIFIER: vector = b"hub"; + +// === The maximum number of decimals allowed === +const DECIMALS_CAP: u8 = 9; + +// ----- +// Types +// ----- public struct ITS_v0 has store { channel: Channel, address_tracker: InterchainAddressTracker, @@ -74,59 +134,6 @@ public(package) fun registered_coin_type( &self.registered_coin_types[token_id] } -public(package) fun coin_data( - self: &ITS_v0, - token_id: TokenId, -): &CoinData { - assert!(self.registered_coins.contains(token_id), EUnregisteredCoin); - &self.registered_coins[token_id] -} - -public(package) fun coin_info( - self: &ITS_v0, - token_id: TokenId, -): &CoinInfo { - coin_data(self, token_id).coin_info() -} - -public(package) fun token_name( - self: &ITS_v0, - token_id: TokenId, -): string::String { - coin_info(self, token_id).name() -} - -public(package) fun token_symbol(self: &ITS_v0, token_id: TokenId): String { - coin_info(self, token_id).symbol() -} - -public(package) fun token_decimals(self: &ITS_v0, token_id: TokenId): u8 { - coin_info(self, token_id).decimals() -} - -public(package) fun token_remote_decimals( - self: &ITS_v0, - token_id: TokenId, -): u8 { - coin_info(self, token_id).remote_decimals() -} - -public(package) fun trusted_address(self: &ITS_v0, chain_name: String): String { - *self.address_tracker.trusted_address(chain_name) -} - -public(package) fun is_trusted_address( - self: &ITS_v0, - source_chain: String, - source_address: String, -): bool { - self.address_tracker.is_trusted_address(source_chain, source_address) -} - -public(package) fun channel_id(self: &ITS_v0): ID { - self.channel.id() -} - public(package) fun channel_address(self: &ITS_v0): address { self.channel.to_address() } @@ -153,7 +160,7 @@ public(package) fun set_trusted_address( public(package) fun remove_trusted_address( self: &mut ITS_v0, chain_name: String, -) { +) { self.address_tracker.remove_trusted_address(chain_name); } @@ -163,19 +170,24 @@ public(package) fun set_trusted_addresses( ) { let (chain_names, trusted_addresses) = trusted_addresses.destroy(); - chain_names.zip_do!(trusted_addresses, |chain_name, trusted_address| self.set_trusted_address( + chain_names.zip_do!( + trusted_addresses, + |chain_name, trusted_address| self.set_trusted_address( chain_name, trusted_address, - )); + ), + ); } public(package) fun remove_trusted_addresses( self: &mut ITS_v0, chain_names: vector, ) { - chain_names.do!(|chain_name| self.remove_trusted_address( + chain_names.do!( + |chain_name| self.remove_trusted_address( chain_name, - )); + ), + ); } public(package) fun coin_data_mut( @@ -198,13 +210,272 @@ public(package) fun version_control(self: &ITS_v0): &VersionControl { &self.version_control } -public(package) fun version_control_mut( +public(package) fun register_coin( + self: &mut ITS_v0, + coin_info: CoinInfo, + coin_management: CoinManagement, +): TokenId { + let token_id = token_id::from_coin_data(&coin_info, &coin_management); + + self.add_registered_coin(token_id, coin_management, coin_info); + + events::coin_registered( + token_id, + ); + + token_id +} + +public(package) fun deploy_remote_interchain_token( + self: &ITS_v0, + token_id: TokenId, + destination_chain: String, +): MessageTicket { + let coin_info = self.coin_info(token_id); + + let name = coin_info.name(); + let symbol = coin_info.symbol(); + let decimals = coin_info.decimals(); + + let mut writer = abi::new_writer(6); + + writer + .write_u256(MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) + .write_u256(token_id.to_u256()) + .write_bytes(*name.as_bytes()) + .write_bytes(*symbol.as_bytes()) + .write_u256((decimals as u256)) + .write_bytes(vector::empty()); + + prepare_message(self, destination_chain, writer.into_bytes()) +} + +public(package) fun send_interchain_transfer( + self: &mut ITS_v0, + ticket: InterchainTransferTicket, + current_version: u64, + clock: &Clock, +): MessageTicket { + let ( + token_id, + balance, + source_address, + destination_chain, + destination_address, + metadata, + version, + ) = ticket.destroy(); + assert!(version <= current_version, ENewerTicket); + + let amount = self + .coin_management_mut(token_id) + .take_balance(balance, clock); + let (_version, data) = its_utils::decode_metadata(metadata); + let mut writer = abi::new_writer(6); + + writer + .write_u256(MESSAGE_TYPE_INTERCHAIN_TRANSFER) + .write_u256(token_id.to_u256()) + .write_bytes(source_address.to_bytes()) + .write_bytes(destination_address) + .write_u256(amount) + .write_bytes(data); + + self.prepare_message(destination_chain, writer.into_bytes()) +} + +public(package) fun receive_interchain_transfer( + self: &mut ITS_v0, + approved_message: ApprovedMessage, + clock: &Clock, + ctx: &mut TxContext, +) { + let (_, payload) = self.decode_approved_message(approved_message); + let mut reader = abi::new_reader(payload); + assert!( + reader.read_u256() == MESSAGE_TYPE_INTERCHAIN_TRANSFER, + EInvalidMessageType, + ); + + let token_id = token_id::from_u256(reader.read_u256()); + reader.skip_slot(); // skip source_address + let destination_address = address::from_bytes(reader.read_bytes()); + let amount = reader.read_u256(); + let data = reader.read_bytes(); + + assert!(data.is_empty(), EInterchainTransferHasData); + + let coin = self + .coin_management_mut(token_id) + .give_coin(amount, clock, ctx); + + transfer::public_transfer(coin, destination_address) +} + +public(package) fun receive_interchain_transfer_with_data( + self: &mut ITS_v0, + approved_message: ApprovedMessage, + channel: &Channel, + clock: &Clock, + ctx: &mut TxContext, +): (String, vector, vector, Coin) { + let (source_chain, payload) = self.decode_approved_message( + approved_message, + ); + let mut reader = abi::new_reader(payload); + assert!( + reader.read_u256() == MESSAGE_TYPE_INTERCHAIN_TRANSFER, + EInvalidMessageType, + ); + + let token_id = token_id::from_u256(reader.read_u256()); + + let source_address = reader.read_bytes(); + let destination_address = reader.read_bytes(); + let amount = reader.read_u256(); + let data = reader.read_bytes(); + + assert!( + address::from_bytes(destination_address) == channel.to_address(), + EWrongDestination, + ); + assert!(!data.is_empty(), EInterchainTransferHasNoData); + + let coin = self.coin_management_mut(token_id).give_coin(amount, clock, ctx); + + (source_chain, source_address, data, coin) +} + +public(package) fun receive_deploy_interchain_token( + self: &mut ITS_v0, + approved_message: ApprovedMessage, +) { + let (_, payload) = self.decode_approved_message(approved_message); + let mut reader = abi::new_reader(payload); + assert!( + reader.read_u256() == MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN, + EInvalidMessageType, + ); + + let token_id = token_id::from_u256(reader.read_u256()); + let name = string::utf8(reader.read_bytes()); + let symbol = ascii::string(reader.read_bytes()); + let remote_decimals = (reader.read_u256() as u8); + let distributor_bytes = reader.read_bytes(); + let decimals = if (remote_decimals > DECIMALS_CAP) DECIMALS_CAP + else remote_decimals; + let (treasury_cap, mut coin_metadata) = self.remove_unregistered_coin( + token_id::unregistered_token_id(&symbol, decimals), + ); + + treasury_cap.update_name(&mut coin_metadata, name); + + let mut coin_management = coin_management::new_with_cap(treasury_cap); + let coin_info = coin_info::from_metadata(coin_metadata, remote_decimals); + + if (distributor_bytes.length() > 0) { + let distributor = address::from_bytes(distributor_bytes); + coin_management.add_distributor(distributor); + }; + + self.add_registered_coin(token_id, coin_management, coin_info); +} + +public(package) fun give_unregistered_coin( + self: &mut ITS_v0, + treasury_cap: TreasuryCap, + mut coin_metadata: CoinMetadata, +) { + assert!(treasury_cap.total_supply() == 0, ENonZeroTotalSupply); + assert!( + coin::get_icon_url(&coin_metadata).is_none(), + EUnregisteredCoinHasUrl, + ); + + treasury_cap.update_description(&mut coin_metadata, string::utf8(b"")); + + let decimals = coin_metadata.get_decimals(); + let symbol = coin_metadata.get_symbol(); + + let module_name = type_name::get_module(&type_name::get()); + assert!( + &module_name == &its_utils::module_from_symbol(&symbol), + EModuleNameDoesNotMatchSymbol, + ); + + let token_id = token_id::unregistered_token_id(&symbol, decimals); + + self.add_unregistered_coin(token_id, treasury_cap, coin_metadata); +} + +public(package) fun mint_as_distributor( self: &mut ITS_v0, -): &mut VersionControl { - &mut self.version_control + channel: &Channel, + token_id: TokenId, + amount: u64, + ctx: &mut TxContext, +): Coin { + let coin_management = self.coin_management_mut(token_id); + let distributor = channel.to_address(); + + assert!(coin_management.is_distributor(distributor), ENotDistributor); + + coin_management.mint(amount, ctx) +} + +public(package) fun mint_to_as_distributor( + self: &mut ITS_v0, + channel: &Channel, + token_id: TokenId, + to: address, + amount: u64, + ctx: &mut TxContext, +) { + let coin_management = self.coin_management_mut(token_id); + let distributor = channel.to_address(); + + assert!(coin_management.is_distributor(distributor), ENotDistributor); + + let coin = coin_management.mint(amount, ctx); + + transfer::public_transfer(coin, to); } -public(package) fun coin_management_mut( +public(package) fun burn_as_distributor( + self: &mut ITS_v0, + channel: &Channel, + token_id: TokenId, + coin: Coin, +) { + let coin_management = self.coin_management_mut(token_id); + let distributor = channel.to_address(); + + assert!(coin_management.is_distributor(distributor), ENotDistributor); + + coin_management.burn(coin.into_balance()); +} + +// ----------------- +// Private Functions +// ----------------- +fun coin_data(self: &ITS_v0, token_id: TokenId): &CoinData { + assert!(self.registered_coins.contains(token_id), EUnregisteredCoin); + &self.registered_coins[token_id] +} + +fun coin_info(self: &ITS_v0, token_id: TokenId): &CoinInfo { + coin_data(self, token_id).coin_info() +} + +fun is_trusted_address( + self: &ITS_v0, + source_chain: String, + source_address: String, +): bool { + self.address_tracker.is_trusted_address(source_chain, source_address) +} + +fun coin_management_mut( self: &mut ITS_v0, token_id: TokenId, ): &mut CoinManagement { @@ -212,7 +483,7 @@ public(package) fun coin_management_mut( coin_data.coin_management_mut() } -public(package) fun add_unregistered_coin( +fun add_unregistered_coin( self: &mut ITS_v0, token_id: UnregisteredTokenId, treasury_cap: TreasuryCap, @@ -232,7 +503,7 @@ public(package) fun add_unregistered_coin( add_unregistered_coin_type(self, token_id, type_name); } -public(package) fun remove_unregistered_coin( +fun remove_unregistered_coin( self: &mut ITS_v0, token_id: UnregisteredTokenId, ): (TreasuryCap, CoinMetadata) { @@ -246,30 +517,10 @@ public(package) fun remove_unregistered_coin( (treasury_cap, coin_metadata) } -public(package) fun add_registered_coin( - self: &mut ITS_v0, - token_id: TokenId, - mut coin_management: CoinManagement, - coin_info: CoinInfo, -) { - coin_management.set_scaling(coin_info.scaling()); - self - .registered_coins - .add( - token_id, - coin_data::new( - coin_management, - coin_info, - ), - ); - - let type_name = type_name::get(); - add_registered_coin_type(self, token_id, type_name); +fun trusted_address(self: &ITS_v0, chain_name: String): String { + *self.address_tracker.trusted_address(chain_name) } -// ----------------- -// Private Functions -// ----------------- fun add_unregistered_coin_type( self: &mut ITS_v0, token_id: UnregisteredTokenId, @@ -293,9 +544,140 @@ fun add_registered_coin_type( self.registered_coin_types.add(token_id, type_name); } +fun add_registered_coin( + self: &mut ITS_v0, + token_id: TokenId, + mut coin_management: CoinManagement, + coin_info: CoinInfo, +) { + coin_management.set_scaling(coin_info.scaling()); + self + .registered_coins + .add( + token_id, + coin_data::new( + coin_management, + coin_info, + ), + ); + + let type_name = type_name::get(); + add_registered_coin_type(self, token_id, type_name); +} + +/// Send a payload to a destination chain. The destination chain needs to have a +/// trusted address. +fun prepare_message( + self: &ITS_v0, + mut destination_chain: String, + mut payload: vector, +): MessageTicket { + let mut destination_address = self.trusted_address(destination_chain); + + // Prevent sending directly to the ITS Hub chain. This is not supported yet, + // so fail early to prevent the user from having their funds stuck. + assert!( + destination_chain.into_bytes() != ITS_HUB_CHAIN_NAME, + EUntrustedChain, + ); + + // Check whether the ITS call should be routed via ITS hub for this + // destination chain + if (destination_address.into_bytes() == ITS_HUB_ROUTING_IDENTIFIER) { + let mut writer = abi::new_writer(3); + writer.write_u256(MESSAGE_TYPE_SEND_TO_HUB); + writer.write_bytes(destination_chain.into_bytes()); + writer.write_bytes(payload); + payload = writer.into_bytes(); + destination_chain = ascii::string(ITS_HUB_CHAIN_NAME); + destination_address = self.trusted_address(destination_chain); + }; + + gateway::prepare_message( + &self.channel, + destination_chain, + destination_address, + payload, + ) +} + +/// Decode an approved call and check that the source chain is trusted. +fun decode_approved_message( + self: &ITS_v0, + approved_message: ApprovedMessage, +): (String, vector) { + let (mut source_chain, _, source_address, mut payload) = self + .channel + .consume_approved_message(approved_message); + + assert!( + self.is_trusted_address(source_chain, source_address), + EUntrustedAddress, + ); + + let mut reader = abi::new_reader(payload); + if (reader.read_u256() == MESSAGE_TYPE_RECEIVE_FROM_HUB) { + assert!( + source_chain.into_bytes() == ITS_HUB_CHAIN_NAME, + EUntrustedChain, + ); + + source_chain = ascii::string(reader.read_bytes()); + payload = reader.read_bytes(); + + assert!( + self.trusted_address(source_chain).into_bytes() == ITS_HUB_ROUTING_IDENTIFIER, + EUntrustedChain, + ); + } else { + assert!( + source_chain.into_bytes() != ITS_HUB_CHAIN_NAME, + EUntrustedChain, + ); + }; + + (source_chain, payload) +} // --------- // Test Only // --------- +#[test_only] +use axelar_gateway::channel; +#[test_only] +use its::coin::COIN; + +#[test_only] +fun create_for_testing(ctx: &mut TxContext): ITS_v0 { + let mut self = new(version_control::version_control::new(vector[]), ctx); + + self.set_trusted_address( + std::ascii::string(b"Chain Name"), + std::ascii::string(b"Address"), + ); + + self +} + +#[test_only] +public fun create_unregistered_coin( + self: &mut ITS_v0, + symbol: vector, + decimals: u8, + ctx: &mut TxContext, +) { + let (treasury_cap, coin_metadata) = its::coin::create_treasury_and_metadata( + symbol, + decimals, + ctx, + ); + let token_id = token_id::unregistered_token_id( + &ascii::string(symbol), + decimals, + ); + + self.add_unregistered_coin(token_id, treasury_cap, coin_metadata); +} + #[test_only] public(package) fun add_unregistered_coin_type_for_testing( self: &mut ITS_v0, @@ -329,3 +711,641 @@ public(package) fun remove_registered_coin_type_for_testing( ): TypeName { self.remove_registered_coin_type_for_testing(token_id) } + +#[test_only] +public(package) fun trusted_address_for_testing( + self: &ITS_v0, + chain_name: String, +): String { + *self.address_tracker.trusted_address(chain_name) +} + +// ----- +// Tests +// ----- +#[test] +fun test_decode_approved_message_axelar_hub_sender() { + let ctx = &mut tx_context::dummy(); + let mut self = create_for_testing(ctx); + + let source_chain = ascii::string(ITS_HUB_CHAIN_NAME); + let source_address = ascii::string(b"Address"); + let message_id = ascii::string(b"message_id"); + let origin_chain = ascii::string(b"Source Chain"); + let payload = b"payload"; + + let mut writer = abi::new_writer(3); + writer.write_u256(MESSAGE_TYPE_RECEIVE_FROM_HUB); + writer.write_bytes(origin_chain.into_bytes()); + writer.write_bytes(payload); + let payload = writer.into_bytes(); + + self.set_trusted_address(source_chain, source_address); + self.set_trusted_address( + origin_chain, + ascii::string(ITS_HUB_ROUTING_IDENTIFIER), + ); + + let approved_message = channel::new_approved_message( + source_chain, + message_id, + source_address, + self.channel.to_address(), + payload, + ); + + self.decode_approved_message(approved_message); + + sui::test_utils::destroy(self); +} + +#[test] +#[expected_failure(abort_code = EUntrustedChain)] +fun test_decode_approved_message_sender_not_hub() { + let ctx = &mut tx_context::dummy(); + let self = create_for_testing(ctx); + + let source_chain = ascii::string(b"Chain Name"); + let source_address = ascii::string(b"Address"); + let message_id = ascii::string(b"message_id"); + + let mut writer = abi::new_writer(3); + writer.write_u256(MESSAGE_TYPE_RECEIVE_FROM_HUB); + writer.write_bytes(b"Source Chain"); + writer.write_bytes(b"payload"); + let payload = writer.into_bytes(); + + let approved_message = channel::new_approved_message( + source_chain, + message_id, + source_address, + self.channel.to_address(), + payload, + ); + + self.decode_approved_message(approved_message); + + sui::test_utils::destroy(self); +} + +#[test] +#[expected_failure(abort_code = EUntrustedChain)] +fun test_decode_approved_message_origin_not_hub_routed() { + let ctx = &mut tx_context::dummy(); + let mut self = create_for_testing(ctx); + + let source_chain = ascii::string(ITS_HUB_CHAIN_NAME); + let source_address = ascii::string(b"Address"); + let message_id = ascii::string(b"message_id"); + let origin_chain = ascii::string(b"Source Chain"); + let origin_trusted_address = ascii::string(b"Origin Trusted Address"); + let payload = b"payload"; + + let mut writer = abi::new_writer(3); + writer.write_u256(MESSAGE_TYPE_RECEIVE_FROM_HUB); + writer.write_bytes(origin_chain.into_bytes()); + writer.write_bytes(payload); + let payload = writer.into_bytes(); + + self.set_trusted_address(source_chain, source_address); + self.set_trusted_address(origin_chain, origin_trusted_address); + + let approved_message = channel::new_approved_message( + source_chain, + message_id, + source_address, + self.channel.to_address(), + payload, + ); + + self.decode_approved_message(approved_message); + + sui::test_utils::destroy(self); +} + +#[test] +fun test_prepare_message_to_hub() { + let ctx = &mut tx_context::dummy(); + let mut self = create_for_testing(ctx); + + let destination_chain = ascii::string(b"Destination Chain"); + let hub_address = ascii::string(b"Address"); + + let payload = b"payload"; + + self.set_trusted_address(ascii::string(ITS_HUB_CHAIN_NAME), hub_address); + self.set_trusted_address( + destination_chain, + ascii::string(ITS_HUB_ROUTING_IDENTIFIER), + ); + + let message_ticket = self.prepare_message(destination_chain, payload); + + assert!( + message_ticket.destination_chain() == ascii::string(ITS_HUB_CHAIN_NAME), + ); + assert!(message_ticket.destination_address() == hub_address); + + sui::test_utils::destroy(self); + sui::test_utils::destroy(message_ticket); +} + +#[test] +#[expected_failure(abort_code = EInvalidMessageType)] +fun test_receive_interchain_transfer_invalid_message_type() { + let ctx = &mut tx_context::dummy(); + let clock = sui::clock::create_for_testing(ctx); + let mut self = create_for_testing(ctx); + + let coin_info = its::coin_info::from_info( + string::utf8(b"Name"), + ascii::string(b"Symbol"), + 10, + 12, + ); + + let amount = 1234; + let mut coin_management = its::coin_management::new_locked(); + let coin = sui::coin::mint_for_testing(amount, ctx); + coin_management.take_balance(coin.into_balance(), &clock); + + let token_id = self.register_coin(coin_info, coin_management); + let source_chain = ascii::string(b"Chain Name"); + let message_id = ascii::string(b"Message Id"); + let message_source_address = ascii::string(b"Address"); + let its_source_address = b"Source Address"; + let destination_address = @0x1; + + let mut writer = abi::new_writer(6); + writer + .write_u256(MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) + .write_u256(token_id.to_u256()) + .write_bytes(its_source_address) + .write_bytes(destination_address.to_bytes()) + .write_u256((amount as u256)) + .write_bytes(b""); + let payload = writer.into_bytes(); + + let approved_message = channel::new_approved_message( + source_chain, + message_id, + message_source_address, + self.channel.to_address(), + payload, + ); + + self.receive_interchain_transfer(approved_message, &clock, ctx); + + clock.destroy_for_testing(); + sui::test_utils::destroy(self); +} + +#[test] +#[expected_failure(abort_code = EInterchainTransferHasData)] +fun test_receive_interchain_transfer_passed_data() { + let ctx = &mut tx_context::dummy(); + let clock = sui::clock::create_for_testing(ctx); + let mut self = create_for_testing(ctx); + + let coin_info = its::coin_info::from_info( + string::utf8(b"Name"), + ascii::string(b"Symbol"), + 10, + 12, + ); + + let amount = 1234; + let mut coin_management = its::coin_management::new_locked(); + let coin = sui::coin::mint_for_testing(amount, ctx); + coin_management.take_balance(coin.into_balance(), &clock); + + let token_id = self.register_coin(coin_info, coin_management); + let source_chain = ascii::string(b"Chain Name"); + let message_id = ascii::string(b"Message Id"); + let message_source_address = ascii::string(b"Address"); + let its_source_address = b"Source Address"; + let destination_address = @0x1; + + let mut writer = abi::new_writer(6); + writer + .write_u256(MESSAGE_TYPE_INTERCHAIN_TRANSFER) + .write_u256(token_id.to_u256()) + .write_bytes(its_source_address) + .write_bytes(destination_address.to_bytes()) + .write_u256((amount as u256)) + .write_bytes(b"some data"); + let payload = writer.into_bytes(); + + let approved_message = channel::new_approved_message( + source_chain, + message_id, + message_source_address, + self.channel.to_address(), + payload, + ); + + self.receive_interchain_transfer(approved_message, &clock, ctx); + + clock.destroy_for_testing(); + sui::test_utils::destroy(self); +} + +#[test] +#[expected_failure(abort_code = EInvalidMessageType)] +fun test_receive_interchain_transfer_with_data_invalid_message_type() { + let ctx = &mut tx_context::dummy(); + let clock = sui::clock::create_for_testing(ctx); + let mut self = create_for_testing(ctx); + let coin_info = its::coin_info::from_info( + string::utf8(b"Name"), + ascii::string(b"Symbol"), + 10, + 12, + ); + + let amount = 1234; + let mut coin_management = its::coin_management::new_locked(); + let coin = sui::coin::mint_for_testing(amount, ctx); + coin_management.take_balance(coin.into_balance(), &clock); + + let token_id = self.register_coin(coin_info, coin_management); + let source_chain = ascii::string(b"Chain Name"); + let message_id = ascii::string(b"Message Id"); + let message_source_address = ascii::string(b"Address"); + let its_source_address = b"Source Address"; + let channel = channel::new(ctx); + let destination_address = channel.to_address(); + + let mut writer = abi::new_writer(6); + writer + .write_u256(MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) + .write_u256(token_id.to_u256()) + .write_bytes(its_source_address) + .write_bytes(destination_address.to_bytes()) + .write_u256((amount as u256)) + .write_bytes(b"some_data"); + let payload = writer.into_bytes(); + + let approved_message = channel::new_approved_message( + source_chain, + message_id, + message_source_address, + self.channel.to_address(), + payload, + ); + + let (_, _, _, received_coin) = self.receive_interchain_transfer_with_data< + COIN, + >( + approved_message, + &channel, + &clock, + ctx, + ); + + clock.destroy_for_testing(); + channel.destroy(); + sui::test_utils::destroy(self); + sui::test_utils::destroy(received_coin); +} + +#[test] +#[expected_failure(abort_code = EWrongDestination)] +fun test_receive_interchain_transfer_with_data_wrong_destination() { + let ctx = &mut tx_context::dummy(); + let clock = sui::clock::create_for_testing(ctx); + let mut self = create_for_testing(ctx); + + let coin_info = its::coin_info::from_info( + string::utf8(b"Name"), + ascii::string(b"Symbol"), + 10, + 12, + ); + + let amount = 1234; + let mut coin_management = its::coin_management::new_locked(); + let coin = sui::coin::mint_for_testing(amount, ctx); + coin_management.take_balance(coin.into_balance(), &clock); + + let token_id = self.register_coin(coin_info, coin_management); + let source_chain = ascii::string(b"Chain Name"); + let message_id = ascii::string(b"Message Id"); + let message_source_address = ascii::string(b"Address"); + let its_source_address = b"Source Address"; + let channel = channel::new(ctx); + let destination_address = @0x1; + + let mut writer = abi::new_writer(6); + writer + .write_u256(MESSAGE_TYPE_INTERCHAIN_TRANSFER) + .write_u256(token_id.to_u256()) + .write_bytes(its_source_address) + .write_bytes(destination_address.to_bytes()) + .write_u256((amount as u256)) + .write_bytes(b"some_data"); + let payload = writer.into_bytes(); + + let approved_message = channel::new_approved_message( + source_chain, + message_id, + message_source_address, + self.channel.to_address(), + payload, + ); + + let (_, _, _, received_coin) = self.receive_interchain_transfer_with_data< + COIN, + >( + approved_message, + &channel, + &clock, + ctx, + ); + + clock.destroy_for_testing(); + channel.destroy(); + sui::test_utils::destroy(self); + sui::test_utils::destroy(received_coin); +} + +#[test] +#[expected_failure(abort_code = EInterchainTransferHasNoData)] +fun test_receive_interchain_transfer_with_data_no_data() { + let ctx = &mut tx_context::dummy(); + let clock = sui::clock::create_for_testing(ctx); + let mut self = create_for_testing(ctx); + + let coin_info = its::coin_info::from_info( + string::utf8(b"Name"), + ascii::string(b"Symbol"), + 10, + 12, + ); + + let amount = 1234; + let mut coin_management = its::coin_management::new_locked(); + let coin = sui::coin::mint_for_testing(amount, ctx); + coin_management.take_balance(coin.into_balance(), &clock); + + let token_id = self.register_coin(coin_info, coin_management); + let source_chain = ascii::string(b"Chain Name"); + let message_id = ascii::string(b"Message Id"); + let message_source_address = ascii::string(b"Address"); + let its_source_address = b"Source Address"; + let channel = channel::new(ctx); + let destination_address = channel.to_address(); + + let mut writer = abi::new_writer(6); + writer + .write_u256(MESSAGE_TYPE_INTERCHAIN_TRANSFER) + .write_u256(token_id.to_u256()) + .write_bytes(its_source_address) + .write_bytes(destination_address.to_bytes()) + .write_u256((amount as u256)) + .write_bytes(b""); + let payload = writer.into_bytes(); + + let approved_message = channel::new_approved_message( + source_chain, + message_id, + message_source_address, + self.channel.to_address(), + payload, + ); + + let (_, _, _, received_coin) = self.receive_interchain_transfer_with_data< + COIN, + >( + approved_message, + &channel, + &clock, + ctx, + ); + + clock.destroy_for_testing(); + channel.destroy(); + sui::test_utils::destroy(self); + sui::test_utils::destroy(received_coin); +} + +#[test] +fun test_receive_deploy_interchain_token_with_distributor() { + let ctx = &mut tx_context::dummy(); + let clock = sui::clock::create_for_testing(ctx); + let mut self = create_for_testing(ctx); + + let source_chain = ascii::string(b"Chain Name"); + let message_id = ascii::string(b"Message Id"); + let source_address = ascii::string(b"Address"); + let name = b"Token Name"; + let symbol = b"Symbol"; + let remote_decimals = 8; + let decimals = if (remote_decimals > DECIMALS_CAP) DECIMALS_CAP + else remote_decimals; + let token_id: u256 = 1234; + let distributor = @0x1; + + self.create_unregistered_coin(symbol, decimals, ctx); + + let mut writer = abi::new_writer(6); + writer + .write_u256(MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) + .write_u256(token_id) + .write_bytes(name) + .write_bytes(symbol) + .write_u256((remote_decimals as u256)) + .write_bytes(distributor.to_bytes()); + let payload = writer.into_bytes(); + + let approved_message = channel::new_approved_message( + source_chain, + message_id, + source_address, + self.channel.to_address(), + payload, + ); + + self.receive_deploy_interchain_token(approved_message); + + clock.destroy_for_testing(); + sui::test_utils::destroy(self); +} + +#[test] +#[expected_failure(abort_code = EInvalidMessageType)] +fun test_receive_deploy_interchain_token_invalid_message_type() { + let ctx = &mut tx_context::dummy(); + let clock = sui::clock::create_for_testing(ctx); + let mut self = create_for_testing(ctx); + + let source_chain = ascii::string(b"Chain Name"); + let message_id = ascii::string(b"Message Id"); + let source_address = ascii::string(b"Address"); + let name = b"Token Name"; + let symbol = b"Symbol"; + let remote_decimals = 8; + let decimals = if (remote_decimals > DECIMALS_CAP) DECIMALS_CAP + else remote_decimals; + let token_id: u256 = 1234; + + self.create_unregistered_coin(symbol, decimals, ctx); + + let mut writer = abi::new_writer(6); + writer + .write_u256(MESSAGE_TYPE_INTERCHAIN_TRANSFER) + .write_u256(token_id) + .write_bytes(name) + .write_bytes(symbol) + .write_u256((remote_decimals as u256)) + .write_bytes(b""); + let payload = writer.into_bytes(); + + let approved_message = channel::new_approved_message( + source_chain, + message_id, + source_address, + self.channel.to_address(), + payload, + ); + + self.receive_deploy_interchain_token(approved_message); + + clock.destroy_for_testing(); + sui::test_utils::destroy(self); +} + +#[test] +#[expected_failure(abort_code = EUnregisteredCoinHasUrl)] +fun test_give_unregistered_coin_with_url() { + let name = b"Coin"; + let symbol = b"COIN"; + let decimals = 12; + let ctx = &mut tx_context::dummy(); + let mut self = create_for_testing(ctx); + let url = sui::url::new_unsafe_from_bytes(b"url"); + + let ( + treasury_cap, + coin_metadata, + ) = its::coin::create_treasury_and_metadata_custom( + name, + symbol, + decimals, + option::some(url), + ctx, + ); + + self.give_unregistered_coin(treasury_cap, coin_metadata); + + sui::test_utils::destroy(self); +} + +#[test] +#[expected_failure(abort_code = ENotDistributor)] +fun test_burn_as_distributor_not_distributor() { + let ctx = &mut tx_context::dummy(); + let mut self = create_for_testing(ctx); + let symbol = b"COIN"; + let decimals = 9; + let remote_decimals = 18; + let amount = 1234; + + let ( + mut treasury_cap, + coin_metadata, + ) = its::coin::create_treasury_and_metadata(symbol, decimals, ctx); + let coin = treasury_cap.mint(amount, ctx); + let coin_info = its::coin_info::from_metadata( + coin_metadata, + remote_decimals, + ); + let mut coin_management = its::coin_management::new_with_cap(treasury_cap); + + let channel = channel::new(ctx); + coin_management.add_distributor(@0x1); + + let token_id = self.register_coin(coin_info, coin_management); + self.burn_as_distributor(&channel, token_id, coin); + + sui::test_utils::destroy(self); + channel.destroy(); +} + +#[test] +#[expected_failure(abort_code = ENonZeroTotalSupply)] +fun test_give_unregistered_coin_not_zero_total_supply() { + let symbol = b"COIN"; + let decimals = 12; + let ctx = &mut tx_context::dummy(); + let mut self = create_for_testing(ctx); + + let ( + mut treasury_cap, + coin_metadata, + ) = its::coin::create_treasury_and_metadata(symbol, decimals, ctx); + let coin = treasury_cap.mint(1, ctx); + + self.give_unregistered_coin(treasury_cap, coin_metadata); + + sui::test_utils::destroy(self); + sui::test_utils::destroy(coin); +} + +#[test] +#[expected_failure(abort_code = EModuleNameDoesNotMatchSymbol)] +fun test_give_unregistered_coin_module_name_missmatch() { + let symbol = b"SYMBOL"; + let decimals = 12; + let ctx = &mut tx_context::dummy(); + let mut self = create_for_testing(ctx); + + let (treasury_cap, coin_metadata) = its::coin::create_treasury_and_metadata( + symbol, + decimals, + ctx, + ); + + self.give_unregistered_coin(treasury_cap, coin_metadata); + + sui::test_utils::destroy(self); +} + +#[test] +#[expected_failure(abort_code = ENotDistributor)] +fun test_mint_as_distributor_not_distributor() { + let ctx = &mut tx_context::dummy(); + let mut self = create_for_testing(ctx); + let symbol = b"COIN"; + let decimals = 9; + let remote_decimals = 18; + + let (treasury_cap, coin_metadata) = its::coin::create_treasury_and_metadata( + symbol, + decimals, + ctx, + ); + let coin_info = its::coin_info::from_metadata( + coin_metadata, + remote_decimals, + ); + let mut coin_management = its::coin_management::new_with_cap(treasury_cap); + + let channel = channel::new(ctx); + coin_management.add_distributor(@0x1); + let amount = 1234; + + let token_id = self.register_coin(coin_info, coin_management); + let coin = self.mint_as_distributor( + &channel, + token_id, + amount, + ctx, + ); + + assert!(coin.value() == amount); + + sui::test_utils::destroy(self); + sui::test_utils::destroy(coin); + channel.destroy(); +} diff --git a/test/testdata/interface_its_events.json b/test/testdata/interface_its_events.json new file mode 100644 index 00000000..0b162a70 --- /dev/null +++ b/test/testdata/interface_its_events.json @@ -0,0 +1,4 @@ +{ + "structs": {}, + "publicFunctions": {} +} diff --git a/test/testdata/interface_its_its.json b/test/testdata/interface_its_its.json index 1b8aa60a..6405f574 100644 --- a/test/testdata/interface_its_its.json +++ b/test/testdata/interface_its_its.json @@ -32,7 +32,7 @@ "name": "deploy_remote_interchain_token", "visibility": "public", "params": { - "self#0#0": "&mut ITS", + "self#0#0": "&ITS", "token_id#0#0": "TokenId", "destination_chain#0#0": "String" },