diff --git a/Cargo.lock b/Cargo.lock index 6e7e326a9..1f5886adf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,7 +101,7 @@ dependencies = [ "cosmwasm-std", "cw-asset", "cw-multi-test", - "cw-orch 0.23.0", + "cw-orch", "cw-storage-plus 1.2.0", "semver", ] @@ -188,7 +188,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-orch 0.23.0", + "cw-orch", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw20", @@ -345,7 +345,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-orch 0.23.0", + "cw-orch", "cw-storage-plus 1.2.0", "cw20", ] @@ -402,7 +402,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-orch 0.23.0", + "cw-orch", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw20", @@ -420,7 +420,7 @@ dependencies = [ "cosmwasm-std", "cw-controllers", "cw-multi-test", - "cw-orch 0.23.0", + "cw-orch", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -440,7 +440,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-orch 0.23.0", + "cw-orch", "cw-storage-plus 1.2.0", "hex", "itertools 0.10.5", @@ -636,7 +636,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-orch 0.23.0", + "cw-orch", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", ] @@ -724,7 +724,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-orch 0.24.1", + "cw-orch", "cw-orch-daemon", "cw20", "cw721 0.18.0", @@ -803,7 +803,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-orch 0.23.0", + "cw-orch", "cw-storage-plus 1.2.0", "serde", ] @@ -1515,27 +1515,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cw-orch" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97c76d9dd1c2632359f964e3531e5d0833d9022ae9fb216ce9aaaf36070d8bdf" -dependencies = [ - "anyhow", - "cosmwasm-std", - "cw-orch-contract-derive", - "cw-orch-core", - "cw-orch-fns-derive 0.21.1", - "cw-orch-mock 0.22.4", - "cw-orch-traits 0.22.0", - "cw-utils 1.0.3", - "hex", - "log", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "cw-orch" version = "0.24.1" @@ -1546,9 +1525,9 @@ dependencies = [ "cosmwasm-std", "cw-orch-contract-derive", "cw-orch-core", - "cw-orch-fns-derive 0.22.0", - "cw-orch-mock 0.23.2", - "cw-orch-traits 0.23.3", + "cw-orch-fns-derive", + "cw-orch-mock", + "cw-orch-traits", "cw-utils 1.0.3", "hex", "log", @@ -1604,7 +1583,7 @@ dependencies = [ "cosmwasm-std", "cw-orch-core", "cw-orch-networks", - "cw-orch-traits 0.23.3", + "cw-orch-traits", "dirs", "ed25519-dalek", "eyre", @@ -1631,18 +1610,6 @@ dependencies = [ "uid", ] -[[package]] -name = "cw-orch-fns-derive" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e85c3dea0893dd742c33ede8b4becdfb19b458e86e006ecf9a09ed66331d0c85" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "cw-orch-fns-derive" version = "0.22.0" @@ -1657,29 +1624,30 @@ dependencies = [ [[package]] name = "cw-orch-interchain" -version = "0.1.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fcf7defbb524e4db4fafd920732de1a7a9ef467b28116223b1e153971e5955" +checksum = "ba25ad0bf28edb98a8eb00a4bb1a922f5e9555b52512c4017cae36a676d671ed" dependencies = [ "cosmwasm-std", "cw-orch-interchain-core", "cw-orch-interchain-mock", "cw1", "cw1-whitelist", + "ibc-relayer-types", "speculoos", ] [[package]] name = "cw-orch-interchain-core" -version = "0.2.2" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ac27dd32926b7f529ed0905e7d5c610126d5eddd8381afd5918831b21c900d" +checksum = "cbe966c1c30f655f704ab201b15219e4e5c01592465bbda8b39fb015e79873b2" dependencies = [ "base64 0.21.7", "cosmwasm-schema", "cosmwasm-std", "cw-orch-core", - "cw-orch-mock 0.22.4", + "cw-orch-mock", "futures", "ibc-relayer-types", "log", @@ -1693,16 +1661,16 @@ dependencies = [ [[package]] name = "cw-orch-interchain-mock" -version = "0.2.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2042d10d65038139d8c2f0a1010ae050b67a43da9b6ee4f4fc2936b2d998b36" +checksum = "94e03b82fb8ae2dd93f04fce878edeb688ba9ceaa7efc27d35f4213a8eadecfa" dependencies = [ "anyhow", "cosmrs 0.15.0", "cosmwasm-std", "cw-orch-core", "cw-orch-interchain-core", - "cw-orch-mock 0.22.4", + "cw-orch-mock", "cw-utils 1.0.3", "ibc-relayer-types", "log", @@ -1711,21 +1679,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cw-orch-mock" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae9536620b86ee78c2729fd8449538feb4f6257a9809c72c5f9e461e720cf3b" -dependencies = [ - "abstract-cw-multi-test", - "cosmwasm-std", - "cw-orch-core", - "cw-utils 1.0.3", - "log", - "serde", - "sha2 0.10.8", -] - [[package]] name = "cw-orch-mock" version = "0.23.2" @@ -1751,17 +1704,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cw-orch-traits" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5959ce29e9d8a52594b47933a0a2736ea94dd9bf5e29b220cbdbe2b097f07c3a" -dependencies = [ - "cw-orch-core", - "prost 0.12.6", - "prost-types 0.12.6", -] - [[package]] name = "cw-orch-traits" version = "0.23.3" @@ -2881,7 +2823,7 @@ dependencies = [ "andromeda-validator-staking", "cosmrs 0.19.0", "cosmwasm-std", - "cw-orch 0.24.1", + "cw-orch", "cw-orch-daemon", "prost-types 0.13.2", "serde", @@ -4849,7 +4791,7 @@ dependencies = [ "cosmwasm-std", "cw-asset", "cw-multi-test", - "cw-orch 0.23.0", + "cw-orch", "cw-orch-interchain", "cw20", "cw721 0.18.0", diff --git a/Cargo.toml b/Cargo.toml index 98347e176..e0bf6f38b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,3 +57,4 @@ enum-repr = "0.2.6" cw-multi-test = { version = "1.0.0", features = ["cosmwasm_1_2"] } serde = { version = "1.0.127" } test-case = { version = "3.3.1" } +cw-orch = "=0.24.1" diff --git a/contracts/data-storage/andromeda-counter/Cargo.toml b/contracts/data-storage/andromeda-counter/Cargo.toml index 92118d1a3..a00cfd848 100644 --- a/contracts/data-storage/andromeda-counter/Cargo.toml +++ b/contracts/data-storage/andromeda-counter/Cargo.toml @@ -29,7 +29,7 @@ cosmwasm-schema = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } cw20 = { workspace = true } -cw-orch = "=0.23.0" +cw-orch = { workspace = true } andromeda-std = { workspace = true, features = ["rates"] } andromeda-data-storage = { workspace = true } diff --git a/contracts/finance/andromeda-splitter/Cargo.toml b/contracts/finance/andromeda-splitter/Cargo.toml index d78b9d3c5..703adedcb 100644 --- a/contracts/finance/andromeda-splitter/Cargo.toml +++ b/contracts/finance/andromeda-splitter/Cargo.toml @@ -20,7 +20,7 @@ cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } -cw-orch = "=0.23.0" +cw-orch = { workspace = true } andromeda-std = { workspace = true } andromeda-finance = { workspace = true } diff --git a/contracts/fungible-tokens/andromeda-ics20/Cargo.toml b/contracts/fungible-tokens/andromeda-ics20/Cargo.toml index 79b1c1b59..233042d21 100644 --- a/contracts/fungible-tokens/andromeda-ics20/Cargo.toml +++ b/contracts/fungible-tokens/andromeda-ics20/Cargo.toml @@ -28,7 +28,7 @@ cw-controllers = "=1.1.2" schemars = "0.8.21" serde = "1.0.203" thiserror = "1.0.61" -cw-orch = "=0.23.0" +cw-orch = { workspace = true } andromeda-std = { workspace = true, features = ["rates"]} andromeda-fungible-tokens = { workspace = true } diff --git a/contracts/os/andromeda-adodb/Cargo.toml b/contracts/os/andromeda-adodb/Cargo.toml index 9758c4f92..deda3bfc6 100644 --- a/contracts/os/andromeda-adodb/Cargo.toml +++ b/contracts/os/andromeda-adodb/Cargo.toml @@ -28,7 +28,7 @@ cw-storage-plus = { workspace = true } andromeda-std = { workspace = true } semver = { workspace = true } cw-asset = { workspace = true } -cw-orch = "=0.23.0" +cw-orch = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] cw-multi-test = { workspace = true, optional = true } diff --git a/contracts/os/andromeda-economics/Cargo.toml b/contracts/os/andromeda-economics/Cargo.toml index ee6d57db2..9a37da3ad 100644 --- a/contracts/os/andromeda-economics/Cargo.toml +++ b/contracts/os/andromeda-economics/Cargo.toml @@ -31,7 +31,7 @@ andromeda-std = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] cw-multi-test = { workspace = true, optional = true } -cw-orch = "=0.23.0" +cw-orch = { workspace = true } [dev-dependencies] # andromeda-testing = { workspace = true, optional = true } diff --git a/contracts/os/andromeda-ibc-registry/Cargo.toml b/contracts/os/andromeda-ibc-registry/Cargo.toml index 87499f7f6..eab40d060 100644 --- a/contracts/os/andromeda-ibc-registry/Cargo.toml +++ b/contracts/os/andromeda-ibc-registry/Cargo.toml @@ -33,6 +33,6 @@ cw20 = { workspace = true } andromeda-std = { workspace = true, features = ["rates"] } andromeda-data-storage = { workspace = true } -cw-orch = "=0.23.0" +cw-orch = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] cw-multi-test = { workspace = true, optional = true } diff --git a/contracts/os/andromeda-kernel/Cargo.toml b/contracts/os/andromeda-kernel/Cargo.toml index a2701eaf6..0f5ac2982 100644 --- a/contracts/os/andromeda-kernel/Cargo.toml +++ b/contracts/os/andromeda-kernel/Cargo.toml @@ -45,7 +45,7 @@ andromeda-std = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] cw-multi-test = { workspace = true, optional = true } -cw-orch = "=0.23.0" +cw-orch = { workspace = true } [dev-dependencies] # andromeda-testing = { workspace = true, optional = true } diff --git a/contracts/os/andromeda-kernel/src/contract.rs b/contracts/os/andromeda-kernel/src/contract.rs index e6977845c..7102d0164 100644 --- a/contracts/os/andromeda-kernel/src/contract.rs +++ b/contracts/os/andromeda-kernel/src/contract.rs @@ -11,7 +11,10 @@ use cosmwasm_std::{ }; use crate::ibc::{IBCLifecycleComplete, SudoMsg}; -use crate::reply::{on_reply_create_ado, on_reply_ibc_hooks_packet_send, on_reply_ibc_transfer}; +use crate::reply::{ + on_reply_create_ado, on_reply_ibc_hooks_packet_send, on_reply_ibc_transfer, + on_reply_refund_ibc_transfer_with_msg, +}; use crate::state::CURR_CHAIN; use crate::{execute, query, sudo}; @@ -44,13 +47,24 @@ pub fn instantiate( } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { +pub fn reply(mut deps: DepsMut, env: Env, msg: Reply) -> Result { if msg.result.is_err() { - return Err(ContractError::Std(StdError::generic_err(format!( - "{}:{}", - msg.id, - msg.result.unwrap_err() - )))); + match ReplyId::from_repr(msg.id) { + Some(ReplyId::IBCTransferWithMsg) => { + return on_reply_refund_ibc_transfer_with_msg( + deps.branch(), + env.clone(), + msg.clone(), + ); + } + _ => { + return Err(ContractError::Std(StdError::generic_err(format!( + "{}:{}", + msg.id, + msg.result.unwrap_err() + )))) + } + } } match ReplyId::from_repr(msg.id) { @@ -83,9 +97,10 @@ pub fn execute( packet, ), ExecuteMsg::Send { message } => execute::send(execute_env, message), - ExecuteMsg::TriggerRelay { packet_sequence } => { - execute::trigger_relay(execute_env, packet_sequence) - } + ExecuteMsg::TriggerRelay { + packet_sequence, + packet_ack_msg, + } => execute::trigger_relay(execute_env, packet_sequence, packet_ack_msg), ExecuteMsg::UpsertKeyAddress { key, value } => { execute::upsert_key_address(execute_env, key, value) } diff --git a/contracts/os/andromeda-kernel/src/execute.rs b/contracts/os/andromeda-kernel/src/execute.rs index c29fb01df..0c3576dca 100644 --- a/contracts/os/andromeda-kernel/src/execute.rs +++ b/contracts/os/andromeda-kernel/src/execute.rs @@ -1,6 +1,7 @@ +use crate::ibc::{get_counterparty_denom, PACKET_LIFETIME}; use andromeda_std::ado_contract::ADOContract; use andromeda_std::amp::addresses::AndrAddr; -use andromeda_std::amp::messages::{AMPCtx, AMPMsg, AMPPkt, IBCConfig}; +use andromeda_std::amp::messages::{AMPCtx, AMPMsg, AMPPkt}; use andromeda_std::amp::{ADO_DB_KEY, VFS_KEY}; use andromeda_std::common::context::ExecuteContext; use andromeda_std::common::has_coins_merged; @@ -8,20 +9,21 @@ use andromeda_std::common::reply::ReplyId; use andromeda_std::error::ContractError; use andromeda_std::os::aos_querier::AOSQuerier; use andromeda_std::os::ibc_registry::path_to_hops; -use andromeda_std::os::kernel::{ChannelInfo, IbcExecuteMsg, Ics20PacketInfo, InternalMsg}; - -use crate::ibc::{generate_ibc_hook_transfer_message, get_counterparty_denom, PACKET_LIFETIME}; +use andromeda_std::os::kernel::{ + AcknowledgementMsg, ChannelInfo, IbcExecuteMsg, Ics20PacketInfo, InternalMsg, + SendMessageWithFundsResponse, +}; use andromeda_std::os::vfs::vfs_resolve_symlink; use cosmwasm_std::{ - attr, ensure, to_json_binary, Addr, BankMsg, Binary, Coin, ContractInfoResponse, CosmosMsg, - DepsMut, Env, IbcMsg, MessageInfo, Response, StdError, SubMsg, WasmMsg, + attr, ensure, from_json, to_json_binary, BankMsg, Binary, Coin, ContractInfoResponse, + CosmosMsg, DepsMut, Env, IbcMsg, IbcPacketAckMsg, MessageInfo, Response, StdError, SubMsg, + WasmMsg, }; use crate::query; use crate::state::{ - IBCHooksPacketSendState, ADO_OWNER, CHAIN_TO_CHANNEL, CHANNEL_TO_CHAIN, CHANNEL_TO_EXECUTE_MSG, - CURR_CHAIN, IBC_FUND_RECOVERY, KERNEL_ADDRESSES, OUTGOING_IBC_HOOKS_PACKETS, - PENDING_MSG_AND_FUNDS, TRIGGER_KEY, + ADO_OWNER, CHAIN_TO_CHANNEL, CHANNEL_TO_CHAIN, CHANNEL_TO_EXECUTE_MSG, CURR_CHAIN, + IBC_FUND_RECOVERY, KERNEL_ADDRESSES, PENDING_MSG_AND_FUNDS, TRIGGER_KEY, }; pub fn send(ctx: ExecuteContext, message: AMPMsg) -> Result { @@ -36,13 +38,13 @@ pub fn send(ctx: ExecuteContext, message: AMPMsg) -> Result Result { //TODO Only the authorized address to handle replies can call this function ensure!( ctx.info.sender == KERNEL_ADDRESSES.load(ctx.deps.storage, TRIGGER_KEY)?, ContractError::Unauthorized {} ); - let ics20_packet_info = CHANNEL_TO_EXECUTE_MSG.load(ctx.deps.storage, packet_sequence.clone())?; @@ -59,16 +61,32 @@ pub fn trigger_relay( .ok_or_else(|| ContractError::InvalidPacket { error: Some(format!("Channel not found for chain {}", chain)), })?; - - handle_ibc_transfer_funds_reply( - ctx.deps, - ctx.info, - ctx.env, - ctx.amp_ctx, - 0, - channel_info, - ics20_packet_info, - ) + let ack: AcknowledgementMsg = + from_json(packet_ack_msg.acknowledgement.data)?; + + match ack { + AcknowledgementMsg::Ok(_) => handle_ibc_transfer_funds_reply( + ctx.deps, + ctx.info, + ctx.env, + ctx.amp_ctx, + 0, + channel_info, + ics20_packet_info, + ), + // This means that the funds have been returned to the contract, time to return the funds to the original sender + AcknowledgementMsg::Error(_) => { + let refund_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: ics20_packet_info.sender.clone(), + amount: vec![ics20_packet_info.funds.clone()], + }); + Ok(Response::default() + .add_message(refund_msg) + .add_attribute("action", "refund") + .add_attribute("recipient", ics20_packet_info.sender) + .add_attribute("amount", ics20_packet_info.funds.to_string())) + } + } } fn handle_ibc_transfer_funds_reply( @@ -80,12 +98,6 @@ fn handle_ibc_transfer_funds_reply( channel_info: ChannelInfo, ics20_packet_info: Ics20PacketInfo, ) -> Result { - ensure!( - !Binary::default().eq(&ics20_packet_info.message), - ContractError::InvalidPacket { - error: Some("The transfer funds reply must contain a message".to_string()) - } - ); let ics20_packet_info = ics20_packet_info.clone(); let chain = ics20_packet_info @@ -136,6 +148,7 @@ fn handle_ibc_transfer_funds_reply( recipient: AndrAddr::from_string(ics20_packet_info.recipient.clone().get_raw_path()), message: ics20_packet_info.message.clone(), funds: adjusted_funds, + original_sender: ics20_packet_info.sender, }; let msg = IbcMsg::SendPacket { channel_id: channel.clone(), @@ -629,7 +642,6 @@ impl MsgHandler { }); }?; if !self.message().funds.is_empty() { - // self.handle_ibc_hooks(deps, info, env, ctx, sequence, channel_info) self.handle_ibc_transfer_funds(deps, info, env, ctx, sequence, channel_info) } else { self.handle_ibc_direct(deps, info, env, ctx, sequence, channel_info) @@ -717,16 +729,11 @@ impl MsgHandler { ); let coin = funds .first() - .map_or_else( - || { - Err(ContractError::InvalidPacket { - error: Some("Transfer funds must contain funds in the AMPMsg".to_string()), - }) - }, - Ok, - )? + .ok_or(ContractError::InvalidPacket { + error: Some("Transfer funds must contain funds in the AMPMsg".to_string()), + })? .clone(); - // let recipient_raw_path = recipient.get_raw_path().to_string(); + let msg = IbcMsg::Transfer { channel_id: channel.clone(), to_address: channel_info.kernel_address.clone(), @@ -736,27 +743,22 @@ impl MsgHandler { let mut resp = Response::default(); // Save execute msg, to be loaded in the reply - if !Binary::default().eq(message) { - PENDING_MSG_AND_FUNDS.save( - deps.storage, - &Ics20PacketInfo { - sender: info.sender.into_string(), - recipient: recipient.clone(), - message: message.clone(), - funds: coin, - channel: channel.clone(), - }, - )?; - resp = resp.add_submessage(SubMsg { - id: ReplyId::IBCTransfer.repr(), - msg: CosmosMsg::Ibc(msg), - gas_limit: None, - reply_on: cosmwasm_std::ReplyOn::Always, - }); - } else { - // If there's no execute msg attached, we currently don't want to handle that in the reply - resp = resp.add_message(msg); - }; + PENDING_MSG_AND_FUNDS.save( + deps.storage, + &Ics20PacketInfo { + sender: info.sender.into_string(), + recipient: recipient.clone(), + message: message.clone(), + funds: coin, + channel: channel.clone(), + }, + )?; + resp = resp.add_submessage(SubMsg { + id: ReplyId::IBCTransfer.repr(), + msg: CosmosMsg::Ibc(msg), + gas_limit: None, + reply_on: cosmwasm_std::ReplyOn::Always, + }); Ok(resp .add_attribute(format!("method:{sequence}"), "execute_transfer_funds") @@ -764,76 +766,4 @@ impl MsgHandler { .add_attribute("receiving_kernel_address:{}", channel_info.kernel_address) .add_attribute("chain:{}", chain)) } - - fn _handle_ibc_hooks( - &self, - deps: DepsMut, - info: MessageInfo, - env: Env, - ctx: Option, - sequence: u64, - channel_info: ChannelInfo, - ) -> Result { - let AMPMsg { - recipient, - message, - funds, - config, - .. - } = self.message(); - let chain = recipient.get_chain().unwrap(); - let channel = if let Some(ics20_channel) = channel_info.ics20_channel_id { - Ok::(ics20_channel) - } else { - return Err(ContractError::InvalidPacket { - error: Some(format!("Channel not found for chain {chain}")), - }); - }?; - let msg_funds = &funds[0].clone(); - let recovery_addr = if let Some(IBCConfig { - recovery_addr: Some(recovery_addr), - }) = config.ibc_config.clone() - { - let addr = recovery_addr.get_raw_address(&deps.as_ref())?; - Ok::(addr) - } else if let Some(AMPPkt { ctx, .. }) = ctx { - Ok::(deps.api.addr_validate(&ctx.get_origin())?) - } else { - Ok::(info.sender) - }?; - let outgoing_state = IBCHooksPacketSendState { - channel_id: channel.clone(), - amount: msg_funds.clone(), - recovery_addr, - }; - - let mut outgoing_packets = OUTGOING_IBC_HOOKS_PACKETS - .load(deps.storage) - .unwrap_or_default(); - outgoing_packets.push(outgoing_state); - OUTGOING_IBC_HOOKS_PACKETS.save(deps.storage, &outgoing_packets)?; - - let msg = generate_ibc_hook_transfer_message( - &deps.as_ref(), - recipient, - message, - msg_funds, - &channel, - env.contract.address.as_str(), - &channel_info.kernel_address, - env.block.time, - )?; - Ok(Response::default() - .add_submessage(SubMsg::reply_always( - msg, - ReplyId::IBCHooksPacketSend.repr(), - )) - .add_attribute(format!("method:{sequence}"), "execute_send_message") - .add_attribute(format!("channel:{sequence}"), channel) - .add_attribute( - format!("receiving_kernel_address:{sequence}"), - channel_info.kernel_address, - ) - .add_attribute(format!("chain:{sequence}"), chain)) - } } diff --git a/contracts/os/andromeda-kernel/src/ibc.rs b/contracts/os/andromeda-kernel/src/ibc.rs index 3f0d5695c..5865a758b 100644 --- a/contracts/os/andromeda-kernel/src/ibc.rs +++ b/contracts/os/andromeda-kernel/src/ibc.rs @@ -1,13 +1,14 @@ use crate::ack::{make_ack_fail, make_ack_success}; use crate::execute; use crate::proto::MsgTransfer; -use crate::state::{CHANNEL_TO_CHAIN, KERNEL_ADDRESSES}; +use crate::state::{CHANNEL_TO_CHAIN, KERNEL_ADDRESSES, REFUND_DATA}; use andromeda_std::amp::{IBC_REGISTRY_KEY, VFS_KEY}; use andromeda_std::common::context::ExecuteContext; use andromeda_std::common::reply::ReplyId; use andromeda_std::error::{ContractError, Never}; use andromeda_std::os::aos_querier::AOSQuerier; use andromeda_std::os::ibc_registry::DenomInfo; +use andromeda_std::os::kernel::RefundData; use andromeda_std::os::{IBC_VERSION, TRANSFER_PORT}; use andromeda_std::{ amp::{messages::AMPMsg, AndrAddr}, @@ -120,9 +121,8 @@ pub fn ibc_packet_ack( ) -> Result { Ok(IbcBasicResponse::new()) } - pub fn do_ibc_packet_receive( - deps: DepsMut, + mut deps: DepsMut, env: Env, msg: IbcPacketReceiveMsg, ) -> Result { @@ -134,7 +134,7 @@ pub fn do_ibc_packet_receive( let packet_msg: IbcExecuteMsg = from_json(&msg.packet.data)?; let mut execute_env = ExecuteContext { env: env.clone(), - deps, + deps: deps.branch(), info: MessageInfo { funds: vec![], sender: Addr::unchecked("foreign_kernel"), @@ -156,19 +156,32 @@ pub fn do_ibc_packet_receive( recipient, message, funds, + original_sender, } => { let amp_msg = AMPMsg::new(recipient, message.clone(), Some(vec![funds.clone()])); execute_env.info = MessageInfo { - funds: vec![funds], + funds: vec![funds.clone()], sender: Addr::unchecked("foreign_kernel"), }; let res = execute::send(execute_env, amp_msg)?; + // Save refund info + REFUND_DATA.save( + deps.storage, + &RefundData { + original_sender, + funds, + channel, + }, + )?; Ok(IbcReceiveResponse::new() .set_ack(make_ack_success()) .add_attributes(res.attributes) - .add_submessages(res.messages) + .add_submessage(SubMsg::reply_always( + res.messages.first().unwrap().msg.clone(), + ReplyId::IBCTransferWithMsg.repr(), + )) .add_events(res.events)) } IbcExecuteMsg::CreateADO { diff --git a/contracts/os/andromeda-kernel/src/interface.rs b/contracts/os/andromeda-kernel/src/interface.rs index 5e6c4e230..1b76af4dd 100644 --- a/contracts/os/andromeda-kernel/src/interface.rs +++ b/contracts/os/andromeda-kernel/src/interface.rs @@ -12,7 +12,6 @@ impl Uploadable for KernelContract { Box::new( ContractWrapper::new_with_empty(execute, instantiate, query) .with_reply(reply) - .with_reply(crate::contract::reply) .with_ibc( crate::ibc::ibc_channel_open, crate::ibc::ibc_channel_connect, diff --git a/contracts/os/andromeda-kernel/src/reply.rs b/contracts/os/andromeda-kernel/src/reply.rs index c2e042e3a..cb300e4eb 100644 --- a/contracts/os/andromeda-kernel/src/reply.rs +++ b/contracts/os/andromeda-kernel/src/reply.rs @@ -1,8 +1,9 @@ use crate::{ + ibc::PACKET_LIFETIME, proto::MsgTransferResponse, state::{ IBCHooksPacketSendState, OutgoingPacket, ADO_OWNER, CHANNEL_TO_EXECUTE_MSG, - OUTGOING_IBC_HOOKS_PACKETS, OUTGOING_IBC_PACKETS, PENDING_MSG_AND_FUNDS, + OUTGOING_IBC_HOOKS_PACKETS, OUTGOING_IBC_PACKETS, PENDING_MSG_AND_FUNDS, REFUND_DATA, }, }; use andromeda_std::{ @@ -13,7 +14,7 @@ use andromeda_std::{ os::aos_querier::AOSQuerier, }; use cosmwasm_std::{ - ensure, wasm_execute, Addr, CosmosMsg, DepsMut, Empty, Env, Reply, Response, SubMsg, + ensure, wasm_execute, Addr, CosmosMsg, DepsMut, Empty, Env, IbcMsg, Reply, Response, SubMsg, SubMsgResponse, SubMsgResult, }; @@ -39,7 +40,6 @@ pub fn on_reply_create_ado(deps: DepsMut, env: Env, msg: Reply) -> Result Result { + let refund_data = REFUND_DATA.load(deps.storage)?; + // Construct the refund message + let refund_msg = IbcMsg::Transfer { + channel_id: refund_data.channel, + to_address: refund_data.original_sender.clone(), + amount: refund_data.funds.clone(), + timeout: env.block.time.plus_seconds(PACKET_LIFETIME).into(), + }; + REFUND_DATA.remove(deps.storage); + Ok(Response::default() + .add_message(refund_msg) + .add_attributes(vec![ + ("action", "refund_ibc_transfer_with_msg"), + ("recipient", refund_data.original_sender.as_str()), + ("amount", refund_data.funds.to_string().as_str()), + ])) +} diff --git a/contracts/os/andromeda-kernel/src/state.rs b/contracts/os/andromeda-kernel/src/state.rs index a8b9a246c..1b32715d8 100644 --- a/contracts/os/andromeda-kernel/src/state.rs +++ b/contracts/os/andromeda-kernel/src/state.rs @@ -1,4 +1,4 @@ -use andromeda_std::os::kernel::{ChannelInfo, Ics20PacketInfo}; +use andromeda_std::os::kernel::{ChannelInfo, Ics20PacketInfo, RefundData}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Coin}; use cw_storage_plus::{Item, Map}; @@ -44,3 +44,6 @@ pub const PENDING_MSG_AND_FUNDS: Item = Item::new("pending_exec /// Used to store sequence/channel against an ExecuteMsg, to be sent after an ack of ICS20 pub const CHANNEL_TO_EXECUTE_MSG: Map = Map::new("channel_to_execute_msg"); + +/// Used to temporarily store the most recent ExecuteMsg with the corresponding Coin to be sent in a reply for ICS20 transfer +pub const REFUND_DATA: Item = Item::new("refund_data"); diff --git a/contracts/os/andromeda-vfs/Cargo.toml b/contracts/os/andromeda-vfs/Cargo.toml index 778345119..5e95e7a91 100644 --- a/contracts/os/andromeda-vfs/Cargo.toml +++ b/contracts/os/andromeda-vfs/Cargo.toml @@ -28,7 +28,7 @@ cw-storage-plus = { workspace = true } serde = { version = "1.0.127", default-features = false, features = ["derive"] } andromeda-std = { workspace = true } -cw-orch = "=0.23.0" +cw-orch = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] cw-multi-test = { workspace = true, optional = true } diff --git a/ibc-tests/Cargo.toml b/ibc-tests/Cargo.toml index 8c0852cf6..55e874a6b 100644 --- a/ibc-tests/Cargo.toml +++ b/ibc-tests/Cargo.toml @@ -11,7 +11,7 @@ name = "validator_staking" [dependencies] serde.workspace = true tokio = "1.39.3" -cw-orch = "0.24.1" +cw-orch = { workspace = true } cw-orch-daemon = "0.24.2" andromeda-testing-e2e = { workspace = true } diff --git a/packages/andromeda-testing-e2e/Cargo.toml b/packages/andromeda-testing-e2e/Cargo.toml index 54fc56a62..44fd573dc 100644 --- a/packages/andromeda-testing-e2e/Cargo.toml +++ b/packages/andromeda-testing-e2e/Cargo.toml @@ -47,4 +47,4 @@ anyhow = "1.0.79" reqwest = { version = "0.12.5", features = ["json"] } tokio = "1.39.2" cw-orch-daemon = "0.24.3" -cw-orch = "0.24.1" +cw-orch = { workspace = true } diff --git a/packages/std/src/common/reply.rs b/packages/std/src/common/reply.rs index 079afb83d..5ff72d4bd 100644 --- a/packages/std/src/common/reply.rs +++ b/packages/std/src/common/reply.rs @@ -10,6 +10,7 @@ pub enum ReplyId { Recovery = 104, RegisterUsername = 105, IBCTransfer = 106, + IBCTransferWithMsg = 107, // App ClaimOwnership = 200, AssignApp = 201, diff --git a/packages/std/src/os/kernel.rs b/packages/std/src/os/kernel.rs index b5cf4e05a..87ac7f443 100644 --- a/packages/std/src/os/kernel.rs +++ b/packages/std/src/os/kernel.rs @@ -2,10 +2,12 @@ use crate::ado_base::ownership::OwnershipMessage; use crate::amp::messages::AMPMsg; use crate::amp::messages::AMPPkt; use crate::amp::AndrAddr; +use crate::error::ContractError; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Addr; use cosmwasm_std::Binary; use cosmwasm_std::Coin; +use cosmwasm_std::IbcPacketAckMsg; #[cw_serde] pub struct ChannelInfo { @@ -43,6 +45,7 @@ pub enum ExecuteMsg { }, TriggerRelay { packet_sequence: String, + packet_ack_msg: IbcPacketAckMsg, }, /// Upserts a key address to the kernel, restricted to the owner of the kernel UpsertKeyAddress { @@ -135,6 +138,7 @@ pub enum IbcExecuteMsg { recipient: AndrAddr, message: Binary, funds: Coin, + original_sender: String, }, CreateADO { instantiation_msg: Binary, @@ -157,3 +161,37 @@ pub struct Ics20PacketInfo { // The restricted wallet will probably already have access to this pub channel: String, } + +#[cw_serde] +pub struct RefundData { + pub original_sender: String, + pub funds: Coin, + pub channel: String, +} + +#[cw_serde] +pub struct SendMessageWithFundsResponse {} + +#[cw_serde] +pub enum AcknowledgementMsg { + Ok(S), + Error(String), +} + +impl AcknowledgementMsg { + pub fn unwrap(self) -> Result { + match self { + AcknowledgementMsg::Ok(data) => Ok(data), + AcknowledgementMsg::Error(err) => Err(ContractError::CustomError { msg: err }), + } + } + + pub fn unwrap_err(self) -> Result { + match self { + AcknowledgementMsg::Ok(_) => Err(ContractError::CustomError { + msg: "Not an error".to_string(), + }), + AcknowledgementMsg::Error(err) => Ok(err), + } + } +} diff --git a/tests-integration/Cargo.toml b/tests-integration/Cargo.toml index 60626a7b0..578b15dfc 100644 --- a/tests-integration/Cargo.toml +++ b/tests-integration/Cargo.toml @@ -130,8 +130,8 @@ cw721 = { workspace = true } cw20 = { workspace = true } cw-asset = { workspace = true } toml = "0.7" -cw-orch = "=0.23.0" -cw-orch-interchain = "=0.1.0" +cw-orch = { workspace = true } +cw-orch-interchain = "=0.3.0" ibc-relayer-types = "=0.25.1" andromeda-std = { workspace = true } diff --git a/tests-integration/tests/kernel_orch.rs b/tests-integration/tests/kernel_orch.rs index 1b73aa10f..6782b3799 100644 --- a/tests-integration/tests/kernel_orch.rs +++ b/tests-integration/tests/kernel_orch.rs @@ -9,6 +9,7 @@ use andromeda_economics::EconomicsContract; use andromeda_finance::splitter::{ AddressPercent, ExecuteMsg as SplitterExecuteMsg, InstantiateMsg as SplitterInstantiateMsg, }; + use andromeda_kernel::KernelContract; use andromeda_splitter::SplitterContract; use andromeda_std::{ @@ -16,13 +17,17 @@ use andromeda_std::{ messages::{AMPMsg, AMPMsgConfig}, AndrAddr, Recipient, }, + common::Milliseconds, os::{ self, - kernel::{ExecuteMsg, InstantiateMsg}, + kernel::{AcknowledgementMsg, ExecuteMsg, InstantiateMsg, SendMessageWithFundsResponse}, }, }; use andromeda_vfs::VFSContract; -use cosmwasm_std::{to_json_binary, Addr, Binary, Decimal, Uint128}; +use cosmwasm_std::{ + to_json_binary, Addr, Binary, Decimal, IbcAcknowledgement, IbcEndpoint, IbcPacket, + IbcPacketAckMsg, IbcTimeout, Timestamp, Uint128, +}; use cw_orch::prelude::*; use cw_orch_interchain::{prelude::*, types::IbcPacketOutcome, InterchainEnv}; use ibc_relayer_types::core::ics24_host::identifier::PortId; @@ -34,8 +39,8 @@ fn test_kernel_ibc_execute_only() { let interchain = MockInterchainEnv::new(vec![("juno", &sender), ("osmosis", &sender)]); - let juno = interchain.chain("juno").unwrap(); - let osmosis = interchain.chain("osmosis").unwrap(); + let juno = interchain.get_chain("juno").unwrap(); + let osmosis = interchain.get_chain("osmosis").unwrap(); juno.set_balance(sender.clone(), vec![Coin::new(100000000000000, "juno")]) .unwrap(); @@ -221,7 +226,7 @@ fn test_kernel_ibc_execute_only() { .unwrap(); let packet_lifetime = interchain - .wait_ibc("juno", kernel_juno_send_request) + .await_packets("juno", kernel_juno_send_request) .unwrap(); // For testing a successful outcome of the first packet sent out in the tx, you can use: @@ -251,8 +256,8 @@ fn test_kernel_ibc_funds_only() { ("cosmoshub", &sender), ]); - let juno = interchain.chain("juno").unwrap(); - let osmosis = interchain.chain("osmosis").unwrap(); + let juno = interchain.get_chain("juno").unwrap(); + let osmosis = interchain.get_chain("osmosis").unwrap(); juno.set_balance(sender.clone(), vec![Coin::new(100000000000000, "juno")]) .unwrap(); @@ -467,7 +472,7 @@ fn test_kernel_ibc_funds_only() { .unwrap(); let packet_lifetime = interchain - .wait_ibc("juno", kernel_juno_send_request) + .await_packets("juno", kernel_juno_send_request) .unwrap(); let ibc_denom = format!("ibc/{}/{}", channel.1.channel.unwrap().as_str(), "juno"); @@ -499,35 +504,56 @@ fn test_kernel_ibc_funds_only() { ) .unwrap(); - // TODO: This is failing - // // Construct an Execute msg from the kernel on juno inteded for the splitter on osmosis - // let kernel_juno_trigger_request = kernel_juno - // .execute( - // &ExecuteMsg::TriggerRelay { - // packet_sequence: "1".to_string(), - // }, - // None, - // ) - // .unwrap(); + // Construct an Execute msg from the kernel on juno inteded for the splitter on osmosis + let kernel_juno_trigger_request = kernel_juno + .execute( + &ExecuteMsg::TriggerRelay { + packet_sequence: "1".to_string(), + packet_ack_msg: IbcPacketAckMsg::new( + IbcAcknowledgement::new( + to_json_binary(&AcknowledgementMsg::::Ok( + SendMessageWithFundsResponse {}, + )) + .unwrap(), + ), + IbcPacket::new( + Binary::default(), + IbcEndpoint { + port_id: "port_id".to_string(), + channel_id: "channel_id".to_string(), + }, + IbcEndpoint { + port_id: "port_id".to_string(), + channel_id: "channel_id".to_string(), + }, + 1, + IbcTimeout::with_timestamp(Timestamp::from_seconds(1)), + ), + Addr::unchecked("relayer"), + ), + }, + None, + ) + .unwrap(); - // let packet_lifetime = interchain - // .wait_ibc("juno", kernel_juno_trigger_request) - // .unwrap(); + let packet_lifetime = interchain + .await_packets("juno", kernel_juno_trigger_request) + .unwrap(); - // // For testing a successful outcome of the first packet sent out in the tx, you can use: - // if let IbcPacketOutcome::Success { .. } = &packet_lifetime.packets[0].outcome { - // // Packet has been successfully acknowledged and decoded, the transaction has gone through correctly + // For testing a successful outcome of the first packet sent out in the tx, you can use: + if let IbcPacketOutcome::Success { .. } = &packet_lifetime.packets[0].outcome { + // Packet has been successfully acknowledged and decoded, the transaction has gone through correctly - // // Check recipient balance after trigger execute msg - // let balances = osmosis.query_all_balances(recipient).unwrap(); - // assert_eq!(balances.len(), 1); - // assert_eq!(balances[0].denom, ibc_denom); - // assert_eq!(balances[0].amount.u128(), 100); - // } else { - // panic!("packet timed out"); - // // There was a decode error or the packet timed out - // // Else the packet timed-out, you may have a relayer error or something is wrong in your application - // }; + // Check recipient balance after trigger execute msg + let balances = osmosis.query_all_balances(recipient).unwrap(); + assert_eq!(balances.len(), 1); + assert_eq!(balances[0].denom, ibc_denom); + assert_eq!(balances[0].amount.u128(), 100); + } else { + panic!("packet timed out"); + // There was a decode error or the packet timed out + // Else the packet timed-out, you may have a relayer error or something is wrong in your application + }; } #[test] fn test_kernel_ibc_funds_and_execute_msg() { @@ -540,8 +566,8 @@ fn test_kernel_ibc_funds_and_execute_msg() { ("cosmoshub", &sender), ]); - let juno = interchain.chain("juno").unwrap(); - let osmosis = interchain.chain("osmosis").unwrap(); + let juno = interchain.get_chain("juno").unwrap(); + let osmosis = interchain.get_chain("osmosis").unwrap(); juno.set_balance(sender.clone(), vec![Coin::new(100000000000000, "juno")]) .unwrap(); @@ -817,7 +843,7 @@ fn test_kernel_ibc_funds_and_execute_msg() { .unwrap(); let packet_lifetime = interchain - .wait_ibc("juno", kernel_juno_send_request) + .await_packets("juno", kernel_juno_send_request) .unwrap(); // For testing a successful outcome of the first packet sent out in the tx, you can use: @@ -846,13 +872,37 @@ fn test_kernel_ibc_funds_and_execute_msg() { .execute( &ExecuteMsg::TriggerRelay { packet_sequence: "1".to_string(), + packet_ack_msg: IbcPacketAckMsg::new( + IbcAcknowledgement::new( + to_json_binary( + &AcknowledgementMsg::::Ok( + SendMessageWithFundsResponse {}, + ), + ) + .unwrap(), + ), + IbcPacket::new( + Binary::default(), + IbcEndpoint { + port_id: "port_id".to_string(), + channel_id: "channel_id".to_string(), + }, + IbcEndpoint { + port_id: "port_id".to_string(), + channel_id: "channel_id".to_string(), + }, + 1, + IbcTimeout::with_timestamp(Timestamp::from_seconds(1)), + ), + Addr::unchecked("relayer"), + ), }, None, ) .unwrap(); let packet_lifetime = interchain - .wait_ibc("juno", kernel_juno_splitter_request) + .await_packets("juno", kernel_juno_splitter_request) .unwrap(); // For testing a successful outcome of the first packet sent out in the tx, you can use: @@ -877,3 +927,657 @@ fn test_kernel_ibc_funds_and_execute_msg() { // Else the packet timed-out, you may have a relayer error or something is wrong in your application }; } + +// Unhappy paths // +#[test] +fn test_kernel_ibc_funds_only_unhappy() { + // Here `juno-1` is the chain-id and `juno` is the address prefix for this chain + let sender = Addr::unchecked("sender_for_all_chains").into_string(); + + let interchain = MockInterchainEnv::new(vec![("juno", &sender), ("osmosis", &sender)]); + + let juno = interchain.get_chain("juno").unwrap(); + let osmosis = interchain.get_chain("osmosis").unwrap(); + + juno.set_balance(sender.clone(), vec![Coin::new(100000000000000, "juno")]) + .unwrap(); + + let kernel_juno = KernelContract::new(juno.clone()); + let vfs_juno = VFSContract::new(juno.clone()); + let kernel_osmosis = KernelContract::new(osmosis.clone()); + let counter_osmosis = CounterContract::new(osmosis.clone()); + let vfs_osmosis = VFSContract::new(osmosis.clone()); + let adodb_osmosis = ADODBContract::new(osmosis.clone()); + let splitter_osmosis = SplitterContract::new(osmosis.clone()); + + kernel_juno.upload().unwrap(); + vfs_juno.upload().unwrap(); + kernel_osmosis.upload().unwrap(); + counter_osmosis.upload().unwrap(); + vfs_osmosis.upload().unwrap(); + adodb_osmosis.upload().unwrap(); + splitter_osmosis.upload().unwrap(); + + let init_msg_juno = &InstantiateMsg { + owner: None, + chain_name: "juno".to_string(), + }; + let init_msg_osmosis = &InstantiateMsg { + owner: None, + chain_name: "osmosis".to_string(), + }; + + kernel_juno.instantiate(init_msg_juno, None, None).unwrap(); + kernel_osmosis + .instantiate(init_msg_osmosis, None, None) + .unwrap(); + + // Set up channel from juno to osmosis + let channel_receipt = interchain + .create_contract_channel(&kernel_juno, &kernel_osmosis, "andr-kernel-1", None) + .unwrap(); + + // After channel creation is complete, we get the channel id, which is necessary for ICA remote execution + let juno_channel = channel_receipt + .interchain_channel + .get_chain("juno") + .unwrap() + .channel + .unwrap(); + + // Set up channel from juno to osmosis for ICS20 transfers + let channel_receipt = interchain + .create_channel( + "juno", + "osmosis", + &PortId::transfer(), + &PortId::transfer(), + "ics20-1", + None, + ) + .unwrap(); + + let channel = channel_receipt + .interchain_channel + .get_ordered_ports_from("juno") + .unwrap(); + + // After channel creation is complete, we get the channel id, which is necessary for ICA remote execution + let _juno_channel_ics20 = channel_receipt + .interchain_channel + .get_chain("juno") + .unwrap() + .channel + .unwrap(); + + vfs_juno + .instantiate( + &os::vfs::InstantiateMsg { + kernel_address: kernel_juno.address().unwrap().into_string(), + owner: None, + }, + None, + None, + ) + .unwrap(); + + vfs_osmosis + .instantiate( + &os::vfs::InstantiateMsg { + kernel_address: kernel_osmosis.address().unwrap().into_string(), + owner: None, + }, + None, + None, + ) + .unwrap(); + + adodb_osmosis + .instantiate( + &os::adodb::InstantiateMsg { + kernel_address: kernel_osmosis.address().unwrap().into_string(), + owner: None, + }, + None, + None, + ) + .unwrap(); + + adodb_osmosis + .execute( + &os::adodb::ExecuteMsg::Publish { + code_id: 2, + ado_type: "counter".to_string(), + action_fees: None, + version: "1.0.2".to_string(), + publisher: None, + }, + None, + ) + .unwrap(); + + kernel_juno + .execute( + &ExecuteMsg::UpsertKeyAddress { + key: "vfs".to_string(), + value: vfs_juno.address().unwrap().into_string(), + }, + None, + ) + .unwrap(); + + kernel_osmosis + .execute( + &ExecuteMsg::UpsertKeyAddress { + key: "vfs".to_string(), + value: vfs_osmosis.address().unwrap().into_string(), + }, + None, + ) + .unwrap(); + + kernel_osmosis + .execute( + &ExecuteMsg::UpsertKeyAddress { + key: "adodb".to_string(), + value: adodb_osmosis.address().unwrap().into_string(), + }, + None, + ) + .unwrap(); + + kernel_juno + .execute( + &ExecuteMsg::AssignChannels { + ics20_channel_id: Some(channel.clone().0.channel.unwrap().to_string()), + direct_channel_id: Some(juno_channel.to_string()), + chain: "osmosis".to_string(), + kernel_address: kernel_osmosis.address().unwrap().into_string(), + }, + None, + ) + .unwrap(); + + kernel_osmosis + .execute( + &ExecuteMsg::AssignChannels { + ics20_channel_id: Some(channel.0.channel.unwrap().to_string()), + direct_channel_id: Some(juno_channel.to_string()), + chain: "juno".to_string(), + kernel_address: kernel_juno.address().unwrap().into_string(), + }, + None, + ) + .unwrap(); + let balances = juno.query_all_balances(sender.clone()).unwrap(); + assert_eq!(balances.len(), 1); + assert_eq!(balances[0].denom, "juno"); + println!("sender balance before transfer: {}", balances[0].amount); + + let kernel_juno_send_request = kernel_juno + .execute( + &ExecuteMsg::Send { + message: AMPMsg { + recipient: AndrAddr::from_string(format!( + "ibc://osmosis/{}", + kernel_osmosis.address().unwrap() + )), + message: Binary::default(), + funds: vec![Coin { + denom: "juno".to_string(), + amount: Uint128::new(100), + }], + config: AMPMsgConfig { + reply_on: cosmwasm_std::ReplyOn::Always, + exit_at_error: false, + gas_limit: None, + direct: true, + ibc_config: None, + }, + }, + }, + Some(&[Coin { + denom: "juno".to_string(), + amount: Uint128::new(100), + }]), + ) + .unwrap(); + + osmosis.wait_seconds(604_810).unwrap(); + + let packet_lifetime = interchain + .await_packets("juno", kernel_juno_send_request) + .unwrap(); + + // For testing a successful outcome of the first packet sent out in the tx, you can use: + if let IbcPacketOutcome::Success { .. } = &packet_lifetime.packets[0].outcome { + // Packet has been successfully acknowledged and decoded, the transaction has gone through correctly + } else { + // Register trigger address + kernel_juno + .execute( + &ExecuteMsg::UpsertKeyAddress { + key: "trigger_key".to_string(), + value: sender.clone(), + }, + None, + ) + .unwrap(); + let kernel_juno_splitter_request = kernel_juno + .execute( + &ExecuteMsg::TriggerRelay { + packet_sequence: "1".to_string(), + packet_ack_msg: IbcPacketAckMsg::new( + IbcAcknowledgement::new( + to_json_binary( + &AcknowledgementMsg::::Error( + "error".to_string(), + ), + ) + .unwrap(), + ), + IbcPacket::new( + Binary::default(), + IbcEndpoint { + port_id: "port_id".to_string(), + channel_id: "channel_id".to_string(), + }, + IbcEndpoint { + port_id: "port_id".to_string(), + channel_id: "channel_id".to_string(), + }, + 1, + IbcTimeout::with_timestamp(Timestamp::from_seconds(1)), + ), + Addr::unchecked("relayer"), + ), + }, + None, + ) + .unwrap(); + let _packet_lifetime = interchain + .await_packets("juno", kernel_juno_splitter_request) + .unwrap(); + + let balances = juno.query_all_balances(sender).unwrap(); + assert_eq!(balances.len(), 1); + assert_eq!(balances[0].denom, "juno"); + // Original amount + assert_eq!(balances[0].amount, Uint128::new(100000000000000)); + + // Make sure kernel has no funds + let balances = juno + .query_all_balances(kernel_juno.address().unwrap()) + .unwrap(); + assert_eq!(balances.len(), 0); + // There was a decode error or the packet timed out + // Else the packet timed-out, you may have a relayer error or something is wrong in your application + }; +} + +#[test] +fn test_kernel_ibc_funds_and_execute_msg_unhappy() { + // Here `juno-1` is the chain-id and `juno` is the address prefix for this chain + let sender = Addr::unchecked("sender_for_all_chains").into_string(); + + let interchain = MockInterchainEnv::new(vec![("juno", &sender), ("osmosis", &sender)]); + + let juno = interchain.get_chain("juno").unwrap(); + let osmosis = interchain.get_chain("osmosis").unwrap(); + + juno.set_balance(sender.clone(), vec![Coin::new(100000000000000, "juno")]) + .unwrap(); + + let kernel_juno = KernelContract::new(juno.clone()); + let vfs_juno = VFSContract::new(juno.clone()); + let kernel_osmosis = KernelContract::new(osmosis.clone()); + let counter_osmosis = CounterContract::new(osmosis.clone()); + let vfs_osmosis = VFSContract::new(osmosis.clone()); + let economics_osmosis = EconomicsContract::new(osmosis.clone()); + let adodb_osmosis = ADODBContract::new(osmosis.clone()); + let splitter_osmosis = SplitterContract::new(osmosis.clone()); + + kernel_juno.upload().unwrap(); + vfs_juno.upload().unwrap(); + kernel_osmosis.upload().unwrap(); + counter_osmosis.upload().unwrap(); + vfs_osmosis.upload().unwrap(); + adodb_osmosis.upload().unwrap(); + splitter_osmosis.upload().unwrap(); + economics_osmosis.upload().unwrap(); + + let init_msg_juno = &InstantiateMsg { + owner: None, + chain_name: "juno".to_string(), + }; + let init_msg_osmosis = &InstantiateMsg { + owner: None, + chain_name: "osmosis".to_string(), + }; + + kernel_juno.instantiate(init_msg_juno, None, None).unwrap(); + kernel_osmosis + .instantiate(init_msg_osmosis, None, None) + .unwrap(); + + // Set up channel from juno to osmosis + let channel_receipt = interchain + .create_contract_channel(&kernel_juno, &kernel_osmosis, "andr-kernel-1", None) + .unwrap(); + + // After channel creation is complete, we get the channel id, which is necessary for ICA remote execution + let juno_channel = channel_receipt + .interchain_channel + .get_chain("juno") + .unwrap() + .channel + .unwrap(); + + // Set up channel from juno to osmosis for ICS20 transfers + let channel_receipt = interchain + .create_channel( + "juno", + "osmosis", + &PortId::transfer(), + &PortId::transfer(), + "ics20-1", + None, + ) + .unwrap(); + + let channel = channel_receipt + .interchain_channel + .get_ordered_ports_from("juno") + .unwrap(); + + // After channel creation is complete, we get the channel id, which is necessary for ICA remote execution + let _juno_channel_ics20 = channel_receipt + .interchain_channel + .get_chain("juno") + .unwrap() + .channel + .unwrap(); + + vfs_juno + .instantiate( + &os::vfs::InstantiateMsg { + kernel_address: kernel_juno.address().unwrap().into_string(), + owner: None, + }, + None, + None, + ) + .unwrap(); + + vfs_osmosis + .instantiate( + &os::vfs::InstantiateMsg { + kernel_address: kernel_osmosis.address().unwrap().into_string(), + owner: None, + }, + None, + None, + ) + .unwrap(); + + economics_osmosis + .instantiate( + &os::economics::InstantiateMsg { + kernel_address: kernel_osmosis.address().unwrap().into_string(), + owner: None, + }, + None, + None, + ) + .unwrap(); + + adodb_osmosis + .instantiate( + &os::adodb::InstantiateMsg { + kernel_address: kernel_osmosis.address().unwrap().into_string(), + owner: None, + }, + None, + None, + ) + .unwrap(); + + adodb_osmosis + .execute( + &os::adodb::ExecuteMsg::Publish { + code_id: splitter_osmosis.code_id().unwrap(), + ado_type: "splitter".to_string(), + action_fees: None, + version: "1.0.0".to_string(), + publisher: None, + }, + None, + ) + .unwrap(); + + adodb_osmosis + .execute( + &os::adodb::ExecuteMsg::Publish { + code_id: 2, + ado_type: "counter".to_string(), + action_fees: None, + version: "1.0.2".to_string(), + publisher: None, + }, + None, + ) + .unwrap(); + + kernel_juno + .execute( + &ExecuteMsg::UpsertKeyAddress { + key: "vfs".to_string(), + value: vfs_juno.address().unwrap().into_string(), + }, + None, + ) + .unwrap(); + + kernel_osmosis + .execute( + &ExecuteMsg::UpsertKeyAddress { + key: "vfs".to_string(), + value: vfs_osmosis.address().unwrap().into_string(), + }, + None, + ) + .unwrap(); + + kernel_osmosis + .execute( + &ExecuteMsg::UpsertKeyAddress { + key: "adodb".to_string(), + value: adodb_osmosis.address().unwrap().into_string(), + }, + None, + ) + .unwrap(); + + kernel_osmosis + .execute( + &ExecuteMsg::UpsertKeyAddress { + key: "economics".to_string(), + value: economics_osmosis.address().unwrap().into_string(), + }, + None, + ) + .unwrap(); + + kernel_juno + .execute( + &ExecuteMsg::AssignChannels { + ics20_channel_id: Some(channel.clone().0.channel.unwrap().to_string()), + direct_channel_id: Some(juno_channel.to_string()), + chain: "osmosis".to_string(), + kernel_address: kernel_osmosis.address().unwrap().into_string(), + }, + None, + ) + .unwrap(); + + kernel_osmosis + .execute( + &ExecuteMsg::AssignChannels { + ics20_channel_id: Some(channel.0.channel.unwrap().to_string()), + direct_channel_id: Some(juno_channel.to_string()), + chain: "juno".to_string(), + kernel_address: kernel_juno.address().unwrap().into_string(), + }, + None, + ) + .unwrap(); + + let recipient = "osmo1qzskhrca90qy2yjjxqzq4yajy842x7c50xq33d"; + + // This section covers the actions that take place after a successful ack from the ICS20 transfer is received + // Let's instantiate a splitter + splitter_osmosis + .instantiate( + &SplitterInstantiateMsg { + recipients: vec![AddressPercent { + recipient: Recipient { + address: AndrAddr::from_string(recipient), + msg: None, + ibc_recovery_address: None, + }, + percent: Decimal::one(), + }], + lock_time: None, + kernel_address: kernel_osmosis.address().unwrap().into_string(), + owner: None, + }, + None, + None, + ) + .unwrap(); + + let kernel_juno_send_request = kernel_juno + .execute( + &ExecuteMsg::Send { + message: AMPMsg { + recipient: AndrAddr::from_string(format!( + "ibc://osmosis/{}", + splitter_osmosis.address().unwrap() + )), + // Send invalid message to the splitter. It's invalid because we'll be attaching funds to it and the msg rejects funds + message: to_json_binary(&SplitterExecuteMsg::UpdateLock { + lock_time: andromeda_std::common::expiration::Expiry::AtTime( + Milliseconds::zero(), + ), + }) + .unwrap(), + funds: vec![Coin { + denom: "juno".to_string(), + amount: Uint128::new(100), + }], + config: AMPMsgConfig { + reply_on: cosmwasm_std::ReplyOn::Always, + exit_at_error: false, + gas_limit: None, + direct: true, + ibc_config: None, + }, + }, + }, + Some(&[Coin { + denom: "juno".to_string(), + amount: Uint128::new(100), + }]), + ) + .unwrap(); + + let packet_lifetime = interchain + .await_packets("juno", kernel_juno_send_request) + .unwrap(); + // Make sure the sender's balance decreased by 100 + let balances = juno.query_all_balances(sender.clone()).unwrap(); + assert_eq!(balances.len(), 1); + assert_eq!(balances[0].denom, "juno"); + // Original amount + assert_eq!(balances[0].amount, Uint128::new(100000000000000 - 100)); + + // For testing a successful outcome of the first packet sent out in the tx, you can use: + if let IbcPacketOutcome::Success { .. } = &packet_lifetime.packets[0].outcome { + // Register trigger address + kernel_juno + .execute( + &ExecuteMsg::UpsertKeyAddress { + key: "trigger_key".to_string(), + value: sender.clone(), + }, + None, + ) + .unwrap(); + + // Construct an Execute msg from the kernel on juno inteded for the splitter on osmosis + let kernel_juno_splitter_request = kernel_juno + .execute( + &ExecuteMsg::TriggerRelay { + packet_sequence: "1".to_string(), + packet_ack_msg: IbcPacketAckMsg::new( + IbcAcknowledgement::new( + to_json_binary( + // It's Ok because the ics20 transfer is supposed to go through. We want the ExecuteMsg to fail + &AcknowledgementMsg::::Ok( + SendMessageWithFundsResponse {}, + ), + ) + .unwrap(), + ), + IbcPacket::new( + Binary::default(), + IbcEndpoint { + port_id: "port_id".to_string(), + channel_id: "channel_id".to_string(), + }, + IbcEndpoint { + port_id: "port_id".to_string(), + channel_id: "channel_id".to_string(), + }, + 1, + IbcTimeout::with_timestamp(Timestamp::from_seconds(1)), + ), + Addr::unchecked("relayer"), + ), + }, + None, + ) + .unwrap(); + // We call UpadeLock, a Msg that doesn't accept funds. So it will error and should trigger a refund from the destination chain + interchain + .await_and_check_packets("juno", kernel_juno_splitter_request.clone()) + .unwrap(); + + // Make sure kernel has no funds + let balances = juno + .query_all_balances(kernel_juno.address().unwrap()) + .unwrap(); + assert_eq!(balances.len(), 0); + + let balances = juno.query_all_balances(sender).unwrap(); + assert_eq!(balances.len(), 1); + assert_eq!(balances[0].denom, "juno"); + // Original amount + assert_eq!(balances[0].amount, Uint128::new(100000000000000)); + + // // For testing a successful outcome of the first packet sent out in the tx, you can use: + // if let IbcPacketOutcome::Success { .. } = &packet_lifetime.packets[0].outcome { + // // Packet has been successfully acknowledged and decoded, the transaction has gone through correctly + // } else { + // panic!("packet timed out"); + // // There was a decode error or the packet timed out + // // Else the packet timed-out, you may have a relayer error or something is wrong in your application + // }; + + // Packet has been successfully acknowledged and decoded, the transaction has gone through correctly + } else { + panic!("packet timed out"); + // There was a decode error or the packet timed out + // Else the packet timed-out, you may have a relayer error or something is wrong in your application + }; +}