diff --git a/.gitignore b/.gitignore index 8cfc89f..ded8580 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,7 @@ libwasmvm_muslc.a tests/test-data.json **/artifacts/* !**/artifacts/*.wasm -.env \ No newline at end of file +.env + +.scannerwork/ +clippy-report.json \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index eec745a..bdcc0d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,9 +215,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.2.7" +version = "1.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb64554a91d6a9231127f4355d351130a0b94e663d5d9dc8b3a54ca17d83de49" +checksum = "aa891994102179dc76d3a38ae2bc8170bc84dc9ba3e27696014957ec0cafd417" dependencies = [ "digest 0.10.7", "ed25519-zebra", @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema" -version = "1.2.7" +version = "1.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "230e5d1cefae5331db8934763c81b9c871db6a2cd899056a5694fa71d292c815" +checksum = "e0864db0a1e0ba2abd35d1105d0487e779538f616daa0338697315cc1601bf24" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -250,9 +250,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.7" +version = "1.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43dadf7c23406cb28079d69e6cb922c9c29b9157b0fe887e3b79c783b7d4bcb8" +checksum = "2cd0f686ff907612ed63fdff97906b3eafe169d560ebd428e92ef400fb56479f" dependencies = [ "proc-macro2", "quote", @@ -261,9 +261,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.2.7" +version = "1.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4337eef8dfaf8572fe6b6b415d6ec25f9308c7bb09f2da63789209fb131363be" +checksum = "197ed654c41885e2d76b23bff8db11b0f9745bfdede0909de9daa564edb19187" dependencies = [ "base64", "cosmwasm-crypto", @@ -303,9 +303,9 @@ dependencies = [ [[package]] name = "cosmwasm-vm" -version = "1.2.7" +version = "1.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2bcad6253a153f448e323435d71093d9730512155bbee33dfae2b5547b94f17" +checksum = "613a3a96581b476b0b0a2fead0c970f9e8c844b7aa41bc45648ebcaa84d6b8ab" dependencies = [ "bitflags 1.3.2", "bytecheck", @@ -501,7 +501,6 @@ dependencies = [ "cosmwasm-testing-util", "cosmwasm-vm", "cw-controllers", - "cw-multi-test", "cw-storage-plus 1.1.0", "cw-utils 0.16.0", "cw2 1.1.0", @@ -514,9 +513,9 @@ dependencies = [ [[package]] name = "cw-multi-test" -version = "0.16.5" +version = "0.16.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "127c7bb95853b8e828bdab97065c81cb5ddc20f7339180b61b2300565aaa99d1" +checksum = "d3a9d62dba95ab262438dfd403bffdca647c6615ef1aa431cdc94c262b09643a" dependencies = [ "anyhow", "cosmwasm-std", @@ -651,7 +650,6 @@ dependencies = [ "cosmwasm-std", "cosmwasm-testing-util", "cosmwasm-vm", - "cw-multi-test", "cw-storage-plus 1.1.0", "cw20", "oraiswap", @@ -677,7 +675,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.68", ] [[package]] @@ -688,7 +686,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.37", + "syn 2.0.68", ] [[package]] @@ -855,7 +853,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.68", ] [[package]] @@ -1206,12 +1204,13 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "oraiswap" version = "0.2.0" -source = "git+https://github.com/oraichain/oraiswap.git?rev=03f4955b#03f4955b629baa8aa3b2d28ddc15dee886fd0836" +source = "git+https://github.com/oraichain/oraiswap.git?rev=29decac#29decac873cd2be2245dfc4e792dbc50ce26979c" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", + "cw-utils 0.16.0", "cw20", "cw20-base", "protobuf", @@ -1268,9 +1267,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -1341,9 +1340,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1564,9 +1563,9 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] @@ -1591,13 +1590,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.68", ] [[package]] @@ -1719,9 +1718,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.37" +version = "2.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" dependencies = [ "proc-macro2", "quote", @@ -1770,7 +1769,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.68", ] [[package]] @@ -1808,7 +1807,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.68", ] [[package]] @@ -1883,7 +1882,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.68", "wasm-bindgen-shared", ] @@ -1905,7 +1904,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.68", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index 67cf40f..57c25d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,17 @@ resolver = "2" [workspace.dependencies] cosmwasm-testing-util = { git = "https://github.com/oraichain/cosmwasm-testing-util.git", rev = "24c138c" } -oraiswap = { git = "https://github.com/oraichain/oraiswap.git", rev = "03f4955b" } +oraiswap = { git = "https://github.com/oraichain/oraiswap.git", rev = "29decac" } +cw2 = { version = "1.0.1" } +cw20 = { version = "1.0.1" } +cw20-base = { version = "1.0.1" } +cw-storage-plus = { version = "1.0.1" } +cw-controllers = { version = "1.0.1" } +cw-utils = "0.16.0" +cw20-ics20-msg = { path = "./packages/cw20-ics20-msg" } +cosmwasm-schema = { version = "1.2.8" } +cosmwasm-std = { version = "1.2.8", default-features = false } +cosmwasm-vm = { version = "1.2.8" } # osmosis-test-tube = { git = "https://github.com/oraichain/test-tube.git", rev = "354d580" } [profile.release] diff --git a/README.md b/README.md index 34c7486..56d2b0c 100755 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ Let's assume the network that has the contract cw20-ics20 deployed is network B, In the source code, we call the network having cw20-ics20 deployed local chain, other networks are remote chains. +As of now, we only support A bridging to B first, not the other way around. + In the cw-ics20-latest contract, there are couple transfer flows in the code below: ## Network A transfers native tokens to B first (A->B, where native token is not IBC token) @@ -14,20 +16,12 @@ Here, if any line returns an error, then the cw-ics20 contract will send a fail ## Network A transfers IBC tokens to B (A->B, where IBC tokens are tokens that were previously sent from B to A) -In this case, the packet is also caught in the `do_ibc_packet_receive`, but we parse the ibc denom to get the original denom instead, and send the same amount that is stored in the cw-ics20 contract to the receiver using SubMsg (with reply on error). This is the logic that the CosmWasm developers wrote. +Currently not supported. ## Network B transfers tokens to A (B->A, where tokens are either native or cw20 that originated from B) -In this case, the user will deposit his tokens into the cw-ics20 contract, then the contract will create a new IBCPacket and transfer it to the network A. The cw-ics20 contract then invokes the `ibc_packet_ack` function, which has the acknowledgement packet sent from A. If the ack is a failure, then we refund (using SubMsg with reply on error) the original sender by using the tokens stored on the cw-ics20 contract. This is also the logic that CosmWasm developers wrote. +Currently not supported. ## Network B transfers tokens to A (B->A, where tokens are native tokens from A) -In this case, the user will deposit tokens to the `allow_contract`, and the allow contract will call the `execute_transfer_back_to_remote_chain` function. Here, the cw-ics20 contract will not hold any tokens. Instead, it only forwards the logic to the chain A. The cw-ics20 contract then invokes the `ibc_packet_ack` function, which has the acknowledgement packet sent from A. If the ack is a failure, then we refund by calling a message to the `allow_contract` requesting for a refund. We use the `CosmosMsg` instead of the `SubMsg`. - -This is really important because by using the CosmosMsg, we force the `allow_contract` to actually refunds successfully. If we use SubMsg, then it could be that the `allow_contract` fails somewhere, and only its state gets reverted, aka the sender receives no refunds. - -If we use CosmosMsg, then the acknowledgement packet will fail entirely, and it will be retried by the relayer as long as we fix the `allow_contract`. - -Normally, if it is a `ibctransfer` application developed as a submodule in Cosmos SDK, then the refund part must not fail, and we can trust that it will not fail. However, the `allow_contract` can be developed by anyone, and can be replaced => cannot be trusted. - -# build packages \ No newline at end of file +In this case, the user will deposit tokens and invoke the function `execute_transfer_back_to_remote_chain` function. It will create an IBC Send packet and lock the cw20 or native token in the cw20-ics20 contract. If there's a failed acknowledgement, the contract will try to automatically refund the locked tokens back to the sender. If the refund msg is failed, then the token will be locked in the contract, and the team will refund these tokens manually for security purposes. diff --git a/contracts/cw-ics20-latest/Cargo.toml b/contracts/cw-ics20-latest/Cargo.toml index 0ec8715..9d52a94 100644 --- a/contracts/cw-ics20-latest/Cargo.toml +++ b/contracts/cw-ics20-latest/Cargo.toml @@ -18,21 +18,20 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cosmwasm-schema = "=1.2" -cw-utils = "0.16.0" -cw2 = "1.0.1" -cw20 = "1.0.1" -cw20-ics20-msg = { path = "../../packages/cw20-ics20-msg" } +cosmwasm-schema = { workspace = true } +cw-utils = { workspace = true } +cw2 = { workspace = true } +cw20 = { workspace = true } +cw20-ics20-msg = { workspace = true } oraiswap = { workspace = true } -cosmwasm-std = { version = "=1.2", features = ["ibc3"] } -cw-storage-plus = "1.0.1" -cw-controllers = "1.0.1" +cosmwasm-std = { workspace = true, features = ["ibc3"] } +cw-storage-plus = { workspace = true } +cw-controllers = { workspace = true } thiserror = { version = "1.0.23" } sha256 = "=1.1.0" [dev-dependencies] -cosmwasm-vm = { version = "=1.2" } +cosmwasm-vm = { workspace = true } # osmosis-test-tube = { workspace = true } cosmwasm-testing-util = { workspace = true } anybuf = "0.3.0" -cw-multi-test = "0.16.0" diff --git a/contracts/cw-ics20-latest/src/contract.rs b/contracts/cw-ics20-latest/src/contract.rs index 717df75..881a2ea 100644 --- a/contracts/cw-ics20-latest/src/contract.rs +++ b/contracts/cw-ics20-latest/src/contract.rs @@ -6,7 +6,7 @@ use cosmwasm_std::{ WasmMsg, }; use cw2::set_contract_version; -use cw20::{Cw20Coin, Cw20ExecuteMsg, Cw20ReceiveMsg}; +use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; use cw20_ics20_msg::converter::ConverterController; use cw20_ics20_msg::helper::parse_ibc_wasm_port_id; use cw_storage_plus::Bound; @@ -239,7 +239,7 @@ pub fn handle_increase_channel_balance_ibc_receive( let reply_args = ReplyArgs { channel: dst_channel_id.clone(), denom: ibc_denom.clone(), - amount: remote_amount.clone(), + amount: remote_amount, local_receiver: local_receiver.clone(), }; REPLY_ARGS.save(deps.storage, &reply_args)?; @@ -373,10 +373,7 @@ pub fn execute_receive( ) -> Result { nonpayable(&info)?; - let amount = Amount::Cw20(Cw20Coin { - address: info.sender.to_string(), - amount: wrapper.amount, - }); + let amount = Amount::cw20(wrapper.amount, info.sender); let api = deps.api; let msg: TransferBackMsg = from_binary(&wrapper.msg)?; @@ -483,21 +480,16 @@ pub fn execute_transfer_back_to_remote_chain( let mapping = mappings .into_iter() .find(|pair| -> bool { - let (denom, is_native) = parse_voucher_denom( + match parse_voucher_denom( pair.key.as_str(), &IbcEndpoint { port_id: parse_ibc_wasm_port_id(env.contract.address.as_str()), channel_id: msg.local_channel_id.clone(), // also verify local channel id }, - ) - .unwrap_or_else(|_| ("", true)); // if there's an error, change is_native to true so it automatically returns false - if is_native { - return false; + ) { + Ok((denom, false)) => msg.remote_denom.eq(denom), + _ => false, } - if denom.eq(&msg.remote_denom) { - return true; - } - return false; }) .ok_or(ContractError::MappingPairNotFound {})?; diff --git a/contracts/cw-ics20-latest/src/error.rs b/contracts/cw-ics20-latest/src/error.rs index 9ab5556..5af9a59 100644 --- a/contracts/cw-ics20-latest/src/error.rs +++ b/contracts/cw-ics20-latest/src/error.rs @@ -40,7 +40,7 @@ pub enum ContractError { InvalidIbcVersion { version: String }, #[error("Only supports unordered channel")] - OnlyOrderedChannel {}, + OnlyUnorderedChannel {}, #[error("Insufficient funds to redeem voucher on channel {id}, {denom}")] InsufficientFunds { id: String, denom: String }, @@ -83,6 +83,9 @@ pub enum ContractError { #[error("Invalid destination memo {error}")] InvalidDestinationMemo { error: String }, + + #[error("User cannot close channel")] + CannotClose {}, } impl From for ContractError { diff --git a/contracts/cw-ics20-latest/src/ibc.rs b/contracts/cw-ics20-latest/src/ibc.rs index b72efd0..897db74 100644 --- a/contracts/cw-ics20-latest/src/ibc.rs +++ b/contracts/cw-ics20-latest/src/ibc.rs @@ -39,7 +39,7 @@ pub const ORAIBRIDGE_PREFIX: &str = "oraib"; /// This is compatible with the JSON serialization #[cw_serde] pub struct Ics20Packet { - /// amount of tokens to transfer is encoded as a string, but limited to u64 max + /// amount of tokens to transfer is encoded as a string pub amount: Uint128, /// the token denomination to be transferred pub denom: String, @@ -67,14 +67,6 @@ impl Ics20Packet { memo, } } - - pub fn validate(&self) -> Result<(), ContractError> { - if self.amount.u128() > (u128::MAX as u128) { - Err(ContractError::AmountOverflow {}) - } else { - Ok(()) - } - } } /// This is a generic ICS acknowledgement format. @@ -98,7 +90,6 @@ pub fn ack_fail(err: String) -> Binary { to_binary(&res).unwrap() } -// pub const RECEIVE_ID: u64 = 1337; pub const NATIVE_RECEIVE_ID: u64 = 1338; pub const FOLLOW_UP_IBC_SEND_FAILURE_ID: u64 = 1339; pub const REFUND_FAILURE_ID: u64 = 1340; @@ -109,23 +100,15 @@ pub const ACK_FAILURE_ID: u64 = 64023; #[entry_point] pub fn reply(deps: DepsMut, _env: Env, reply: Reply) -> Result { - match reply.id { - // happens only when send cw20 amount to recipient failed. Wont refund because this case is unlikely to happen - NATIVE_RECEIVE_ID => match reply.result { - SubMsgResult::Ok(_) => Ok(Response::new()), - // we all set ack success so that the token is stuck on Oraichain, not on OraiBridge because if ack fail => token refunded on OraiBridge yet still refund on Oraichain - // so no undo increase channel balance - SubMsgResult::Err(err) => Ok(Response::new() + if let SubMsgResult::Err(err) = reply.result { + return match reply.id { + // happens only when send cw20 amount to recipient failed. Wont refund because this case is unlikely to happen + NATIVE_RECEIVE_ID => Ok(Response::new() .set_data(ack_success()) .add_attribute("action", "native_receive_id") .add_attribute("error_transferring_ibc_tokens_to_cw20", err)), - }, - // happens when swap failed. Will refund by sending to the initial receiver of the packet receive, amount is local on Oraichain & send through cw20 - SWAP_OPS_FAILURE_ID => match reply.result { - SubMsgResult::Ok(_) => Ok(Response::new()), - // we all set ack success so that the token is stuck on Oraichain, not on OraiBridge because if ack fail => token refunded on OraiBridge yet still refund on Oraichain - // so no undo increase channel balance - SubMsgResult::Err(err) => { + // happens when swap failed. Will refund by sending to the initial receiver of the packet receive, amount is local on Oraichain & send through cw20 + SWAP_OPS_FAILURE_ID => { let reply_args = REPLY_ARGS.load(deps.storage)?; REPLY_ARGS.remove(deps.storage); let sub_msg = handle_packet_refund( @@ -142,12 +125,9 @@ pub fn reply(deps: DepsMut, _env: Env, reply: Reply) -> Result match reply.result { - SubMsgResult::Ok(_) => Ok(Response::new()), - SubMsgResult::Err(err) => { + // happens when failed to ibc send the packet to another chain after receiving the packet from the first remote chain. + // also when swap is successful. Will refund similarly to swap ops + FOLLOW_UP_IBC_SEND_FAILURE_ID => { let reply_args = SINGLE_STEP_REPLY_ARGS.load(deps.storage)?; SINGLE_STEP_REPLY_ARGS.remove(deps.storage); @@ -179,32 +159,26 @@ pub fn reply(deps: DepsMut, _env: Env, reply: Reply) -> Result will refund manually - REFUND_FAILURE_ID => match reply.result { - SubMsgResult::Ok(_) => Ok(Response::new()), - SubMsgResult::Err(err) => Ok(Response::new() + // fallback case when refund fails. Wont retry => will refund manually + REFUND_FAILURE_ID => { // we all set ack success so that this token is stuck on Oraichain, not on OraiBridge because if ack fail => token refunded on OraiBridge yet still refund on Oraichain - .set_data(ack_success()) - .add_attribute("action", "refund_failure_id") - .add_attribute("error_trying_to_refund_single_step", err)), - }, - // fallback case when we dont have a mapping and have to do IBC transfer and it also failed. Wont refund because it is a rare case as we dont use IBC transfer as much - // this means that we are sending to a normal ibc transfer channel, not ibc wasm. - IBC_TRANSFER_NATIVE_ERROR_ID => match reply.result { - SubMsgResult::Ok(_) => Ok(Response::new()), - SubMsgResult::Err(err) => Ok(Response::new() + Ok(Response::new() + .set_data(ack_success()) + .add_attribute("action", "refund_failure_id") + .add_attribute("error_trying_to_refund_single_step", err)) + } + + // fallback case when we dont have a mapping and have to do IBC transfer and it also failed. Wont refund because it is a rare case as we dont use IBC transfer as much + // this means that we are sending to a normal ibc transfer channel, not ibc wasm. + IBC_TRANSFER_NATIVE_ERROR_ID => { // we all set ack success so that this token is stuck on Oraichain, not on OraiBridge because if ack fail => token refunded on OraiBridge yet still refund on Oraichain - .set_data(ack_success()) - .add_attribute("action", "ibc_transfer_native_error_id") - .add_attribute("error_trying_to_transfer_ibc_native_with_error", err)), - }, - // happens when convert failed. Will refund by sending to the initial receiver of the packet receive, amount is local on Oraichain & send through cw20 - CONVERT_FAILURE_ID => match reply.result { - SubMsgResult::Ok(_) => Ok(Response::new()), - // we all set ack success so that the token is stuck on Oraichain, not on OraiBridge because if ack fail => token refunded on OraiBridge yet still refund on Oraichain - // so no undo increase - SubMsgResult::Err(err) => { + Ok(Response::new() + .set_data(ack_success()) + .add_attribute("action", "ibc_transfer_native_error_id") + .add_attribute("error_trying_to_transfer_ibc_native_with_error", err)) + } + // happens when convert failed. Will refund by sending to the initial receiver of the packet receive, amount is local on Oraichain & send through cw20 + CONVERT_FAILURE_ID => { let reply_args = CONVERT_REPLY_ARGS.load(deps.storage)?; CONVERT_REPLY_ARGS.remove(deps.storage); let sub_msg = handle_asset_refund(reply_args.local_receiver, reply_args.asset)?; @@ -215,9 +189,11 @@ pub fn reply(deps: DepsMut, _env: Env, reply: Reply) -> Result Err(ContractError::UnknownReplyId { id: reply.id }), + _ => Err(ContractError::UnknownReplyId { id: reply.id }), + }; } + // default response + Ok(Response::new()) } #[entry_point] @@ -269,7 +245,7 @@ fn enforce_order_and_version( } } if channel.order != ICS20_ORDERING { - return Err(ContractError::OnlyOrderedChannel {}); + return Err(ContractError::OnlyUnorderedChannel {}); } Ok(()) } @@ -280,9 +256,8 @@ pub fn ibc_channel_close( _env: Env, _channel: IbcChannelCloseMsg, ) -> Result { - // TODO: what to do here? - // we will have locked funds that need to be returned somehow - unimplemented!(); + // don't allow close channel + Err(ContractError::CannotClose {}) } #[entry_point] @@ -351,7 +326,7 @@ pub fn parse_voucher_denom<'a>( // Returns local denom if the denom is an encoded voucher from the expected endpoint // Otherwise, error -pub fn parse_ibc_denom_without_sanity_checks<'a>(ibc_denom: &'a str) -> StdResult<&'a str> { +pub fn parse_ibc_denom_without_sanity_checks(ibc_denom: &str) -> StdResult<&str> { let split_denom: Vec<&str> = ibc_denom.splitn(3, '/').collect(); if split_denom.len() != 3 { @@ -364,7 +339,7 @@ pub fn parse_ibc_denom_without_sanity_checks<'a>(ibc_denom: &'a str) -> StdResul // Returns // Otherwise, error -pub fn parse_ibc_channel_without_sanity_checks<'a>(ibc_denom: &'a str) -> StdResult<&'a str> { +pub fn parse_ibc_channel_without_sanity_checks(ibc_denom: &str) -> StdResult<&str> { let split_denom: Vec<&str> = ibc_denom.splitn(3, '/').collect(); if split_denom.len() != 3 { @@ -375,9 +350,7 @@ pub fn parse_ibc_channel_without_sanity_checks<'a>(ibc_denom: &'a str) -> StdRes Ok(split_denom[1]) } -pub fn parse_ibc_info_without_sanity_checks<'a>( - ibc_denom: &'a str, -) -> StdResult<(&'a str, &'a str, &'a str)> { +pub fn parse_ibc_info_without_sanity_checks(ibc_denom: &str) -> StdResult<(&str, &str, &str)> { let split_denom: Vec<&str> = ibc_denom.splitn(3, '/').collect(); if split_denom.len() != 3 { @@ -398,7 +371,6 @@ fn do_ibc_packet_receive( relayer: &str, ) -> Result { let msg: Ics20Packet = from_binary(&packet.data)?; - // let channel = packet.dest.channel_id.clone(); // If the token originated on the remote chain, it looks like "ucosm". // If it originated on our chain, it looks like "port/channel/ucosm". @@ -407,11 +379,11 @@ fn do_ibc_packet_receive( // if denom is native, we handle it the native way if denom.1 { return handle_ibc_packet_receive_native_remote_chain( - storage, api, &querier, env, &denom.0, &packet, &msg, relayer, + storage, api, querier, env, denom.0, packet, &msg, relayer, ); } - Err(ContractError::Std(StdError::generic_err("Not suppported"))) + Err(ContractError::Std(StdError::generic_err("Not supported"))) } fn handle_ibc_packet_receive_native_remote_chain( @@ -442,9 +414,9 @@ fn handle_ibc_packet_receive_native_remote_chain( let pair_mapping = ics20_denoms() .load(storage, &ibc_denom) .map_err(|_| ContractError::NotOnMappingList {})?; - let initial_receive_asset_info = pair_mapping.asset_info.clone(); + let initial_receive_asset_info = pair_mapping.asset_info; let to_send = Amount::from_parts( - parse_asset_info_denom(initial_receive_asset_info.clone()), + parse_asset_info_denom(&initial_receive_asset_info), convert_remote_to_local( msg.amount, pair_mapping.remote_decimals, @@ -516,25 +488,25 @@ fn handle_ibc_packet_receive_native_remote_chain( if !swap_operations.is_empty() { // if simulate swap fails, set fee to zero - additional_relayer_fee = match config.swap_router_contract.simulate_swap( - querier, - additional_relayer_fee, - swap_operations, - ) { - Ok(res) => res.amount, - Err(_) => Uint128::default(), - }; + additional_relayer_fee = config + .swap_router_contract + .simulate_swap(querier, additional_relayer_fee, swap_operations) + .map(|res| res.amount) + .unwrap_or_default(); } - fee_data.relayer_fee = fee_data.relayer_fee.checked_add(additional_relayer_fee); - fee_data.deducted_amount = fee_data - .deducted_amount - .checked_sub( - additional_token_fee - .checked_add(additional_relayer_fee) - .unwrap_or(additional_token_fee), - ) - .unwrap_or_default(); + // if additional_relayer_fee is greater than 0 + if !additional_relayer_fee.is_zero() { + fee_data.relayer_fee = fee_data.relayer_fee.checked_add(additional_relayer_fee); + fee_data.deducted_amount = fee_data + .deducted_amount + .checked_sub( + additional_token_fee + .checked_add(additional_relayer_fee) + .unwrap_or(additional_token_fee), + ) + .unwrap_or_default(); + } } // if the fees have consumed all user funds, we send all the fees to our token fee receiver @@ -639,10 +611,10 @@ pub fn get_follow_up_msgs( let mut minimum_receive = to_send.amount(); let mut ibc_transfer_amount = to_send.amount(); - if swap_operations.len() > 0 { + if !swap_operations.is_empty() { let response = config.swap_router_contract.simulate_swap( querier, - to_send.amount().clone(), + to_send.amount(), swap_operations.clone(), ); if response.is_err() { @@ -687,14 +659,14 @@ pub fn get_follow_up_msgs( env, orai_receiver, ibc_transfer_amount, - &ibc_sender, - &destination, + ibc_sender, + destination, config.default_timeout, destination_pair_mapping, destination_asset_info_on_orai, ); - if let Some(ibc_msg) = build_ibc_msg_result.as_mut().ok() { + if let Ok(ibc_msg) = build_ibc_msg_result.as_mut() { sub_msgs.append(ibc_msg); // if there's an ibc msg => swap receiver is None so the receiver is this ibc wasm address to = None; @@ -724,7 +696,7 @@ pub fn get_follow_up_msgs( return Ok(follow_up_msgs_data); }; follow_up_msgs_data.sub_msgs = sub_msgs; - return Ok(follow_up_msgs_data); + Ok(follow_up_msgs_data) } pub fn build_swap_operations( @@ -765,11 +737,11 @@ pub fn build_swap_msgs( operations: Vec, ) -> StdResult<()> { // the swap msg must be executed before other msgs because we need the ask token amount to create ibc msg => insert in first index - if operations.len() == 0 { + if operations.is_empty() { return Ok(()); } // double check. We cannot let swap ops with Some(to) aka swap to someone else, not this contract and then transfer ibc => would be double spending - if to.is_some() && sub_msgs.len() > 0 { + if to.is_some() && !sub_msgs.is_empty() { // forbidden case. Pop all sub messages and return empty while sub_msgs.pop().is_some() { sub_msgs.pop(); @@ -813,7 +785,7 @@ pub fn build_ibc_msg( let (is_evm_based, _) = destination.is_receiver_evm_based(); if is_evm_based { if let Some(mapping) = pair_mapping { - return Ok(process_ibc_msg( + return process_ibc_msg( mapping, env.contract.address.to_string(), local_receiver, @@ -823,7 +795,7 @@ pub fn build_ibc_msg( Some(destination.receiver.clone()), amount, timeout, - )?); + ); } return Err(StdError::generic_err("cannot find pair mappings")); } @@ -831,7 +803,7 @@ pub fn build_ibc_msg( let is_cosmos_based = destination.is_receiver_cosmos_based(); if is_cosmos_based { if let Some(mapping) = pair_mapping { - return Ok(process_ibc_msg( + return process_ibc_msg( mapping, env.contract.address.to_string(), local_receiver, @@ -841,7 +813,7 @@ pub fn build_ibc_msg( None, // no need memo because it is not used in the remote cosmos based chain amount, timeout, - )?); + ); } // final case, where the destination token is from a remote chain that we dont have a pair mapping with. @@ -871,7 +843,7 @@ pub fn build_ibc_msg( )]); } Err(StdError::generic_err( - "The destination info is neither evm or cosmos based", + "The destination info is neither evm nor cosmos based", )) } @@ -910,7 +882,7 @@ pub fn process_ibc_msg( msg: to_binary(&ExecuteMsg::ReduceChannelBalanceIbcReceive { src_channel_id: src_channel.to_string(), ibc_denom: pair_query.key.clone(), - amount: remote_amount.clone(), + amount: remote_amount, local_receiver: local_receiver.to_string(), })?, funds: vec![], @@ -926,7 +898,7 @@ pub fn check_gas_limit(deps: Deps, amount: &Amount) -> Result, Contr match amount { Amount::Cw20(coin) => { // if cw20 token, use the registered gas limit, or error if not whitelisted - let addr = deps.api.addr_validate(&coin.address)?; + let addr = deps.api.addr_validate(coin.address.as_str())?; let allowed = ALLOW_LIST.may_load(deps.storage, &addr)?; match allowed { Some(allow) => Ok(allow.gas_limit), @@ -952,6 +924,18 @@ pub fn process_deduct_fee( let local_denom = local_amount.denom(); let (deducted_amount, token_fee) = deduct_token_fee(storage, remote_token_denom, local_amount.amount())?; + + let mut fee_data = FeeData { + deducted_amount, + token_fee: Amount::from_parts(local_denom.clone(), token_fee), + relayer_fee: Amount::from_parts(local_denom.clone(), Uint128::zero()), + }; + // if after token fee, the deducted amount is 0 then we deduct all to token fee + if deducted_amount.is_zero() { + fee_data.token_fee = local_amount; + return Ok(fee_data); + } + // simulate for relayer fee let ask_asset_info = denom_to_asset_info(api, &local_amount.raw_denom()); @@ -965,25 +949,11 @@ pub fn process_deduct_fee( swap_router_contract, )?; - let mut fee_data = FeeData { - deducted_amount: deducted_amount.checked_sub(relayer_fee).unwrap_or_default(), - token_fee: Amount::from_parts(local_denom.clone(), token_fee), - relayer_fee: Amount::from_parts(local_denom.clone(), relayer_fee), - }; - - // if after token fee, the deducted amount is 0 then we deduct all to token fee - if deducted_amount.is_zero() { - fee_data.deducted_amount = Uint128::zero(); - fee_data.token_fee = local_amount; - fee_data.relayer_fee = Amount::from_parts(local_denom, Uint128::zero()); - return Ok(fee_data); - } + fee_data.deducted_amount = deducted_amount.checked_sub(relayer_fee).unwrap_or_default(); + fee_data.relayer_fee = Amount::from_parts(local_denom.clone(), relayer_fee); // if the relayer fee makes the final amount 0, then we charge the remaining deducted amount as relayer fee if fee_data.deducted_amount.is_zero() { - fee_data.deducted_amount = Uint128::zero(); - fee_data.token_fee = Amount::from_parts(local_denom.clone(), token_fee); fee_data.relayer_fee = Amount::from_parts(local_denom.clone(), deducted_amount); - return Ok(fee_data); } Ok(fee_data) } @@ -993,13 +963,13 @@ pub fn deduct_token_fee( remote_token_denom: &str, amount: Uint128, ) -> StdResult<(Uint128, Uint128)> { - let token_fee = TOKEN_FEE.may_load(storage, &remote_token_denom)?; + let token_fee = TOKEN_FEE.may_load(storage, remote_token_denom)?; if let Some(token_fee) = token_fee { let fee = deduct_fee(token_fee, amount); let new_deducted_amount = amount.checked_sub(fee)?; return Ok((new_deducted_amount, fee)); } - Ok((amount, Uint128::from(0u64))) + Ok((amount, Uint128::zero())) } pub fn deduct_relayer_fee( @@ -1014,37 +984,35 @@ pub fn deduct_relayer_fee( // this is bech32 prefix of sender from other chains. Should not error because we are in the cosmos ecosystem. Every address should have prefix // evm case, need to filter remote token denom since prefix is always oraib let prefix_result = get_prefix_decode_bech32(remote_address); - // api.debug(format!("prefix: {}", prefix).as_str()); - let prefix: String = if prefix_result.is_err() { - convert_remote_denom_to_evm_prefix(remote_token_denom) - } else { - let prefix = prefix_result.unwrap(); - if prefix.eq(ORAIBRIDGE_PREFIX) { - convert_remote_denom_to_evm_prefix(remote_token_denom) - } else { - prefix + + let prefix: String = match prefix_result { + Err(_) => convert_remote_denom_to_evm_prefix(remote_token_denom), + Ok(prefix) => { + if prefix.eq(ORAIBRIDGE_PREFIX) { + convert_remote_denom_to_evm_prefix(remote_token_denom) + } else { + prefix + } } }; let relayer_fee = RELAYER_FEE.may_load(storage, &prefix)?; // no need to deduct fee if no fee is found in the mapping - if relayer_fee.is_none() { - return Ok(Uint128::from(0u64)); - } - - let relayer_fee = get_swap_token_amount_out_from_orai( - querier, - relayer_fee.unwrap(), - swap_router_contract, - ask_asset_info, - ); - - Ok(relayer_fee) + Ok(relayer_fee + .map(|offer_amount| { + get_swap_token_amount_out_from_orai( + querier, + offer_amount, + swap_router_contract, + ask_asset_info, + ) + }) + .unwrap_or_default()) } pub fn deduct_fee(token_fee: Ratio, amount: Uint128) -> Uint128 { // ignore case where denominator is zero since we cannot divide with 0 if token_fee.denominator == 0 { - return Uint128::from(0u64); + return Uint128::zero(); } amount.mul(Decimal::from_ratio( token_fee.nominator, @@ -1064,7 +1032,7 @@ pub fn get_swap_token_amount_out_from_orai( if ask_asset_info.eq(&orai_asset_info) { return offer_amount; } - let token_price = swap_router_contract + swap_router_contract .simulate_swap( querier, offer_amount, @@ -1075,13 +1043,12 @@ pub fn get_swap_token_amount_out_from_orai( }], ) .map(|data| data.amount) - .unwrap_or_default(); - token_price + .unwrap_or_default() } pub fn convert_remote_denom_to_evm_prefix(remote_denom: &str) -> String { match remote_denom.split_once("0x") { - Some((evm_prefix, _)) => return evm_prefix.to_string(), + Some((evm_prefix, _)) => evm_prefix.to_string(), None => "".to_string(), } } @@ -1109,21 +1076,6 @@ pub fn collect_fee_msgs( Ok(cosmos_msgs) } -pub fn find_evm_pair_mapping( - ibc_denom_pair_mapping_key: &str, - evm_prefix: &str, - destination_channel: &str, -) -> bool { - // eg: 'wasm.orai195269awwnt5m6c843q6w7hp8rt0k7syfu9de4h0wz384slshuzps8y7ccm/channel-29/eth-mainnet0x4c11249814f11b9346808179Cf06e71ac328c1b5' - // parse to get eth-mainnet0x... - // then collect eth-mainnet prefix, and compare with dest channel - // then we compare the dest channel with the pair mapping. They should match as well - let (_, ibc_channel, ibc_denom) = - parse_ibc_info_without_sanity_checks(ibc_denom_pair_mapping_key).unwrap_or_default(); - convert_remote_denom_to_evm_prefix(ibc_denom).eq(&evm_prefix) - && ibc_channel.eq(destination_channel) -} - #[entry_point] /// check if success or failure and update balance, or return funds /// This entrypoint is called when we receive an acknowledgement packet from a remote chain @@ -1227,7 +1179,7 @@ pub fn handle_packet_refund( // check if mint_burn mechanism, then mint token for packet sender, if not, send from contract let send_amount_msg = Amount::from_parts( - parse_asset_info_denom(pair_mapping.asset_info.to_owned()), + parse_asset_info_denom(&pair_mapping.asset_info), local_amount, ) .send_amount(packet_sender.to_string(), None); @@ -1263,15 +1215,10 @@ pub fn build_ibc_send_packet( ) -> StdResult { // build ics20 packet let packet = Ics20Packet::new( - amount.clone(), + amount, denom, // we use ibc denom in form // so that when it is sent back to remote chain, it gets parsed correctly and burned - sender, - receiver, - memo, + sender, receiver, memo, ); - packet - .validate() - .map_err(|err| StdError::generic_err(err.to_string()))?; // prepare ibc message Ok(IbcMsg::SendPacket { @@ -1282,7 +1229,7 @@ pub fn build_ibc_send_packet( } pub fn handle_asset_refund(receiver: String, asset: Asset) -> Result { - let to_send = Amount::from_parts(parse_asset_info_denom(asset.info), asset.amount); + let to_send = Amount::from_parts(parse_asset_info_denom(&asset.info), asset.amount); let cosmos_msg = to_send.send_amount(receiver, None); // used submsg here & reply on error. This means that if the refund process fails => tokens will be locked in this IBC Wasm contract. We will manually handle that case. No retry diff --git a/contracts/cw-ics20-latest/src/ibc_hooks.rs b/contracts/cw-ics20-latest/src/ibc_hooks.rs index 14930ea..f5af3bc 100644 --- a/contracts/cw-ics20-latest/src/ibc_hooks.rs +++ b/contracts/cw-ics20-latest/src/ibc_hooks.rs @@ -78,7 +78,7 @@ pub fn ibc_hooks_universal_swap( }; let mut to_send_amount = - Amount::from_parts(parse_asset_info_denom(to_send.info.clone()), to_send.amount); + Amount::from_parts(parse_asset_info_denom(&to_send.info), to_send.amount); let follow_up_msg_data; // There are 2 cases: @@ -159,7 +159,7 @@ pub fn ibc_hooks_universal_swap( } to_send_amount = Amount::from_parts( - parse_asset_info_denom(to_send.info.clone()), + parse_asset_info_denom(&to_send.info), fee_data.deducted_amount, ); } else { diff --git a/contracts/cw-ics20-latest/src/query_helper.rs b/contracts/cw-ics20-latest/src/query_helper.rs index 0377682..92db537 100644 --- a/contracts/cw-ics20-latest/src/query_helper.rs +++ b/contracts/cw-ics20-latest/src/query_helper.rs @@ -38,14 +38,14 @@ pub fn get_destination_info_on_orai( ) -> (AssetInfo, Option) { // destination is Oraichain if destination_channel.is_empty() { - return (denom_to_asset_info(api, &destination_denom), None); + return (denom_to_asset_info(api, destination_denom), None); } // case 1: port is ibc wasm, must be registered in mapping let ibc_denom = get_key_ics20_ibc_denom( &parse_ibc_wasm_port_id(env.contract.address.as_str()), - &destination_channel, - &destination_denom, + destination_channel, + destination_denom, ); if let Ok(pair_mapping) = ics20_denoms().load(storage, &ibc_denom) { return ( diff --git a/contracts/cw-ics20-latest/src/state.rs b/contracts/cw-ics20-latest/src/state.rs index 8468812..2fa74d2 100644 --- a/contracts/cw-ics20-latest/src/state.rs +++ b/contracts/cw-ics20-latest/src/state.rs @@ -65,7 +65,9 @@ impl<'a> IndexList for MappingMetadataIndexex<'a> { } } -// used when chain A (no cosmwasm) sends native token to chain B (has cosmwasm). key - original denom of chain A, in form of ibc no hash for destination port & channel - transfer/channel-0/uatom for example; value - mapping data including asset info, can be either native or cw20 +/// used when chain A (no cosmwasm) sends native token to chain B (has cosmwasm). key - original denom of chain A, +/// in form of ibc no hash for destination port & channel - transfer/channel-0/uatom for example; value - mapping data +/// including asset info, can be either native or cw20 pub fn ics20_denoms<'a>() -> IndexedMap<'a, &'a str, MappingMetadata, MappingMetadataIndexex<'a>> { let indexes = MappingMetadataIndexex { asset_info: MultiIndex::new( @@ -158,13 +160,12 @@ pub fn increase_channel_balance( denom: &str, // should be ibc denom amount: Uint128, ) -> Result<(), ContractError> { - CHANNEL_REVERSE_STATE.update(storage, (channel, denom), |orig| -> StdResult<_> { - let mut state = orig.unwrap_or_default(); - state.outstanding += amount; - state.total_sent += amount; - Ok(state) - })?; - Ok(()) + let store = CHANNEL_REVERSE_STATE.key((channel, denom)); + // whatever error or not found, return default + let mut state = store.load(storage).unwrap_or_default(); + state.outstanding += amount; + state.total_sent += amount; + store.save(storage, &state).map_err(ContractError::Std) } pub fn reduce_channel_balance( @@ -173,26 +174,24 @@ pub fn reduce_channel_balance( denom: &str, // should be ibc denom amount: Uint128, ) -> Result<(), ContractError> { - CHANNEL_REVERSE_STATE.update( - storage, - (channel, denom), - |orig| -> Result<_, ContractError> { - // this will return error if we don't have the funds there to cover the request (or no denom registered) - let mut cur = orig.ok_or(ContractError::NoSuchChannelState { + let store = CHANNEL_REVERSE_STATE.key((channel, denom)); + let Ok(mut state) = store.load(storage) else { + return Err(ContractError::NoSuchChannelState { + id: channel.to_string(), + denom: denom.to_string(), + }); + }; + + state.outstanding = + state + .outstanding + .checked_sub(amount) + .map_err(|_| ContractError::InsufficientFunds { id: channel.to_string(), denom: denom.to_string(), })?; - cur.outstanding = - cur.outstanding - .checked_sub(amount) - .or(Err(ContractError::InsufficientFunds { - id: channel.to_string(), - denom: denom.to_string(), - }))?; - Ok(cur) - }, - )?; - Ok(()) + + store.save(storage, &state).map_err(ContractError::Std) } // only used for admin of the contract diff --git a/contracts/cw-ics20-latest/src/testing/ibc_tests.rs b/contracts/cw-ics20-latest/src/testing/ibc_tests.rs index 4065ca7..b2fd021 100644 --- a/contracts/cw-ics20-latest/src/testing/ibc_tests.rs +++ b/contracts/cw-ics20-latest/src/testing/ibc_tests.rs @@ -1,8 +1,8 @@ use std::ops::Sub; use cosmwasm_std::{ - coin, Addr, BankMsg, CosmosMsg, Decimal, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcTimeout, - StdError, + coin, Addr, BankMsg, Coin, CosmosMsg, Decimal, IbcChannelConnectMsg, IbcChannelOpenMsg, + IbcTimeout, StdError, }; use cosmwasm_testing_util::mock::MockContract; use cosmwasm_vm::testing::MockInstanceOptions; @@ -34,7 +34,7 @@ use crate::state::{ MappingMetadata, Ratio, RelayerFee, TokenFee, CHANNEL_REVERSE_STATE, RELAYER_FEE, REPLY_ARGS, TOKEN_FEE, }; -use cw20::{Cw20Coin, Cw20ExecuteMsg, Cw20ReceiveMsg}; +use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; use cw20_ics20_msg::amount::{convert_local_to_remote, convert_remote_to_local, Amount}; use crate::contract::{ @@ -265,7 +265,7 @@ fn proper_checks_on_execute_native_transfer_back_to_remote() { let custom_addr = "custom-addr"; let original_sender = "original_sender"; let denom = "uatom0x"; - let amount = 1234567u128; + let amount = Uint128::from(1234567u128); let token_addr = Addr::unchecked("token-addr".to_string()); let asset_info = AssetInfo::Token { contract_addr: token_addr.clone(), @@ -277,8 +277,7 @@ fn proper_checks_on_execute_native_transfer_back_to_remote() { nominator: 1, denominator: 10, }; - let fee_amount = - Uint128::from(amount) * Decimal::from_ratio(ratio.nominator, ratio.denominator); + let fee_amount = amount * Decimal::from_ratio(ratio.nominator, ratio.denominator); let mut deps = setup(&[remote_channel, local_channel], &[]); TOKEN_FEE .save(deps.as_mut().storage, denom, &ratio) @@ -312,7 +311,7 @@ fn proper_checks_on_execute_native_transfer_back_to_remote() { let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { sender: original_sender.to_string(), - amount: Uint128::from(amount), + amount, msg: to_binary(&transfer).unwrap(), }); @@ -328,8 +327,13 @@ fn proper_checks_on_execute_native_transfer_back_to_remote() { ); // prepare some mock packets - let recv_packet = - mock_receive_packet(remote_channel, local_channel, amount, denom, custom_addr); + let recv_packet = mock_receive_packet( + remote_channel, + local_channel, + amount, + denom.to_string(), + custom_addr.to_string(), + ); // receive some tokens. Assume that the function works perfectly because the test case is elsewhere let ibc_msg = IbcPacketReceiveMsg::new(recv_packet.clone(), relayer); @@ -398,7 +402,7 @@ fn proper_checks_on_execute_native_transfer_back_to_remote() { msg, to_binary(&Cw20ExecuteMsg::Transfer { recipient: "gov".to_string(), - amount: fee_amount.clone() + amount: fee_amount }) .unwrap() ); @@ -411,15 +415,15 @@ fn proper_checks_on_execute_native_transfer_back_to_remote() { assert_eq!( chan.balances, vec![Amount::native( - fee_amount.u128(), - &get_key_ics20_ibc_denom(CONTRACT_PORT, local_channel, denom) + fee_amount, + get_key_ics20_ibc_denom(CONTRACT_PORT, local_channel, denom) )] ); assert_eq!( chan.total_sent, vec![Amount::native( amount, - &get_key_ics20_ibc_denom(CONTRACT_PORT, local_channel, denom) + get_key_ics20_ibc_denom(CONTRACT_PORT, local_channel, denom) )] ); @@ -703,7 +707,7 @@ fn test_build_swap_msgs() { build_swap_msgs( minimum_receive.clone(), &oraiswap::router::RouterController(swap_router_contract.to_string()), - amount.clone(), + amount, initial_receive_asset_info.clone(), to.clone(), &mut cosmos_msgs, @@ -716,10 +720,10 @@ fn test_build_swap_msgs() { ask_asset_info: initial_receive_asset_info.clone(), }); build_swap_msgs( - minimum_receive.clone(), + minimum_receive, &oraiswap::router::RouterController(swap_router_contract.to_string()), - amount.clone(), - initial_receive_asset_info.clone(), + amount, + initial_receive_asset_info, to.clone(), &mut cosmos_msgs, operations.clone(), @@ -734,10 +738,10 @@ fn test_build_swap_msgs() { denom: native_denom.to_string(), }; build_swap_msgs( - minimum_receive.clone(), + minimum_receive, &oraiswap::router::RouterController(swap_router_contract.to_string()), - amount.clone(), - initial_receive_asset_info.clone(), + amount, + initial_receive_asset_info, to.clone(), &mut cosmos_msgs, operations.clone(), @@ -791,10 +795,10 @@ fn test_build_swap_msgs_forbidden_case() { amount: coins(1u128, "orai"), }))); build_swap_msgs( - minimum_receive.clone(), + minimum_receive, &oraiswap::router::RouterController(swap_router_contract.to_string()), - amount.clone(), - initial_receive_asset_info.clone(), + amount, + initial_receive_asset_info, Some(Addr::unchecked("attacker")), &mut cosmos_msgs, operations.clone(), @@ -917,7 +921,7 @@ fn test_get_ibc_msg_evm_case() { CosmosMsg::Ibc(IbcMsg::SendPacket { channel_id: destination.destination_channel.clone(), data: to_binary(&Ics20Packet::new( - remote_amount.clone(), + remote_amount, pair_mapping_key.clone(), env.contract.address.as_str(), &remote_address, @@ -960,7 +964,7 @@ fn test_get_ibc_msg_cosmos_based_case() { let local_channel_id = "channel"; let local_receiver = "receiver"; let timeout = 10u64; - let remote_amount = convert_local_to_remote(amount.clone(), 18, 6).unwrap(); + let remote_amount = convert_local_to_remote(amount, 18, 6).unwrap(); let destination = DestinationInfo { receiver: "cosmos1zedxv25ah8fksmg2lzrndrpkvsjqgk4zt5ff7n".to_string(), destination_channel: send_channel.to_string(), @@ -984,7 +988,7 @@ fn test_get_ibc_msg_cosmos_based_case() { deps.as_mut().storage, (local_channel_id, ibc_denom.as_str()), &ChannelState { - outstanding: remote_amount.clone(), + outstanding: remote_amount, total_sent: Uint128::from(100u128), }, ) @@ -995,7 +999,7 @@ fn test_get_ibc_msg_cosmos_based_case() { deps.as_mut().storage, (send_channel, pair_mapping_key.as_str()), &ChannelState { - outstanding: remote_amount.clone(), + outstanding: remote_amount, total_sent: Uint128::from(100u128), }, ) @@ -1050,7 +1054,7 @@ fn test_get_ibc_msg_cosmos_based_case() { deps.as_mut().storage, (local_channel_id, &pair_mapping_key), &ChannelState { - outstanding: remote_amount.clone(), + outstanding: remote_amount, total_sent: Uint128::from(100u128), }, ) @@ -1083,7 +1087,7 @@ fn test_get_ibc_msg_cosmos_based_case() { CosmosMsg::Ibc(IbcMsg::SendPacket { channel_id: send_channel.to_string(), data: to_binary(&Ics20Packet::new( - remote_amount.clone(), + remote_amount, pair_mapping_key.clone(), env.contract.address.as_str(), &destination.receiver, @@ -1141,7 +1145,7 @@ fn test_get_ibc_msg_neither_cosmos_or_evm_based_case() { .unwrap_err(); assert_eq!( result, - StdError::generic_err("The destination info is neither evm or cosmos based") + StdError::generic_err("The destination info is neither evm nor cosmos based") ) } @@ -1166,17 +1170,14 @@ fn test_follow_up_msgs() { deps_mut.api, &deps_mut.querier, env.clone(), - Amount::Cw20(Cw20Coin { - address: "foobar".to_string(), - amount: amount.clone(), - }), + Amount::cw20(amount, Addr::unchecked("foobar")), initial_asset_info.clone(), AssetInfo::NativeToken { denom: "".to_string(), }, "foobar", receiver, - &DestinationInfo::from_str(""), + &DestinationInfo::parse_str(""), None, ) .unwrap(); @@ -1188,7 +1189,7 @@ fn test_follow_up_msgs() { contract_addr: env.contract.address.to_string(), msg: to_binary(&Cw20ExecuteMsg::Transfer { recipient: receiver.to_string(), - amount: amount.clone() + amount }) .unwrap(), funds: vec![] @@ -1204,17 +1205,14 @@ fn test_follow_up_msgs() { deps_mut.api, &deps_mut.querier, env.clone(), - Amount::Cw20(Cw20Coin { - address: "foobar".to_string(), - amount, - }), + Amount::cw20(amount, Addr::unchecked("foobar")), initial_asset_info.clone(), AssetInfo::NativeToken { denom: "cosmosabcd".to_string(), }, "foobar", "foobar", - &DestinationInfo::from_str(memo), + &DestinationInfo::parse_str(memo), None, ) .unwrap(); @@ -1226,7 +1224,7 @@ fn test_follow_up_msgs() { contract_addr: env.contract.address.to_string(), msg: to_binary(&Cw20ExecuteMsg::Transfer { recipient: receiver.to_string(), - amount: amount.clone() + amount }) .unwrap(), funds: vec![] @@ -1242,10 +1240,7 @@ fn test_follow_up_msgs() { deps_mut.api, &deps_mut.querier, env.clone(), - Amount::Cw20(Cw20Coin { - address: "foobar".to_string(), - amount, - }), + Amount::cw20(amount, Addr::unchecked("foobar")), AssetInfo::NativeToken { denom: "orai".to_string(), }, @@ -1254,7 +1249,7 @@ fn test_follow_up_msgs() { }, "foobar", "foobar", - &DestinationInfo::from_str(memo), + &DestinationInfo::parse_str(memo), None, ) .unwrap(); @@ -1266,7 +1261,7 @@ fn test_follow_up_msgs() { contract_addr: env.contract.address.to_string(), msg: to_binary(&Cw20ExecuteMsg::Transfer { recipient: receiver.to_string(), - amount: amount.clone() + amount }) .unwrap(), funds: vec![] @@ -1286,7 +1281,7 @@ fn test_deduct_fee() { }, Uint128::from(1000u64) ), - Uint128::from(0u64) + Uint128::zero() ); assert_eq!( deduct_fee( @@ -1380,10 +1375,7 @@ fn test_deduct_token_fee() { let storage = deps.as_mut().storage; let token_fee_denom = "foo0x"; // should return amount because we have not set relayer fee yet - assert_eq!( - deduct_token_fee(storage, "foo", amount).unwrap().0, - amount.clone() - ); + assert_eq!(deduct_token_fee(storage, "foo", amount).unwrap().0, amount); TOKEN_FEE .save( storage, @@ -1423,7 +1415,7 @@ fn test_deduct_relayer_fee() { &swap_router_contract, ) .unwrap(); - assert_eq!(result, Uint128::from(0u64)); + assert_eq!(result, Uint128::zero()); // remote address is wrong (dont follow bech32 form) assert_eq!( @@ -1452,7 +1444,7 @@ fn test_deduct_relayer_fee() { &swap_router_contract, ) .unwrap(), - Uint128::from(0u64) + Uint128::zero() ); // oraib prefix case. @@ -1518,14 +1510,14 @@ fn test_process_ibc_msg() { let local_receiver = "local_receiver"; let memo = None; let timeout = Timestamp::from_seconds(10u64); - let remote_amount = convert_local_to_remote(amount.clone(), 18, 6).unwrap(); + let remote_amount = convert_local_to_remote(amount, 18, 6).unwrap(); CHANNEL_REVERSE_STATE .save( storage, (local_channel_id, ibc_denom), &ChannelState { - outstanding: remote_amount.clone(), + outstanding: remote_amount, total_sent: Uint128::from(100u128), }, ) @@ -1566,7 +1558,7 @@ fn test_process_ibc_msg() { IbcMsg::SendPacket { channel_id: local_channel_id.to_string(), data: to_binary(&Ics20Packet { - amount: remote_amount.clone(), + amount: remote_amount, denom: ibc_denom.to_string(), receiver: ibc_msg_receiver.to_string(), sender: ibc_msg_sender.to_string(), @@ -2041,16 +2033,16 @@ fn test_delete_cw20_mapping() { fn mock_receive_packet( remote_channel: &str, local_channel: &str, - amount: u128, - denom: &str, - receiver: &str, + amount: Uint128, + denom: String, + receiver: String, ) -> IbcPacket { let data = Ics20Packet { // this is returning a foreign (our) token, thus denom is // - denom: denom.to_string(), - amount: amount.into(), + denom, + amount, sender: "remote-sender".to_string(), - receiver: receiver.to_string(), + receiver, memo: Some("memo".to_string()), }; IbcPacket::new( @@ -2077,7 +2069,7 @@ fn proper_checks_on_execute_cw20_transfer_back_to_remote() { let custom_addr = "custom-addr"; let original_sender = "original_sender"; let denom = "uatom0x"; - let amount = 1234567u128; + let amount = Uint128::from(1234567u128); let asset_info = AssetInfo::NativeToken { denom: denom.into(), }; @@ -2088,8 +2080,7 @@ fn proper_checks_on_execute_cw20_transfer_back_to_remote() { nominator: 1, denominator: 10, }; - let fee_amount = - Uint128::from(amount) * Decimal::from_ratio(ratio.nominator, ratio.denominator); + let fee_amount = amount * Decimal::from_ratio(ratio.nominator, ratio.denominator); let mut deps = setup(&[remote_channel, local_channel], &[]); TOKEN_FEE .save(deps.as_mut().storage, denom, &ratio) @@ -2124,7 +2115,13 @@ fn proper_checks_on_execute_cw20_transfer_back_to_remote() { let msg = ExecuteMsg::TransferToRemote(transfer.clone()); // insufficient funds case because we need to receive from remote chain first - let info = mock_info(cw20_raw_denom, &[coin(amount, denom)]); + let info = mock_info( + cw20_raw_denom, + &[Coin { + amount, + denom: denom.to_string(), + }], + ); let res = execute(deps.as_mut(), mock_env(), info.clone(), msg.clone()); assert_eq!( res.unwrap_err(), @@ -2135,8 +2132,13 @@ fn proper_checks_on_execute_cw20_transfer_back_to_remote() { ); // prepare some mock packets - let recv_packet = - mock_receive_packet(remote_channel, local_channel, amount, denom, custom_addr); + let recv_packet = mock_receive_packet( + remote_channel, + local_channel, + amount, + denom.to_string(), + custom_addr.to_string(), + ); // receive some tokens. Assume that the function works perfectly because the test case is elsewhere let ibc_msg = IbcPacketReceiveMsg::new(recv_packet.clone(), relayer); @@ -2149,7 +2151,7 @@ fn proper_checks_on_execute_cw20_transfer_back_to_remote() { ExecuteMsg::IncreaseChannelBalanceIbcReceive { dest_channel_id: local_channel.to_string(), ibc_denom: ibc_denom.clone(), - amount: Uint128::from(amount), + amount, local_receiver: custom_addr.to_string(), }, ) @@ -2176,10 +2178,7 @@ fn proper_checks_on_execute_cw20_transfer_back_to_remote() { assert_eq!(timeout, expected_timeout.into()); assert_eq!(channel_id.as_str(), local_channel); let msg: Ics20Packet = from_binary(&data).unwrap(); - assert_eq!( - msg.amount, - Uint128::new(1234567).sub(Uint128::from(fee_amount)) - ); + assert_eq!(msg.amount, Uint128::new(1234567).sub(fee_amount)); assert_eq!( msg.denom.as_str(), get_key_ics20_ibc_denom(CONTRACT_PORT, local_channel, denom) @@ -2206,15 +2205,15 @@ fn proper_checks_on_execute_cw20_transfer_back_to_remote() { assert_eq!( chan.balances, vec![Amount::native( - fee_amount.u128(), - &get_key_ics20_ibc_denom(CONTRACT_PORT, local_channel, denom) + fee_amount, + get_key_ics20_ibc_denom(CONTRACT_PORT, local_channel, denom) )] ); assert_eq!( chan.total_sent, vec![Amount::native( amount, - &get_key_ics20_ibc_denom(CONTRACT_PORT, local_channel, denom) + get_key_ics20_ibc_denom(CONTRACT_PORT, local_channel, denom) )] ); @@ -2471,10 +2470,10 @@ fn test_increase_channel_balance_ibc_receive() { let channel_state = CHANNEL_REVERSE_STATE .load(deps.as_ref().storage, (local_channel_id, &ibc_denom_keys)) .unwrap(); - assert_eq!(channel_state.outstanding, amount.clone()); - assert_eq!(channel_state.total_sent, amount.clone()); + assert_eq!(channel_state.outstanding, amount); + assert_eq!(channel_state.total_sent, amount); let reply_args = REPLY_ARGS.load(deps.as_ref().storage).unwrap(); - assert_eq!(reply_args.amount, amount.clone()); + assert_eq!(reply_args.amount, amount); assert_eq!(reply_args.channel, local_channel_id); assert_eq!(reply_args.denom, ibc_denom_keys.to_string()); assert_eq!(reply_args.local_receiver, local_receiver.to_string()); @@ -2562,7 +2561,7 @@ fn test_reduce_channel_balance_ibc_receive() { assert_eq!(channel_state.outstanding, Uint128::zero()); assert_eq!(channel_state.total_sent, Uint128::from(10u128)); let reply_args = REPLY_ARGS.load(deps.as_ref().storage).unwrap(); - assert_eq!(reply_args.amount, amount.clone()); + assert_eq!(reply_args.amount, amount); assert_eq!(reply_args.channel, local_channel_id); assert_eq!(reply_args.denom, ibc_denom_keys); assert_eq!(reply_args.local_receiver, local_receiver.to_string()); diff --git a/packages/cw20-ics20-msg/Cargo.toml b/packages/cw20-ics20-msg/Cargo.toml index 762b9d7..1f579da 100644 --- a/packages/cw20-ics20-msg/Cargo.toml +++ b/packages/cw20-ics20-msg/Cargo.toml @@ -11,15 +11,14 @@ documentation = "https://docs.orai.io" readme = "README.md" [dependencies] -cosmwasm-schema = "=1.2" -cosmwasm-std = { version = "=1.2", default-features = false } -cw-storage-plus = "1.0.1" -cw20 = "1.0.1" -bech32 = "0.8.1" +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw20 = { workspace = true } oraiswap = { workspace = true } +bech32 = "0.8.1" anybuf = "0.3.0" [dev-dependencies] -cw-multi-test = "0.16.0" cosmwasm-testing-util = { workspace = true } -cosmwasm-vm = { version = "=1.2" } +cosmwasm-vm = { workspace = true } diff --git a/packages/cw20-ics20-msg/src/amount.rs b/packages/cw20-ics20-msg/src/amount.rs index 3e44a52..7e45dac 100644 --- a/packages/cw20-ics20-msg/src/amount.rs +++ b/packages/cw20-ics20-msg/src/amount.rs @@ -1,9 +1,9 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - to_binary, Api, BankMsg, Binary, Coin, CosmosMsg, Decimal, StdError, StdResult, Uint128, + to_binary, Addr, Api, BankMsg, Binary, Coin, CosmosMsg, Decimal, StdError, StdResult, Uint128, WasmMsg, }; -use cw20::{Cw20Coin, Cw20ExecuteMsg}; +use cw20::{Cw20CoinVerified, Cw20ExecuteMsg}; use oraiswap::asset::AssetInfo; use std::convert::TryInto; @@ -11,37 +11,34 @@ use std::convert::TryInto; pub enum Amount { Native(Coin), // FIXME? USe Cw20CoinVerified, and validate cw20 addresses - Cw20(Cw20Coin), + Cw20(Cw20CoinVerified), } impl Amount { pub fn from_parts(denom: String, amount: Uint128) -> Self { - if denom.starts_with("cw20:") { - let address = denom.get(5..).unwrap().into(); - Amount::Cw20(Cw20Coin { address, amount }) + if let Some(address) = denom.strip_prefix("cw20:") { + Amount::Cw20(Cw20CoinVerified { + address: Addr::unchecked(address), + amount, + }) } else { Amount::Native(Coin { denom, amount }) } } - pub fn cw20(amount: u128, addr: &str) -> Self { - Amount::Cw20(Cw20Coin { - address: addr.into(), - amount: Uint128::new(amount), - }) + // return struct should copy instead of ref + pub fn cw20(amount: Uint128, address: Addr) -> Self { + Amount::Cw20(Cw20CoinVerified { address, amount }) } - pub fn native(amount: u128, denom: &str) -> Self { - Amount::Native(Coin { - denom: denom.to_string(), - amount: Uint128::new(amount), - }) + pub fn native(amount: Uint128, denom: String) -> Self { + Amount::Native(Coin { denom, amount }) } pub fn into_asset_info(&self, api: &dyn Api) -> StdResult { match self { Amount::Native(coin) => Ok(AssetInfo::NativeToken { - denom: coin.denom.to_owned(), + denom: coin.denom.clone(), }), Amount::Cw20(cw20_coin) => Ok(AssetInfo::Token { contract_addr: api.addr_validate(cw20_coin.address.as_str())?, @@ -75,11 +72,10 @@ impl Amount { /// convert the amount into u64 pub fn u64_amount(&self) -> Result { - Ok(self - .amount() + self.amount() .u128() .try_into() - .map_err(|_| StdError::generic_err("error casting to u64 from u128".to_string()))?) + .map_err(|_| StdError::generic_err("error casting to u64 from u128".to_string())) } pub fn is_empty(&self) -> bool { @@ -111,7 +107,7 @@ impl Amount { }; WasmMsg::Execute { - contract_addr: coin.address, + contract_addr: coin.address.to_string(), msg: to_binary(&msg_cw20).unwrap(), funds: vec![], } @@ -123,11 +119,10 @@ impl Amount { impl Amount { pub fn checked_add(&self, add_amount: Uint128) -> Self { + let amount = self.amount(); Amount::from_parts( self.denom(), - self.amount() - .checked_add(add_amount) - .unwrap_or(self.amount()), + amount.checked_add(add_amount).unwrap_or(amount), ) } } @@ -191,14 +186,14 @@ mod tests { #[test] pub fn test_into_asset_info() { let deps = mock_dependencies(); - let amount = Amount::cw20(1u128, "addr"); + let amount = Amount::cw20(1u128.into(), Addr::unchecked("addr")); assert_eq!( amount.into_asset_info(deps.as_ref().api).unwrap(), AssetInfo::Token { contract_addr: Addr::unchecked("addr") } ); - let amount = Amount::native(1u128, "native"); + let amount = Amount::native(1u128.into(), "native".to_string()); assert_eq!( amount.into_asset_info(deps.as_ref().api).unwrap(), AssetInfo::NativeToken { @@ -210,13 +205,13 @@ mod tests { #[test] pub fn test_checked_add() { assert_eq!( - Amount::cw20(1u128, "addr").checked_add(Uint128::one()), - Amount::cw20(2u128, "addr") + Amount::cw20(1u128.into(), Addr::unchecked("addr")).checked_add(Uint128::one()), + Amount::cw20(2u128.into(), Addr::unchecked("addr")) ); assert_eq!( - Amount::cw20(1u128, "addr").checked_add(Uint128::MAX), - Amount::cw20(1u128, "addr") + Amount::cw20(1u128.into(), Addr::unchecked("addr")).checked_add(Uint128::MAX), + Amount::cw20(1u128.into(), Addr::unchecked("addr")) ) } } diff --git a/packages/cw20-ics20-msg/src/converter.rs b/packages/cw20-ics20-msg/src/converter.rs index 5249d41..8073696 100644 --- a/packages/cw20-ics20-msg/src/converter.rs +++ b/packages/cw20-ics20-msg/src/converter.rs @@ -25,15 +25,14 @@ impl ConverterController { querier: &QuerierWrapper, source_info: &AssetInfo, ) -> Option { - match querier.query_wasm_smart( - self.addr(), - &converter::QueryMsg::ConvertInfo { - asset_info: source_info.to_owned(), - }, - ) { - Ok(val) => return val, - Err(_) => return None, - }; + querier + .query_wasm_smart( + self.addr(), + &converter::QueryMsg::ConvertInfo { + asset_info: source_info.to_owned(), + }, + ) + .ok() } pub fn process_convert( @@ -44,15 +43,13 @@ impl ConverterController { convert_type: ConvertType, ) -> StdResult<(Option, Asset)> { match self.converter_info(querier, source_info) { - None => { - return Ok(( - None, - Asset { - info: source_info.to_owned(), - amount, - }, - )) - } + None => Ok(( + None, + Asset { + info: source_info.clone(), + amount, + }, + )), Some(converter_info) => match convert_type { ConvertType::FromSource => { let return_asset = Asset { @@ -60,11 +57,14 @@ impl ConverterController { amount: amount * converter_info.token_ratio.ratio, }; - let msg = match source_info.to_owned() { + let msg = match source_info { AssetInfo::NativeToken { denom } => CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: self.addr(), msg: to_binary(&converter::ExecuteMsg::Convert {})?, - funds: vec![Coin { denom, amount }], + funds: vec![Coin { + denom: denom.clone(), + amount, + }], }), AssetInfo::Token { contract_addr } => CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: contract_addr.to_string(), @@ -77,7 +77,7 @@ impl ConverterController { }), }; - return Ok((Some(msg), return_asset)); + Ok((Some(msg), return_asset)) } ConvertType::ToSource => { let return_asset = Asset { @@ -106,10 +106,10 @@ impl ConverterController { }), }; - return Ok((Some(msg), return_asset)); + Ok((Some(msg), return_asset)) } }, - }; + } } } @@ -186,6 +186,7 @@ mod tests { SystemResult::Ok(ContractResult::Ok( to_binary(&ConvertInfoResponse { token_ratio: TokenRatio { + is_mint_burn: false, info: AssetInfo::Token { contract_addr: Addr::unchecked("orai123"), }, @@ -252,6 +253,7 @@ mod tests { res, Some(ConvertInfoResponse { token_ratio: TokenRatio { + is_mint_burn: false, info: AssetInfo::Token { contract_addr: Addr::unchecked("orai123"), }, diff --git a/packages/cw20-ics20-msg/src/helper.rs b/packages/cw20-ics20-msg/src/helper.rs index 1a561af..9d501d2 100644 --- a/packages/cw20-ics20-msg/src/helper.rs +++ b/packages/cw20-ics20-msg/src/helper.rs @@ -2,20 +2,20 @@ use cosmwasm_std::{Api, StdError, StdResult}; use oraiswap::asset::AssetInfo; pub fn get_prefix_decode_bech32(address: &str) -> StdResult { - let decode_result = bech32::decode(address); - if decode_result.is_err() { + let Ok((prefix, _, _)) = bech32::decode(address) else { return Err(StdError::generic_err(format!( "Cannot decode remote sender: {}", address ))); - } - Ok(decode_result.unwrap().0) + }; + + Ok(prefix) } -pub fn parse_asset_info_denom(asset_info: AssetInfo) -> String { +pub fn parse_asset_info_denom(asset_info: &AssetInfo) -> String { match asset_info { - AssetInfo::Token { contract_addr } => format!("cw20:{}", contract_addr.to_string()), - AssetInfo::NativeToken { denom } => denom, + AssetInfo::Token { contract_addr } => format!("cw20:{}", contract_addr.as_str()), + AssetInfo::NativeToken { denom } => denom.to_string(), } } @@ -34,21 +34,19 @@ pub fn denom_to_asset_info(api: &dyn Api, denom: &str) -> AssetInfo { } pub fn to_orai_bridge_address(address: &str) -> StdResult { - let decode_result = bech32::decode(address).map_err(|_| { - StdError::generic_err(format!( + let Ok((_, data, _)) = bech32::decode(address) else { + return Err(StdError::generic_err(format!( "Cannot decode sender address in to_orai_bridge_address: {}", address - )) - })?; - let oraib_address = - bech32::encode("oraib", decode_result.1, bech32::Variant::Bech32).map_err(|_| { - StdError::generic_err(format!( - "Cannot encode sender address to oraibridge address in to_orai_bridge_address: {}", - address - )) - })?; + ))); + }; - Ok(oraib_address) + bech32::encode("oraib", data, bech32::Variant::Bech32).map_err(|_| { + StdError::generic_err(format!( + "Cannot encode sender address to oraibridge address in to_orai_bridge_address: {}", + address + )) + }) } #[cfg(test)] diff --git a/packages/cw20-ics20-msg/src/ibc_hooks.rs b/packages/cw20-ics20-msg/src/ibc_hooks.rs index 4e50e97..99570e6 100644 --- a/packages/cw20-ics20-msg/src/ibc_hooks.rs +++ b/packages/cw20-ics20-msg/src/ibc_hooks.rs @@ -19,15 +19,8 @@ pub struct IbcHooksUniversalSwap { impl IbcHooksUniversalSwap { pub fn from_binary(api: &dyn Api, value: &Binary) -> StdResult { - let deserialized = match Bufany::deserialize(&value) { - Ok(val) => val, - Err(err) => { - return Err(StdError::generic_err(format!( - "Error on deserialize: {:?}", - err - ))) - } - }; + let deserialized = Bufany::deserialize(value) + .map_err(|err| StdError::generic_err(format!("Error on deserialize: {:?}", err)))?; let receiver = api .addr_humanize( @@ -41,6 +34,13 @@ impl IbcHooksUniversalSwap { .string(2) .ok_or_else(|| StdError::generic_err("Error on deserialize destination_receiver"))?; + // Always require destination.receiver + if destination_receiver.is_empty() { + return Err(StdError::generic_err( + "Require destination receiver in memo", + )); + } + let destination_channel = deserialized .string(3) .ok_or_else(|| StdError::generic_err("Error on deserialize destination_channel"))?; @@ -49,22 +49,12 @@ impl IbcHooksUniversalSwap { .string(4) .ok_or_else(|| StdError::generic_err("Error on deserialize destination_denom"))?; - let bridge_receiver = match to_orai_bridge_address(&receiver) { - Ok(val) => val, - Err(err) => { - return Err(StdError::generic_err(format!( - "Error on convert to orai bridge address: {:?}", - err - ))) - } - }; - - // Always require destination.receiver - if destination_receiver.is_empty() { - return Err(StdError::generic_err( - "Require destination receiver in memo", - )); - } + let bridge_receiver = to_orai_bridge_address(&receiver).map_err(|err| { + StdError::generic_err(format!( + "Error on convert to orai bridge address: {:?}", + err + )) + })?; Ok(Self { receiver: receiver.clone(), diff --git a/packages/cw20-ics20-msg/src/receiver.rs b/packages/cw20-ics20-msg/src/receiver.rs index 33f4801..169bf75 100644 --- a/packages/cw20-ics20-msg/src/receiver.rs +++ b/packages/cw20-ics20-msg/src/receiver.rs @@ -24,16 +24,9 @@ impl Default for DestinationInfo { impl DestinationInfo { // destination string format: /: - pub fn from_str(value: &str) -> Self { - let (destination, denom) = match value.split_once(':') { - Some((destination, denom)) => (destination, denom), - None => (value, ""), - }; - - let (channel, receiver) = match destination.split_once('/') { - Some((channel, receiver)) => (channel, receiver), - None => ("", destination), - }; + pub fn parse_str(value: &str) -> Self { + let (destination, denom) = value.split_once(':').unwrap_or((value, "")); + let (channel, receiver) = destination.split_once('/').unwrap_or(("", destination)); Self { receiver: receiver.to_string(), @@ -47,15 +40,8 @@ impl DestinationInfo { } fn from_binary(value: &Binary) -> StdResult { - let deserialized = match Bufany::deserialize(&value) { - Ok(val) => val, - Err(err) => { - return Err(StdError::generic_err(format!( - "Error on deserialize: {:?}", - err - ))) - } - }; + let deserialized = Bufany::deserialize(value) + .map_err(|err| StdError::generic_err(format!("Error on deserialize: {:?}", err)))?; let destination_receiver = deserialized .string(1) @@ -95,29 +81,23 @@ impl DestinationInfo { } pub fn is_receiver_cosmos_based(&self) -> bool { - match get_prefix_decode_bech32(&self.receiver).ok() { - None => false, - Some(prefix) => { - if prefix.is_empty() { - return false; - } - true - } - } + !get_prefix_decode_bech32(&self.receiver) + .unwrap_or_default() // empty string if error + .is_empty() } } #[test] fn test_is_evm_based() { - let d1 = DestinationInfo::from_str("cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz"); + let d1 = DestinationInfo::parse_str("cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz"); assert_eq!(false, d1.is_receiver_evm_based().0); - let d1 = DestinationInfo::from_str("0x3C5C6b570C1DA469E8B24A2E8Ed33c278bDA3222"); + let d1 = DestinationInfo::parse_str("0x3C5C6b570C1DA469E8B24A2E8Ed33c278bDA3222"); // false here because we need the evm-prefix as well! assert_eq!(false, d1.is_receiver_evm_based().0); - let d1 = DestinationInfo::from_str("foobar0x3C5C6b570C1DA469E8B24A2E8Ed33c278b"); + let d1 = DestinationInfo::parse_str("foobar0x3C5C6b570C1DA469E8B24A2E8Ed33c278b"); // false here because of the wrong eth address, not enough in length assert_eq!(false, d1.is_receiver_evm_based().0); - let d1 = DestinationInfo::from_str( + let d1 = DestinationInfo::parse_str( "channel-15/foobar0x3C5C6b570C1DA469E8B24A2E8Ed33c278bDA3222:usdt", ); let (is_evm_based, prefix) = d1.is_receiver_evm_based(); @@ -131,55 +111,56 @@ fn test_is_evm_based() { #[test] fn test_is_cosmos_based() { - let d1 = DestinationInfo::from_str("foo"); + let d1 = DestinationInfo::parse_str("foo"); assert_eq!(false, d1.is_receiver_cosmos_based()); - let d1 = DestinationInfo::from_str("channel-15/foo:usdt"); + let d1 = DestinationInfo::parse_str("channel-15/foo:usdt"); assert_eq!(false, d1.is_receiver_cosmos_based()); let d1 = - DestinationInfo::from_str("channel-15/cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz:usdt"); + DestinationInfo::parse_str("channel-15/cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz:usdt"); let result = d1.is_receiver_cosmos_based(); assert_eq!(true, result); let d1 = - DestinationInfo::from_str("channel-15/akash1g4h64yjt0fvzv5v2j8tyfnpe5kmnetejjpn5xp:usdt"); + DestinationInfo::parse_str("channel-15/akash1g4h64yjt0fvzv5v2j8tyfnpe5kmnetejjpn5xp:usdt"); let result = d1.is_receiver_cosmos_based(); assert_eq!(true, result); - let d1 = - DestinationInfo::from_str("channel-15/bostrom1g4h64yjt0fvzv5v2j8tyfnpe5kmnetejuf2qpu:usdt"); + let d1 = DestinationInfo::parse_str( + "channel-15/bostrom1g4h64yjt0fvzv5v2j8tyfnpe5kmnetejuf2qpu:usdt", + ); let result = d1.is_receiver_cosmos_based(); assert_eq!(true, result); - let d1 = DestinationInfo::from_str("channel-124/cosmos1g4h64yjt0fvzv5v2j8tyfnpe5kmnetejl67nlm:orai17l2zk3arrx0a0fyuneyx8raln68622a2lrsz8ph75u7gw9tgz3esayqryf"); + let d1 = DestinationInfo::parse_str("channel-124/cosmos1g4h64yjt0fvzv5v2j8tyfnpe5kmnetejl67nlm:orai17l2zk3arrx0a0fyuneyx8raln68622a2lrsz8ph75u7gw9tgz3esayqryf"); let result = d1.is_receiver_cosmos_based(); assert_eq!(true, result); } #[test] -fn test_destination_info_from_str() { - let d1 = DestinationInfo::from_str("cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz"); +fn test_destination_info_parse_str() { + let d1 = DestinationInfo::parse_str("cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz"); assert_eq!(d1.destination_channel, ""); assert_eq!(d1.receiver, "cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz"); assert_eq!(d1.destination_denom, ""); - let d1 = DestinationInfo::from_str("cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz:foo"); + let d1 = DestinationInfo::parse_str("cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz:foo"); assert_eq!(d1.destination_channel, ""); assert_eq!(d1.receiver, "cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz"); assert_eq!(d1.destination_denom, "foo"); - let d1 = DestinationInfo::from_str("foo/cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz"); + let d1 = DestinationInfo::parse_str("foo/cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz"); assert_eq!(d1.destination_channel, "foo"); assert_eq!(d1.receiver, "cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz"); assert_eq!(d1.destination_denom, ""); - let d1 = DestinationInfo::from_str("foo/cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz:bar"); + let d1 = DestinationInfo::parse_str("foo/cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz:bar"); assert_eq!(d1.destination_channel, "foo"); assert_eq!(d1.receiver, "cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz"); assert_eq!(d1.destination_denom, "bar"); - let d1 = DestinationInfo::from_str(""); + let d1 = DestinationInfo::parse_str(""); assert_eq!(d1.destination_channel, ""); assert_eq!(d1.receiver, ""); assert_eq!(d1.destination_denom, ""); @@ -195,7 +176,7 @@ mod tests { #[test] fn test_parse_destination_info() { // swap to orai then orai to atom, then use swapped amount to transfer ibc to destination - let d1 = DestinationInfo::from_str( + let d1 = DestinationInfo::parse_str( "channel-15/cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz:atom", ); assert_eq!( @@ -207,7 +188,7 @@ mod tests { } ); // swap to orai then orai to usdt with 'to' as the receiver when swapping, then we're done - let d2 = DestinationInfo::from_str("orai14n3tx8s5ftzhlxvq0w5962v60vd82h30rha573:usdt"); + let d2 = DestinationInfo::parse_str("orai14n3tx8s5ftzhlxvq0w5962v60vd82h30rha573:usdt"); assert_eq!( d2, DestinationInfo { @@ -218,7 +199,7 @@ mod tests { ); // this case returns an error (because it has channel but no destination denom) let d3 = - DestinationInfo::from_str("channel-15/cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz"); + DestinationInfo::parse_str("channel-15/cosmos14n3tx8s5ftzhlxvq0w5962v60vd82h30sythlz"); assert_eq!( d3, DestinationInfo { @@ -227,8 +208,9 @@ mod tests { destination_denom: "".to_string() } ); - let d4 = - DestinationInfo::from_str("trx-mainnet0x73Ddc880916021EFC4754Cb42B53db6EAB1f9D64:usdt"); + let d4 = DestinationInfo::parse_str( + "trx-mainnet0x73Ddc880916021EFC4754Cb42B53db6EAB1f9D64:usdt", + ); assert_eq!( d4, DestinationInfo { @@ -238,7 +220,7 @@ mod tests { } ); - let d5 = DestinationInfo::from_str("orai14n3tx8s5ftzhlxvq0w5962v60vd82h30rha573"); + let d5 = DestinationInfo::parse_str("orai14n3tx8s5ftzhlxvq0w5962v60vd82h30rha573"); assert_eq!( d5, DestinationInfo { @@ -248,7 +230,7 @@ mod tests { } ); - let d6 = DestinationInfo::from_str( + let d6 = DestinationInfo::parse_str( "channel-5/trx-mainnet0x73Ddc880916021EFC4754Cb42B53db6EAB1f9D64:usdt", ); assert_eq!( @@ -260,7 +242,7 @@ mod tests { } ); // ibc hash case - let d7 = DestinationInfo::from_str("channel-5/trx-mainnet0x73Ddc880916021EFC4754Cb42B53db6EAB1f9D64:ibc/A2E2EEC9057A4A1C2C0A6A4C78B0239118DF5F278830F50B4A6BDD7A66506B78"); + let d7 = DestinationInfo::parse_str("channel-5/trx-mainnet0x73Ddc880916021EFC4754Cb42B53db6EAB1f9D64:ibc/A2E2EEC9057A4A1C2C0A6A4C78B0239118DF5F278830F50B4A6BDD7A66506B78"); assert_eq!( d7, DestinationInfo { @@ -271,7 +253,7 @@ mod tests { .to_string() } ); - let d8 = DestinationInfo::from_str("channel-124/cosmos1g4h64yjt0fvzv5v2j8tyfnpe5kmnetejl67nlm:orai17l2zk3arrx0a0fyuneyx8raln68622a2lrsz8ph75u7gw9tgz3esayqryf"); + let d8 = DestinationInfo::parse_str("channel-124/cosmos1g4h64yjt0fvzv5v2j8tyfnpe5kmnetejl67nlm:orai17l2zk3arrx0a0fyuneyx8raln68622a2lrsz8ph75u7gw9tgz3esayqryf"); assert_eq!( d8, DestinationInfo { diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..da04fea --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "1.76" +components = ["rustfmt", "rust-src", "clippy"] +targets = ["wasm32-unknown-unknown"] +profile = "minimal" diff --git a/simulate-tests/.env.example b/simulate-tests/.env.example index b8eb534..4bea07e 100644 --- a/simulate-tests/.env.example +++ b/simulate-tests/.env.example @@ -1 +1,2 @@ +# cwtools build ../contracts/cw-ics20-latest ICS20_LATEST=../contracts/cw-ics20-latest/artifacts/cw-ics20-latest.wasm \ No newline at end of file diff --git a/simulate-tests/bridge-contract.spec.ts b/simulate-tests/bridge-contract.spec.ts index 7b37b8d..5690a44 100644 --- a/simulate-tests/bridge-contract.spec.ts +++ b/simulate-tests/bridge-contract.spec.ts @@ -1,152 +1,116 @@ -import { Event, toBinary } from "@cosmjs/cosmwasm-stargate"; -import { Coin, coins, coin } from "@cosmjs/proto-signing"; -import { - CWSimulateApp, - GenericError, - IbcOrder, - IbcPacket, - SimulateCosmWasmClient, -} from "@oraichain/cw-simulate"; -import { Ok } from "ts-results"; -import bech32 from "bech32"; -import { readFileSync } from "fs"; -import { - OraiswapFactoryClient, - OraiswapRouterClient, - OraiswapTokenClient, - OraiswapPairClient, - OraiswapOracleClient, -} from "@oraichain/oraidex-contracts-sdk"; +import { Event, toBinary } from '@cosmjs/cosmwasm-stargate'; +import { Coin, coins, coin } from '@cosmjs/proto-signing'; +import { CWSimulateApp, GenericError, IbcOrder, IbcPacket, SimulateCosmWasmClient } from '@oraichain/cw-simulate'; +import { Ok } from 'ts-results'; +import bech32 from 'bech32'; +import { readFileSync } from 'fs'; +import { OraiswapFactoryClient, OraiswapRouterClient, OraiswapTokenClient, OraiswapPairClient, OraiswapOracleClient } from '@oraichain/oraidex-contracts-sdk'; // import { CwIcs20LatestClient } from "@oraichain/common-contracts-sdk"; -import { CwIcs20LatestClient } from "./contracts-sdk/CwIcs20Latest.client"; -import * as oraidexArtifacts from "@oraichain/oraidex-contracts-build"; -import { FungibleTokenPacketData } from "cosmjs-types/ibc/applications/transfer/v2/packet"; -import { - deployIcs20Token, - deployToken, - senderAddress as oraiSenderAddress, - senderAddress, -} from "./common"; -import { oraib2oraichain, toAmount } from "@oraichain/oraidex-common"; -import { ORAI } from "@oraichain/oraidex-common"; -import { - AssetInfo, - TransferBackMsg, -} from "@oraichain/common-contracts-sdk/build/CwIcs20Latest.types"; -import { toDisplay } from "@oraichain/oraidex-common"; -import { parseToIbcWasmMemo } from "./proto-gen"; +import { CwIcs20LatestClient } from './contracts-sdk/CwIcs20Latest.client'; +import * as oraidexArtifacts from '@oraichain/oraidex-contracts-build'; +import { FungibleTokenPacketData } from 'cosmjs-types/ibc/applications/transfer/v2/packet'; +import { deployIcs20Token, deployToken, senderAddress as oraiSenderAddress, senderAddress } from './common'; +import { oraib2oraichain, toAmount } from '@oraichain/oraidex-common'; +import { ORAI } from '@oraichain/oraidex-common'; +import { AssetInfo, TransferBackMsg } from '@oraichain/common-contracts-sdk/build/CwIcs20Latest.types'; +import { toDisplay } from '@oraichain/oraidex-common'; +import { parseToIbcWasmMemo } from './proto-gen'; let cosmosChain: CWSimulateApp; // oraichain support cosmwasm let oraiClient: SimulateCosmWasmClient; -const bobAddress = "orai1ur2vsjrjarygawpdwtqteaazfchvw4fg6uql76"; -const bobAddressEth = "0x8754032Ac7966A909e2E753308dF56bb08DabD69"; -const bridgeReceiver = "tron-testnet0x3C5C6b570C1DA469E8B24A2E8Ed33c278bDA3222"; -const routerContractAddress = "placeholder"; // we will update the contract config later when we need to deploy the actual router contract -const converterContractAddress = "converter"; // we will update the contract config later when we need to deploy the actual converter contract -const cosmosSenderAddress = bech32.encode( - "cosmos", - bech32.decode(oraiSenderAddress).words -); -const relayerAddress = "orai1704r4dhuwdqvt7vs35m0360py6ep6cwwxeyfxn"; -const oraibridgeSenderAddress = bech32.encode( - "oraib", - bech32.decode(oraiSenderAddress).words -); +const bobAddress = 'orai1ur2vsjrjarygawpdwtqteaazfchvw4fg6uql76'; +const bobAddressEth = '0x8754032Ac7966A909e2E753308dF56bb08DabD69'; +const bridgeReceiver = 'tron-testnet0x3C5C6b570C1DA469E8B24A2E8Ed33c278bDA3222'; +const routerContractAddress = 'placeholder'; // we will update the contract config later when we need to deploy the actual router contract +const converterContractAddress = 'converter'; // we will update the contract config later when we need to deploy the actual converter contract +const cosmosSenderAddress = bech32.encode('cosmos', bech32.decode(oraiSenderAddress).words); +const relayerAddress = 'orai1704r4dhuwdqvt7vs35m0360py6ep6cwwxeyfxn'; +const oraibridgeSenderAddress = bech32.encode('oraib', bech32.decode(oraiSenderAddress).words); console.log({ cosmosSenderAddress }); -const ibcTransferAmount = "100000000"; -const initialBalanceAmount = "10000000000000"; +const ibcTransferAmount = '100000000'; +const initialBalanceAmount = '10000000000000'; -describe.only("IBCModule", () => { +describe.only('IBCModule', () => { let oraiPort: string; - let oraiIbcDenom: string = - "tron-testnet0xA325Ad6D9c92B55A3Fc5aD7e412B1518F96441C0"; - let airiIbcDenom: string = - "tron-testnet0x7e2A35C746F2f7C240B664F1Da4DD100141AE71F"; - let usdtIbcDenom: string = - "tron-testnet0xdac17f958d2ee523a2206206994597c13d831ec7"; - let AtomDenom = - "ibc/A2E2EEC9057A4A1C2C0A6A4C78B0239118DF5F278830F50B4A6BDD7A66506B78"; - let atomChannel = "channel-15"; - let cosmosPort: string = "transfer"; - let channel = "channel-0"; + let oraiIbcDenom: string = 'tron-testnet0xA325Ad6D9c92B55A3Fc5aD7e412B1518F96441C0'; + let airiIbcDenom: string = 'tron-testnet0x7e2A35C746F2f7C240B664F1Da4DD100141AE71F'; + let usdtIbcDenom: string = 'tron-testnet0xdac17f958d2ee523a2206206994597c13d831ec7'; + let AtomDenom = 'ibc/A2E2EEC9057A4A1C2C0A6A4C78B0239118DF5F278830F50B4A6BDD7A66506B78'; + let atomChannel = 'channel-15'; + let cosmosPort: string = 'transfer'; + let channel = 'channel-0'; let ics20Contract: CwIcs20LatestClient; let airiToken: OraiswapTokenClient; let packetData = { src: { port_id: cosmosPort, - channel_id: channel, + channel_id: channel }, dest: { port_id: oraiPort, - channel_id: channel, + channel_id: channel }, sequence: 27, timeout: { block: { revision: 1, - height: 12345678, - }, - }, + height: 12345678 + } + } }; beforeEach(async () => { // reset state for every test cosmosChain = new CWSimulateApp({ - chainId: "cosmoshub-4", - bech32Prefix: "cosmos", + chainId: 'cosmoshub-4', + bech32Prefix: 'cosmos' }); oraiClient = new SimulateCosmWasmClient({ - chainId: "Oraichain", + chainId: 'Oraichain', bech32Prefix: ORAI, - metering: process.env.METERING === "true", + metering: process.env.METERING === 'true' }); ics20Contract = await deployIcs20Token(oraiClient, { swap_router_contract: routerContractAddress, - converter_contract: converterContractAddress, + converter_contract: converterContractAddress }); - oraiPort = "wasm." + ics20Contract.contractAddress; + oraiPort = 'wasm.' + ics20Contract.contractAddress; packetData.dest.port_id = oraiPort; // init cw20 AIRI token airiToken = await deployToken(oraiClient, { decimals: 6, - symbol: "AIRI", - name: "Airight token", + symbol: 'AIRI', + name: 'Airight token', initial_balances: [ { address: ics20Contract.contractAddress, - amount: initialBalanceAmount, - }, - ], + amount: initialBalanceAmount + } + ] }); // init ibc channel between two chains - oraiClient.app.ibc.relay( - channel, - oraiPort, - channel, - cosmosPort, - cosmosChain - ); + oraiClient.app.ibc.relay(channel, oraiPort, channel, cosmosPort, cosmosChain); await cosmosChain.ibc.sendChannelOpen({ open_init: { channel: { counterparty_endpoint: { port_id: oraiPort, - channel_id: channel, + channel_id: channel }, endpoint: { port_id: cosmosPort, - channel_id: channel, + channel_id: channel }, order: IbcOrder.Unordered, - version: "ics20-1", - connection_id: "connection-0", - }, - }, + version: 'ics20-1', + connection_id: 'connection-0' + } + } }); await cosmosChain.ibc.sendChannelConnect({ @@ -154,114 +118,105 @@ describe.only("IBCModule", () => { channel: { counterparty_endpoint: { port_id: oraiPort, - channel_id: channel, + channel_id: channel }, endpoint: { port_id: cosmosPort, - channel_id: channel, + channel_id: channel }, order: IbcOrder.Unordered, - version: "ics20-1", - connection_id: "connection-0", + version: 'ics20-1', + connection_id: 'connection-0' }, - counterparty_version: "ics20-1", - }, + counterparty_version: 'ics20-1' + } }); cosmosChain.ibc.addMiddleWare((msg, app) => { const data = msg.data.packet as IbcPacket; if (Number(data.timeout.timestamp) < cosmosChain.time) { - throw new GenericError("timeout at " + data.timeout.timestamp); + throw new GenericError('timeout at ' + data.timeout.timestamp); } }); // topup - oraiClient.app.bank.setBalance( - ics20Contract.contractAddress, - coins(initialBalanceAmount, ORAI) - ); + oraiClient.app.bank.setBalance(ics20Contract.contractAddress, coins(initialBalanceAmount, ORAI)); await ics20Contract.updateMappingPair({ localAssetInfo: { token: { - contract_addr: airiToken.contractAddress, - }, + contract_addr: airiToken.contractAddress + } }, localAssetInfoDecimals: 6, denom: airiIbcDenom, remoteDecimals: 6, - localChannelId: channel, + localChannelId: channel }); }); - it("demo-getting-channel-state-ibc-wasm-should-increase-balances-and-total-sent", async () => { + it('demo-getting-channel-state-ibc-wasm-should-increase-balances-and-total-sent', async () => { // fixture. Setup everything from the ics 20 contract to ibc relayer const oraiClient = new SimulateCosmWasmClient({ - chainId: "Oraichain", + chainId: 'Oraichain', bech32Prefix: ORAI, - metering: process.env.METERING === "true", + metering: process.env.METERING === 'true' }); const ics20Contract = await deployIcs20Token(oraiClient, { swap_router_contract: routerContractAddress, - converter_contract: converterContractAddress, + converter_contract: converterContractAddress }); - const oraiPort = "wasm." + ics20Contract.contractAddress; + const oraiPort = 'wasm.' + ics20Contract.contractAddress; let newPacketData = { src: { port_id: cosmosPort, - channel_id: channel, + channel_id: channel }, dest: { port_id: oraiPort, - channel_id: channel, + channel_id: channel }, sequence: 27, timeout: { block: { revision: 1, - height: 12345678, - }, - }, + height: 12345678 + } + } }; newPacketData.dest.port_id = oraiPort; // init cw20 AIRI token const airiToken = await deployToken(oraiClient, { decimals: 6, - symbol: "AIRI", - name: "Airight token", + symbol: 'AIRI', + name: 'Airight token', initial_balances: [ { address: ics20Contract.contractAddress, - amount: initialBalanceAmount, - }, - ], + amount: initialBalanceAmount + } + ] }); // init ibc channel between two chains - oraiClient.app.ibc.relay( - channel, - oraiPort, - channel, - cosmosPort, - cosmosChain - ); + oraiClient.app.ibc.relay(channel, oraiPort, channel, cosmosPort, cosmosChain); await cosmosChain.ibc.sendChannelOpen({ open_init: { channel: { counterparty_endpoint: { port_id: oraiPort, - channel_id: channel, + channel_id: channel }, endpoint: { port_id: cosmosPort, - channel_id: channel, + channel_id: channel }, order: IbcOrder.Unordered, - version: "ics20-1", - connection_id: "connection-0", - }, - }, + version: 'ics20-1', + connection_id: 'connection-0' + } + } }); await cosmosChain.ibc.sendChannelConnect({ @@ -269,37 +224,37 @@ describe.only("IBCModule", () => { channel: { counterparty_endpoint: { port_id: oraiPort, - channel_id: channel, + channel_id: channel }, endpoint: { port_id: cosmosPort, - channel_id: channel, + channel_id: channel }, order: IbcOrder.Unordered, - version: "ics20-1", - connection_id: "connection-0", + version: 'ics20-1', + connection_id: 'connection-0' }, - counterparty_version: "ics20-1", - }, + counterparty_version: 'ics20-1' + } }); cosmosChain.ibc.addMiddleWare((msg, app) => { const data = msg.data.packet as IbcPacket; if (Number(data.timeout.timestamp) < cosmosChain.time) { - throw new GenericError("timeout at " + data.timeout.timestamp); + throw new GenericError('timeout at ' + data.timeout.timestamp); } }); // topup await ics20Contract.updateMappingPair({ localAssetInfo: { token: { - contract_addr: airiToken.contractAddress, - }, + contract_addr: airiToken.contractAddress + } }, localAssetInfoDecimals: 6, denom: airiIbcDenom, remoteDecimals: 6, - localChannelId: channel, + localChannelId: channel }); const icsPackage: FungibleTokenPacketData = { @@ -307,15 +262,15 @@ describe.only("IBCModule", () => { denom: airiIbcDenom, receiver: bobAddress, sender: cosmosSenderAddress, - memo: "", + memo: '' }; // transfer from cosmos to oraichain, should pass. This should increase the balances & total sent await cosmosChain.ibc.sendPacketReceive({ packet: { data: toBinary(icsPackage), - ...newPacketData, + ...newPacketData }, - relayer: relayerAddress, + relayer: relayerAddress }); const { channels } = await ics20Contract.listChannels(); @@ -323,24 +278,17 @@ describe.only("IBCModule", () => { const { balances } = await ics20Contract.channel({ id: channel.id }); console.log(balances); for (let balance of balances) { - if ("native" in balance) { + if ('native' in balance) { const pairMapping = await ics20Contract.pairMapping({ - key: balance.native.denom, + key: balance.native.denom }); - const { balance: channelBalance } = - await ics20Contract.channelWithKey({ - channelId: channel.id, - denom: balance.native.denom, - }); - if ("native" in channelBalance) { - const trueBalance = toDisplay( - channelBalance.native.amount, - pairMapping.pair_mapping.remote_decimals - ); - expect(trueBalance).toEqual( - parseInt(ibcTransferAmount) / - 10 ** pairMapping.pair_mapping.remote_decimals - ); + const { balance: channelBalance } = await ics20Contract.channelWithKey({ + channelId: channel.id, + denom: balance.native.denom + }); + if ('native' in channelBalance) { + const trueBalance = toDisplay(channelBalance.native.amount, pairMapping.pair_mapping.remote_decimals); + expect(trueBalance).toEqual(parseInt(ibcTransferAmount) / 10 ** pairMapping.pair_mapping.remote_decimals); } } else { // do nothing because currently we dont have any cw20 balance in the channel @@ -354,63 +302,51 @@ describe.only("IBCModule", () => { [ { native_token: { - denom: ORAI, - }, + denom: ORAI + } }, ibcTransferAmount, oraiIbcDenom, coins(ibcTransferAmount, ORAI), - "cw-ics20-success-should-increase-native-balance-remote-to-local", - ], - [ - null, - ibcTransferAmount, - oraiIbcDenom, - [], - "cw-ics20-fail-no-pair-mapping-should-not-send-balance-remote-to-local", + 'cw-ics20-success-should-increase-native-balance-remote-to-local' ], + [null, ibcTransferAmount, oraiIbcDenom, [], 'cw-ics20-fail-no-pair-mapping-should-not-send-balance-remote-to-local'], [ { native_token: { - denom: ORAI, - }, + denom: ORAI + } }, - "10000000000001", + '10000000000001', oraiIbcDenom, [], - "cw-ics20-fail-transfer-native-fail-insufficient-funds-should-not-send-balance-remote-to-local", + 'cw-ics20-fail-transfer-native-fail-insufficient-funds-should-not-send-balance-remote-to-local' ], [ { token: { - contract_addr: "orai18cvw806fj5n7xxz06ak8vjunveeks4zzzn37cu", // has to hard-code address airi due to jest issue: https://github.com/facebook/jest/issues/6888 - }, + contract_addr: 'orai18cvw806fj5n7xxz06ak8vjunveeks4zzzn37cu' // has to hard-code address airi due to jest issue: https://github.com/facebook/jest/issues/6888 + } }, ibcTransferAmount, airiIbcDenom, - [{ amount: ibcTransferAmount, denom: "" }], - "cw-ics20-success-transfer-cw20-should-increase-cw20-balance-remote-to-local", + [{ amount: ibcTransferAmount, denom: '' }], + 'cw-ics20-success-transfer-cw20-should-increase-cw20-balance-remote-to-local' ], [ { token: { - contract_addr: "orai18cvw806fj5n7xxz06ak8vjunveeks4zzzn37cu", // has to hard-code address airi due to jest issue: https://github.com/facebook/jest/issues/6888 - }, + contract_addr: 'orai18cvw806fj5n7xxz06ak8vjunveeks4zzzn37cu' // has to hard-code address airi due to jest issue: https://github.com/facebook/jest/issues/6888 + } }, - "10000000000001", + '10000000000001', airiIbcDenom, - [{ amount: "0", denom: "" }], - "cw-ics20-fail-transfer-cw20-fail-insufficient-funds-should-not-send-balance-remote-to-local", - ], + [{ amount: '0', denom: '' }], + 'cw-ics20-fail-transfer-cw20-fail-insufficient-funds-should-not-send-balance-remote-to-local' + ] ])( - "bridge-test-cw-ics20-transfer-remote-to-local-given %j %s %s should return expected amount %j", //reference: https://jestjs.io/docs/api#1-testeachtablename-fn-timeout - async ( - assetInfo: AssetInfo, - transferAmount: string, - transferDenom: string, - expectedBalance: Coin[], - _name: string - ) => { + 'bridge-test-cw-ics20-transfer-remote-to-local-given %j %s %s should return expected amount %j', //reference: https://jestjs.io/docs/api#1-testeachtablename-fn-timeout + async (assetInfo: AssetInfo, transferAmount: string, transferDenom: string, expectedBalance: Coin[], _name: string) => { // create mapping if (assetInfo) { const pair = { @@ -418,7 +354,7 @@ describe.only("IBCModule", () => { localAssetInfoDecimals: 6, denom: transferDenom, remoteDecimals: 6, - localChannelId: channel, + localChannelId: channel }; await ics20Contract.updateMappingPair(pair); } @@ -427,19 +363,19 @@ describe.only("IBCModule", () => { denom: transferDenom, receiver: bobAddress, sender: cosmosSenderAddress, - memo: "", + memo: '' }; await cosmosChain.ibc.sendPacketReceive({ packet: { data: toBinary(icsPackage), - ...packetData, + ...packetData }, - relayer: relayerAddress, + relayer: relayerAddress }); if (assetInfo && (assetInfo as any).token) { const bobBalance = await airiToken.balance({ address: bobAddress }); - console.log("bob balance contract address: ", bobBalance); + console.log('bob balance contract address: ', bobBalance); expect(bobBalance.balance).toEqual(expectedBalance[0].amount); return; } @@ -448,124 +384,107 @@ describe.only("IBCModule", () => { } ); - it("cw-ics20-fail-outcoming-channel-larger-than-incoming-should-not-transfer-balance-local-to-remote", async () => { + it('cw-ics20-fail-outcoming-channel-larger-than-incoming-should-not-transfer-balance-local-to-remote', async () => { // now send ibc package const icsPackage: FungibleTokenPacketData = { amount: ibcTransferAmount, denom: airiIbcDenom, receiver: bobAddress, sender: cosmosSenderAddress, - memo: "", + memo: '' }; // transfer from cosmos to oraichain, should pass await cosmosChain.ibc.sendPacketReceive({ packet: { data: toBinary(icsPackage), - ...packetData, + ...packetData }, - relayer: relayerAddress, + relayer: relayerAddress }); // mint new cw20 token to bob - await airiToken.mint({ amount: "1", recipient: bobAddress }); + await airiToken.mint({ amount: '1', recipient: bobAddress }); // try to send back to cosmos from oraichain, which will fail because outcoming channel balance is greater const transferBackMsg: TransferBackMsg = { local_channel_id: channel, remote_address: cosmosSenderAddress, - remote_denom: airiIbcDenom, + remote_denom: airiIbcDenom }; airiToken.sender = bobAddress; try { const packetReceiveRes = await airiToken.send({ amount: (parseInt(ibcTransferAmount) + 1).toString(), contract: ics20Contract.contractAddress, - msg: Buffer.from(JSON.stringify(transferBackMsg)).toString("base64"), + msg: Buffer.from(JSON.stringify(transferBackMsg)).toString('base64') }); } catch (error) { - expect(error.message).toContain( - "Insufficient funds to redeem voucher on channel" - ); + expect(error.message).toContain('Insufficient funds to redeem voucher on channel'); } }); - it("cw-ics20-success-cw20-should-transfer-balance-to-ibc-wasm-contract-local-to-remote", async () => { + it('cw-ics20-success-cw20-should-transfer-balance-to-ibc-wasm-contract-local-to-remote', async () => { // now send ibc package const icsPackage: FungibleTokenPacketData = { amount: ibcTransferAmount, denom: airiIbcDenom, receiver: bobAddress, sender: cosmosSenderAddress, - memo: "", + memo: '' }; // transfer from cosmos to oraichain, should pass await cosmosChain.ibc.sendPacketReceive({ packet: { data: toBinary(icsPackage), - ...packetData, + ...packetData }, - relayer: relayerAddress, + relayer: relayerAddress }); const transferBackMsg: TransferBackMsg = { local_channel_id: channel, remote_address: cosmosSenderAddress, - remote_denom: airiIbcDenom, + remote_denom: airiIbcDenom }; airiToken.sender = bobAddress; await airiToken.send({ amount: ibcTransferAmount, contract: ics20Contract.contractAddress, - msg: Buffer.from(JSON.stringify(transferBackMsg)).toString("base64"), + msg: Buffer.from(JSON.stringify(transferBackMsg)).toString('base64') }); const ibcWasmAiriBalance = await airiToken.balance({ - address: ics20Contract.contractAddress, + address: ics20Contract.contractAddress }); expect(ibcWasmAiriBalance.balance).toEqual(initialBalanceAmount); }); it.each([ - [ - parseToIbcWasmMemo("", "", ""), - ibcTransferAmount, - "empty-memo-should-fallback-to-transfer-to-receiver", - ], - [ - parseToIbcWasmMemo(bobAddress, "", ""), - ibcTransferAmount, - "only-receiver-memo-should-fallback-to-transfer-to-receiver", - ], - [ - parseToIbcWasmMemo(bobAddress, oraib2oraichain, ""), - ibcTransferAmount, - "receiver-and-channel-memo-should-fallback-to-transfer-to-receiver", - ], - ])( - "cw-ics20-test-single-step-invalid-dest-denom-memo-remote-to-local-given %s should-get-expected-amount %s", - async (memo: string, expectedAmount: string, _name: string) => { - // now send ibc package - const icsPackage: FungibleTokenPacketData = { - amount: ibcTransferAmount, - denom: airiIbcDenom, - receiver: bobAddress, - sender: cosmosSenderAddress, - memo, - }; - // transfer from cosmos to oraichain, should pass - await cosmosChain.ibc.sendPacketReceive({ - packet: { - data: toBinary(icsPackage), - ...packetData, - }, - relayer: relayerAddress, - }); - const ibcWasmAiriBalance = await airiToken.balance({ - address: bobAddress, - }); - expect(ibcWasmAiriBalance.balance).toEqual(expectedAmount); - } - ); + [parseToIbcWasmMemo('', '', ''), ibcTransferAmount, 'empty-memo-should-fallback-to-transfer-to-receiver'], + [parseToIbcWasmMemo(bobAddress, '', ''), ibcTransferAmount, 'only-receiver-memo-should-fallback-to-transfer-to-receiver'], + [parseToIbcWasmMemo(bobAddress, oraib2oraichain, ''), ibcTransferAmount, 'receiver-and-channel-memo-should-fallback-to-transfer-to-receiver'] + ])('cw-ics20-test-single-step-invalid-dest-denom-memo-remote-to-local-given %s should-get-expected-amount %s', async (memo: string, expectedAmount: string, _name: string) => { + // now send ibc package + const icsPackage: FungibleTokenPacketData = { + amount: ibcTransferAmount, + denom: airiIbcDenom, + receiver: bobAddress, + sender: cosmosSenderAddress, + memo + }; + // transfer from cosmos to oraichain, should pass + await cosmosChain.ibc.sendPacketReceive({ + packet: { + data: toBinary(icsPackage), + ...packetData + }, + relayer: relayerAddress + }); + const ibcWasmAiriBalance = await airiToken.balance({ + address: bobAddress + }); + expect(ibcWasmAiriBalance.balance).toEqual(expectedAmount); + }); - describe("cw-ics20-test-single-step-swap-to-tokens", () => { + describe('cw-ics20-test-single-step-swap-to-tokens', () => { let factoryContract: OraiswapFactoryClient; let routerContract: OraiswapRouterClient; let usdtToken: OraiswapTokenClient; @@ -577,32 +496,14 @@ describe.only("IBCModule", () => { denom: airiIbcDenom, receiver: bobAddress, sender: cosmosSenderAddress, - memo: "", + memo: '' }; - const findWasmEvent = (events: Event[], key: string, value: string) => - events.find( - (event) => - event.type === "wasm" && - event.attributes.find( - (attr) => attr.key === key && attr.value === value - ) - ); + const findWasmEvent = (events: Event[], key: string, value: string) => events.find((event) => event.type === 'wasm' && event.attributes.find((attr) => attr.key === key && attr.value === value)); beforeEach(async () => { - assetInfos = [ - { native_token: { denom: ORAI } }, - { token: { contract_addr: airiToken.contractAddress } }, - ]; + assetInfos = [{ native_token: { denom: ORAI } }, { token: { contract_addr: airiToken.contractAddress } }]; // upload pair & lp token code id - const { codeId: pairCodeId } = await oraiClient.upload( - oraiSenderAddress, - readFileSync(oraidexArtifacts.getContractDir("oraiswap_pair")), - "auto" - ); - const { codeId: lpCodeId } = await oraiClient.upload( - oraiSenderAddress, - readFileSync(oraidexArtifacts.getContractDir("oraiswap_token")), - "auto" - ); + const { codeId: pairCodeId } = await oraiClient.upload(oraiSenderAddress, readFileSync(oraidexArtifacts.getContractDir('oraiswap_pair')), 'auto'); + const { codeId: lpCodeId } = await oraiClient.upload(oraiSenderAddress, readFileSync(oraidexArtifacts.getContractDir('oraiswap_token')), 'auto'); lpId = lpCodeId; // deploy another cw20 for oraiswap testing const { contractAddress: usdtAddress } = await oraiClient.instantiate( @@ -610,78 +511,53 @@ describe.only("IBCModule", () => { lpCodeId, { decimals: 6, - symbol: "USDT", - name: "USDT token", + symbol: 'USDT', + name: 'USDT token', initial_balances: [ { address: ics20Contract.contractAddress, - amount: initialBalanceAmount, - }, + amount: initialBalanceAmount + } ], mint: { - minter: oraiSenderAddress, - }, + minter: oraiSenderAddress + } }, - "cw20-usdt" - ); - usdtToken = new OraiswapTokenClient( - oraiClient, - oraiSenderAddress, - usdtAddress + 'cw20-usdt' ); + usdtToken = new OraiswapTokenClient(oraiClient, oraiSenderAddress, usdtAddress); // deploy oracle addr - const { contractAddress: oracleAddress } = - await oraidexArtifacts.deployContract( - oraiClient, - oraiSenderAddress, - {}, - "oraiswap-oracle", - "oraiswap_oracle" - ); + const { contractAddress: oracleAddress } = await oraidexArtifacts.deployContract(oraiClient, oraiSenderAddress, {}, 'oraiswap-oracle', 'oraiswap_oracle'); // deploy factory contract - oracleContract = new OraiswapOracleClient( - oraiClient, - oraiSenderAddress, - oracleAddress - ); - - await oracleContract.updateTaxRate({ rate: "0" }); - await oracleContract.updateTaxCap({ denom: AtomDenom, cap: "100000" }); - const { contractAddress: factoryAddress } = - await oraidexArtifacts.deployContract( - oraiClient, - oraiSenderAddress, - { - commission_rate: "0", - oracle_addr: oracleAddress, - pair_code_id: pairCodeId, - token_code_id: lpCodeId, - }, - "oraiswap-factory", - "oraiswap_factory" - ); + oracleContract = new OraiswapOracleClient(oraiClient, oraiSenderAddress, oracleAddress); - const { contractAddress: routerAddress } = - await oraidexArtifacts.deployContract( - oraiClient, - oraiSenderAddress, - { - factory_addr: factoryAddress, - factory_addr_v2: factoryAddress, - }, - "oraiswap-router", - "oraiswap_router" - ); - factoryContract = new OraiswapFactoryClient( + await oracleContract.updateTaxRate({ rate: '0' }); + await oracleContract.updateTaxCap({ denom: AtomDenom, cap: '100000' }); + const { contractAddress: factoryAddress } = await oraidexArtifacts.deployContract( oraiClient, oraiSenderAddress, - factoryAddress + { + commission_rate: '0', + oracle_addr: oracleAddress, + pair_code_id: pairCodeId, + token_code_id: lpCodeId + }, + 'oraiswap-factory', + 'oraiswap_factory' ); - routerContract = new OraiswapRouterClient( + + const { contractAddress: routerAddress } = await oraidexArtifacts.deployContract( oraiClient, oraiSenderAddress, - routerAddress + { + factory_addr: factoryAddress, + factory_addr_v2: factoryAddress + }, + 'oraiswap-router', + 'oraiswap_router' ); + factoryContract = new OraiswapFactoryClient(oraiClient, oraiSenderAddress, factoryAddress); + routerContract = new OraiswapRouterClient(oraiClient, oraiSenderAddress, routerAddress); // set correct router contract to prepare for the tests await ics20Contract.updateConfig({ swapRouterContract: routerAddress }); @@ -689,511 +565,349 @@ describe.only("IBCModule", () => { await ics20Contract.updateMappingPair({ localAssetInfo: { token: { - contract_addr: airiToken.contractAddress, - }, + contract_addr: airiToken.contractAddress + } }, localAssetInfoDecimals: 6, denom: airiIbcDenom, remoteDecimals: 6, - localChannelId: channel, + localChannelId: channel }); await ics20Contract.updateMappingPair({ localAssetInfo: { token: { - contract_addr: usdtToken.contractAddress, - }, + contract_addr: usdtToken.contractAddress + } }, localAssetInfoDecimals: 6, denom: usdtIbcDenom, remoteDecimals: 6, - localChannelId: channel, + localChannelId: channel }); await factoryContract.createPair({ - assetInfos, + assetInfos }); await factoryContract.createPair({ - assetInfos: [ - assetInfos[0], - { token: { contract_addr: usdtToken.contractAddress } }, - ], + assetInfos: [assetInfos[0], { token: { contract_addr: usdtToken.contractAddress } }] }); await factoryContract.createPair({ assetInfos: [ assetInfos[0], { native_token: { - denom: AtomDenom, - }, - }, - ], + denom: AtomDenom + } + } + ] }); const firstPairInfo = await factoryContract.pair({ - assetInfos, + assetInfos }); const secondPairInfo = await factoryContract.pair({ - assetInfos: [ - assetInfos[0], - { token: { contract_addr: usdtToken.contractAddress } }, - ], + assetInfos: [assetInfos[0], { token: { contract_addr: usdtToken.contractAddress } }] }); const thirdPairInfo = await factoryContract.pair({ assetInfos: [ assetInfos[0], { native_token: { - denom: AtomDenom, - }, - }, - ], + denom: AtomDenom + } + } + ] }); // mint lots of orai, airi for the pair contracts to mock provide lp // here, ratio is 1:1 => 1 AIRI = 1 ORAI - oraiClient.app.bank.setBalance( - firstPairInfo.contract_addr, - coins(initialBalanceAmount, ORAI) - ); + oraiClient.app.bank.setBalance(firstPairInfo.contract_addr, coins(initialBalanceAmount, ORAI)); await airiToken.mint({ amount: initialBalanceAmount, - recipient: firstPairInfo.contract_addr, + recipient: firstPairInfo.contract_addr }); - oraiClient.app.bank.setBalance( - secondPairInfo.contract_addr, - coins(initialBalanceAmount, ORAI) - ); + oraiClient.app.bank.setBalance(secondPairInfo.contract_addr, coins(initialBalanceAmount, ORAI)); await usdtToken.mint({ amount: initialBalanceAmount, - recipient: secondPairInfo.contract_addr, + recipient: secondPairInfo.contract_addr }); - oraiClient.app.bank.setBalance(thirdPairInfo.contract_addr, [ - coin(initialBalanceAmount, ORAI), - coin(initialBalanceAmount, AtomDenom), - ]); + oraiClient.app.bank.setBalance(thirdPairInfo.contract_addr, [coin(initialBalanceAmount, ORAI), coin(initialBalanceAmount, AtomDenom)]); }); - it("test-simulate-withdraw-liquidity", async () => { + it('test-simulate-withdraw-liquidity', async () => { // deploy another cw20 for oraiswap testing let scatomToken: OraiswapTokenClient; - const atomIbc = - "ibc/A2E2EEC9057A4A1C2C0A6A4C78B0239118DF5F278830F50B4A6BDD7A66506B78"; + const atomIbc = 'ibc/A2E2EEC9057A4A1C2C0A6A4C78B0239118DF5F278830F50B4A6BDD7A66506B78'; const { contractAddress: scatomAddress } = await oraiClient.instantiate( oraiSenderAddress, lpId, { decimals: 6, - symbol: "scATOM", - name: "scATOM token", - initial_balances: [ - { address: oraiSenderAddress, amount: initialBalanceAmount }, - ], + symbol: 'scATOM', + name: 'scATOM token', + initial_balances: [{ address: oraiSenderAddress, amount: initialBalanceAmount }], mint: { - minter: oraiSenderAddress, - }, + minter: oraiSenderAddress + } }, - "cw20-scatom" - ); - scatomToken = new OraiswapTokenClient( - oraiClient, - oraiSenderAddress, - scatomAddress + 'cw20-scatom' ); - const assetInfos = [ - { native_token: { denom: atomIbc } }, - { token: { contract_addr: scatomAddress } }, - ]; + scatomToken = new OraiswapTokenClient(oraiClient, oraiSenderAddress, scatomAddress); + const assetInfos = [{ native_token: { denom: atomIbc } }, { token: { contract_addr: scatomAddress } }]; await factoryContract.createPair({ - assetInfos, + assetInfos }); const firstPairInfo = await factoryContract.pair({ - assetInfos, + assetInfos }); const pairAddress = firstPairInfo.contract_addr; await scatomToken.increaseAllowance({ amount: initialBalanceAmount, - spender: pairAddress, + spender: pairAddress }); - oraiClient.app.bank.setBalance( - pairAddress, - coins(initialBalanceAmount, atomIbc) - ); - oraiClient.app.bank.setBalance( - oraiSenderAddress, - coins(initialBalanceAmount, atomIbc) - ); + oraiClient.app.bank.setBalance(pairAddress, coins(initialBalanceAmount, atomIbc)); + oraiClient.app.bank.setBalance(oraiSenderAddress, coins(initialBalanceAmount, atomIbc)); - const pairContract = new OraiswapPairClient( - oraiClient, - oraiSenderAddress, - pairAddress - ); + const pairContract = new OraiswapPairClient(oraiClient, oraiSenderAddress, pairAddress); await pairContract.provideLiquidity( { assets: [ { - amount: "10000000", - info: { token: { contract_addr: scatomAddress } }, + amount: '10000000', + info: { token: { contract_addr: scatomAddress } } }, - { amount: "10000000", info: { native_token: { denom: atomIbc } } }, - ], + { amount: '10000000', info: { native_token: { denom: atomIbc } } } + ] }, - "auto", + 'auto', undefined, - [{ denom: atomIbc, amount: "10000000" }] + [{ denom: atomIbc, amount: '10000000' }] ); // query liquidity balance - const lpToken = new OraiswapTokenClient( - oraiClient, - oraiSenderAddress, - firstPairInfo.liquidity_token - ); + const lpToken = new OraiswapTokenClient(oraiClient, oraiSenderAddress, firstPairInfo.liquidity_token); const result = await lpToken.balance({ address: oraiSenderAddress }); // set tax rate - await oracleContract.updateTaxRate({ rate: "0.003" }); - await oracleContract.updateTaxCap({ denom: atomIbc, cap: "1000000" }); + await oracleContract.updateTaxRate({ rate: '0.003' }); + await oracleContract.updateTaxCap({ denom: atomIbc, cap: '1000000' }); // now we withdraw lp await lpToken.send({ - amount: "1000", + amount: '1000', contract: pairAddress, - msg: Buffer.from(JSON.stringify({ withdraw_liquidity: {} })).toString( - "base64" - ), + msg: Buffer.from(JSON.stringify({ withdraw_liquidity: {} })).toString('base64') }); }); - it("cw-ics20-test-simulate-swap-ops-mock-pair-contract", async () => { + it('cw-ics20-test-simulate-swap-ops-mock-pair-contract', async () => { const simulateResult = await routerContract.simulateSwapOperations({ - offerAmount: "1", + offerAmount: '1', operations: [ { orai_swap: { offer_asset_info: assetInfos[1], - ask_asset_info: assetInfos[0], - }, + ask_asset_info: assetInfos[0] + } }, { orai_swap: { offer_asset_info: assetInfos[0], ask_asset_info: { - token: { contract_addr: usdtToken.contractAddress }, - }, - }, - }, - ], + token: { contract_addr: usdtToken.contractAddress } + } + } + } + ] }); - expect(simulateResult.amount).toEqual("1"); + expect(simulateResult.amount).toEqual('1'); }); it.each<[string, string, string]>([ - [ - parseToIbcWasmMemo(bobAddress, "", "orai"), - bobAddress, - "Generic error: Destination channel empty in build ibc msg", - ], - [ - parseToIbcWasmMemo( - "not-evm-based-nor-cosmos-based", - channel, - oraiIbcDenom - ), - bobAddress, - "Generic error: The destination info is neither evm or cosmos based", - ], - ])( - "cw-ics20-test-single-step-native-token-swap-operations-to-dest-denom memo %s expected recipient %s", - async ( - memo: string, - expectedRecipient: string, - expectedIbcErrorMsg: string - ) => { - await ics20Contract.updateMappingPair({ - localAssetInfo: { - native_token: { - denom: ORAI, - }, - }, - localAssetInfoDecimals: 6, - denom: oraiIbcDenom, - remoteDecimals: 6, - localChannelId: channel, - }); + [parseToIbcWasmMemo(bobAddress, '', 'orai'), bobAddress, 'Generic error: Destination channel empty in build ibc msg'], + [parseToIbcWasmMemo('not-evm-based-nor-cosmos-based', channel, oraiIbcDenom), bobAddress, 'Generic error: The destination info is neither evm nor cosmos based'] + ])('cw-ics20-test-single-step-native-token-swap-operations-to-dest-denom memo %s expected recipient %s', async (memo: string, expectedRecipient: string, expectedIbcErrorMsg: string) => { + await ics20Contract.updateMappingPair({ + localAssetInfo: { + native_token: { + denom: ORAI + } + }, + localAssetInfoDecimals: 6, + denom: oraiIbcDenom, + remoteDecimals: 6, + localChannelId: channel + }); - // now send ibc package - icsPackage.memo = memo; - // transfer from cosmos to oraichain, should pass - const result = await cosmosChain.ibc.sendPacketReceive({ - packet: { - data: toBinary(icsPackage), - ...packetData, - }, - relayer: relayerAddress, - }); + // now send ibc package + icsPackage.memo = memo; + // transfer from cosmos to oraichain, should pass + const result = await cosmosChain.ibc.sendPacketReceive({ + packet: { + data: toBinary(icsPackage), + ...packetData + }, + relayer: relayerAddress + }); - const bobBalance = oraiClient.app.bank.getBalance(expectedRecipient); - expect(bobBalance.length).toBeGreaterThan(0); - expect(bobBalance[0].denom).toEqual(ORAI); - expect(parseInt(bobBalance[0].amount)).toBeGreaterThan(0); - const transferEvent = result.events.find( - (event) => - event.type === "transfer" && - event.attributes.find( - (attr) => - attr.key === "recipient" && attr.value === expectedRecipient - ) - ); - expect(transferEvent).not.toBeUndefined(); - const ibcErrorMsg = result.attributes.find( - (attr) => attr.key === "ibc_error_msg" - ); - expect(ibcErrorMsg).not.toBeUndefined(); - expect(ibcErrorMsg.value).toEqual(expectedIbcErrorMsg); - } - ); + const bobBalance = oraiClient.app.bank.getBalance(expectedRecipient); + expect(bobBalance.length).toBeGreaterThan(0); + expect(bobBalance[0].denom).toEqual(ORAI); + expect(parseInt(bobBalance[0].amount)).toBeGreaterThan(0); + const transferEvent = result.events.find((event) => event.type === 'transfer' && event.attributes.find((attr) => attr.key === 'recipient' && attr.value === expectedRecipient)); + expect(transferEvent).not.toBeUndefined(); + const ibcErrorMsg = result.attributes.find((attr) => attr.key === 'ibc_error_msg'); + expect(ibcErrorMsg).not.toBeUndefined(); + expect(ibcErrorMsg.value).toEqual(expectedIbcErrorMsg); + }); it.each([ - [ - `${bobAddress}`, - "orai1n6fwuamldz6mv5f3qwe9296pudjjemhmkfcgc3", - bobAddress, - "Generic error: Destination channel empty in build ibc msg", - ], // hard-coded usdt address - [ - `${bobAddress}`, - "orai18cvw806fj5n7xxz06ak8vjunveeks4zzzn37cu", - bobAddress, - "Generic error: Destination channel empty in build ibc msg", - ], // edge case, dest denom is also airi - ])( - "cw-ics20-test-single-step-cw20-token-swap-operations-to-dest-denom memo %s dest denom %s expected recipient %s", - async ( - destReceiver: string, - destDenom: string, - expectedRecipient: string, - expectedIbcErrorMsg: string - ) => { - // now send ibc package - icsPackage.memo = parseToIbcWasmMemo(destReceiver, "", destDenom); - // transfer from cosmos to oraichain, should pass - const result = await cosmosChain.ibc.sendPacketReceive({ - packet: { - data: toBinary(icsPackage), - ...packetData, - }, - relayer: relayerAddress, - }); + [`${bobAddress}`, 'orai1n6fwuamldz6mv5f3qwe9296pudjjemhmkfcgc3', bobAddress, 'Generic error: Destination channel empty in build ibc msg'], // hard-coded usdt address + [`${bobAddress}`, 'orai18cvw806fj5n7xxz06ak8vjunveeks4zzzn37cu', bobAddress, 'Generic error: Destination channel empty in build ibc msg'] // edge case, dest denom is also airi + ])('cw-ics20-test-single-step-cw20-token-swap-operations-to-dest-denom memo %s dest denom %s expected recipient %s', async (destReceiver: string, destDenom: string, expectedRecipient: string, expectedIbcErrorMsg: string) => { + // now send ibc package + icsPackage.memo = parseToIbcWasmMemo(destReceiver, '', destDenom); + // transfer from cosmos to oraichain, should pass + const result = await cosmosChain.ibc.sendPacketReceive({ + packet: { + data: toBinary(icsPackage), + ...packetData + }, + relayer: relayerAddress + }); - const token = new OraiswapTokenClient( - oraiClient, - oraiSenderAddress, - destDenom - ); - const cw20Balance = await token.balance({ address: expectedRecipient }); - expect(parseInt(cw20Balance.balance)).toBeGreaterThan(1000); - expect( - result.attributes.find((attr) => attr.key === "ibc_error_msg").value - ).toEqual(expectedIbcErrorMsg); - } - ); + const token = new OraiswapTokenClient(oraiClient, oraiSenderAddress, destDenom); + const cw20Balance = await token.balance({ address: expectedRecipient }); + expect(parseInt(cw20Balance.balance)).toBeGreaterThan(1000); + expect(result.attributes.find((attr) => attr.key === 'ibc_error_msg').value).toEqual(expectedIbcErrorMsg); + }); - it("cw-ics20-test-single-step-cw20-token-swap-operations-to-dest-denom-FAILED-cannot-simulate-swap", async () => { + it('cw-ics20-test-single-step-cw20-token-swap-operations-to-dest-denom-FAILED-cannot-simulate-swap', async () => { // now send ibc package // => dest token on Orai = ibc/EB7094899ACFB7A6F2A67DB084DEE2E9A83DEFAA5DEF92D9A9814FFD9FF673FA - icsPackage.memo = parseToIbcWasmMemo(bobAddressEth, "channel-0", "foo"); + icsPackage.memo = parseToIbcWasmMemo(bobAddressEth, 'channel-0', 'foo'); // transfer from cosmos to oraichain, should pass const result = await cosmosChain.ibc.sendPacketReceive({ packet: { data: toBinary(icsPackage), - ...packetData, + ...packetData }, - relayer: relayerAddress, + relayer: relayerAddress }); - expect( - result.attributes.find((attr) => attr.key === "ibc_error_msg").value - ).toEqual( + expect(result.attributes.find((attr) => attr.key === 'ibc_error_msg').value).toEqual( 'Cannot simulate swap with ops: [OraiSwap { offer_asset_info: Token { contract_addr: Addr("orai18cvw806fj5n7xxz06ak8vjunveeks4zzzn37cu") }, ask_asset_info: NativeToken { denom: "orai" } }, OraiSwap { offer_asset_info: NativeToken { denom: "orai" }, ask_asset_info: NativeToken { denom: "ibc/EB7094899ACFB7A6F2A67DB084DEE2E9A83DEFAA5DEF92D9A9814FFD9FF673FA" } }] with error: "Error parsing into type oraiswap::router::SimulateSwapOperationsResponse: unknown field `ok`, expected `amount`"' ); }); - it("cw-ics20-test-single-step-cw20-FAILED-NATIVE_RECEIVE_ID-ack-SUCCESS", async () => { + it('cw-ics20-test-single-step-cw20-FAILED-NATIVE_RECEIVE_ID-ack-SUCCESS', async () => { // now send ibc package - icsPackage.memo = ""; - icsPackage.amount = initialBalanceAmount + "0"; + icsPackage.memo = ''; + icsPackage.amount = initialBalanceAmount + '0'; // transfer from cosmos to oraichain, should pass const result = await cosmosChain.ibc.sendPacketReceive({ packet: { data: toBinary(icsPackage), - ...packetData, + ...packetData }, - relayer: relayerAddress, + relayer: relayerAddress }); - expect( - findWasmEvent(result.events, "action", "native_receive_id") - ).not.toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'native_receive_id')).not.toBeUndefined(); // ack should be successful - expect(result.acknowledgement).toEqual( - Buffer.from('{"result":"MQ=="}').toString("base64") - ); + expect(result.acknowledgement).toEqual(Buffer.from('{"result":"MQ=="}').toString('base64')); // other types of reply id must not be called - expect( - findWasmEvent(result.events, "action", "swap_ops_failure_id") - ).toBeUndefined(); - expect( - findWasmEvent(result.events, "action", "follow_up_failure_id") - ).toBeUndefined(); - expect( - findWasmEvent(result.events, "action", "refund_failure_id") - ).toBeUndefined(); - expect( - findWasmEvent(result.events, "action", "ibc_transfer_native_error_id") - ).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'swap_ops_failure_id')).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'follow_up_failure_id')).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'refund_failure_id')).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'ibc_transfer_native_error_id')).toBeUndefined(); }); - it("cw-ics20-test-single-step-cw20-FAILED-SWAP_OPS_FAILURE_ID-ack-SUCCESS", async () => { + it('cw-ics20-test-single-step-cw20-FAILED-SWAP_OPS_FAILURE_ID-ack-SUCCESS', async () => { // fixture // icsPackage.memo = `${bobAddress}:${usdtToken.contractAddress}`; - icsPackage.memo = parseToIbcWasmMemo( - bobAddress, - "", - usdtToken.contractAddress - ); + icsPackage.memo = parseToIbcWasmMemo(bobAddress, '', usdtToken.contractAddress); console.log(icsPackage.memo); - icsPackage.amount = initialBalanceAmount + "0"; + icsPackage.amount = initialBalanceAmount + '0'; // transfer from cosmos to oraichain, should pass const result = await cosmosChain.ibc.sendPacketReceive({ packet: { data: toBinary(icsPackage), - ...packetData, + ...packetData }, - relayer: relayerAddress, + relayer: relayerAddress }); - expect( - findWasmEvent(result.events, "action", "swap_ops_failure_id") - ).not.toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'swap_ops_failure_id')).not.toBeUndefined(); // ack should be successful - expect(result.acknowledgement).toEqual( - Buffer.from('{"result":"MQ=="}').toString("base64") - ); + expect(result.acknowledgement).toEqual(Buffer.from('{"result":"MQ=="}').toString('base64')); // refunding also fails because of not enough balance to refund - expect( - findWasmEvent(result.events, "action", "refund_failure_id") - ).not.toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'refund_failure_id')).not.toBeUndefined(); // other types of reply id must not be called - expect( - findWasmEvent(result.events, "action", "native_receive_id") - ).toBeUndefined(); - expect( - findWasmEvent(result.events, "action", "follow_up_failure_id") - ).toBeUndefined(); - expect( - findWasmEvent(result.events, "action", "ibc_transfer_native_error_id") - ).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'native_receive_id')).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'follow_up_failure_id')).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'ibc_transfer_native_error_id')).toBeUndefined(); }); - it("cw-ics20-test-single-step-cw20-FAILED-IBC_TRANSFER_NATIVE_ERROR_ID-ack-SUCCESS", async () => { + it('cw-ics20-test-single-step-cw20-FAILED-IBC_TRANSFER_NATIVE_ERROR_ID-ack-SUCCESS', async () => { // fixture // icsPackage.memo = `unknown-channel/${bobAddress}:${usdtToken.contractAddress}`; // dest denom on orai: ibc/79E5EC9A42F2FC01B2BA609F13C985393779BE5153E01D24E79C2681B0DFB592 - icsPackage.memo = parseToIbcWasmMemo(bobAddress, "channel-15", "uatom"); + icsPackage.memo = parseToIbcWasmMemo(bobAddress, 'channel-15', 'uatom'); icsPackage.amount = initialBalanceAmount; // transfer from cosmos to oraichain, should pass const result = await cosmosChain.ibc.sendPacketReceive({ packet: { data: toBinary(icsPackage), - ...packetData, + ...packetData }, - relayer: relayerAddress, + relayer: relayerAddress }); console.log(result); // refunding also fails because of not enough balance to refund - expect( - findWasmEvent(result.events, "action", "ibc_transfer_native_error_id") - ).not.toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'ibc_transfer_native_error_id')).not.toBeUndefined(); // ack should be successful - expect(result.acknowledgement).toEqual( - Buffer.from('{"result":"MQ=="}').toString("base64") - ); - expect( - findWasmEvent(result.events, "undo_increase_channel", channel) - ).toBeUndefined(); + expect(result.acknowledgement).toEqual(Buffer.from('{"result":"MQ=="}').toString('base64')); + expect(findWasmEvent(result.events, 'undo_increase_channel', channel)).toBeUndefined(); // other types of reply id must not be called - expect( - findWasmEvent(result.events, "action", "swap_ops_failure_id") - ).toBeUndefined(); - expect( - findWasmEvent(result.events, "action", "native_receive_id") - ).toBeUndefined(); - expect( - findWasmEvent(result.events, "action", "follow_up_failure_id") - ).toBeUndefined(); - expect( - findWasmEvent(result.events, "action", "refund_failure_id") - ).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'swap_ops_failure_id')).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'native_receive_id')).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'follow_up_failure_id')).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'refund_failure_id')).toBeUndefined(); // for ibc native transfer case, we wont have refund either expect( - result.events.find( - (ev) => - ev.type === "wasm" && - ev.attributes.find( - (attr) => attr.key === "action" && attr.value === "transfer" - ) && - ev.attributes.find( - (attr) => attr.key === "to" && attr.value === bobAddress - ) - ) + result.events.find((ev) => ev.type === 'wasm' && ev.attributes.find((attr) => attr.key === 'action' && attr.value === 'transfer') && ev.attributes.find((attr) => attr.key === 'to' && attr.value === bobAddress)) ).toBeUndefined(); }); - it("cw-ics20-test-single-step-cw20-FAILED-REFUND_FAILURE_ID-ack-SUCCESS", async () => { + it('cw-ics20-test-single-step-cw20-FAILED-REFUND_FAILURE_ID-ack-SUCCESS', async () => { // fixture - icsPackage.memo = parseToIbcWasmMemo( - bobAddress, - "", - usdtToken.contractAddress - ); - icsPackage.amount = initialBalanceAmount + "0"; + icsPackage.memo = parseToIbcWasmMemo(bobAddress, '', usdtToken.contractAddress); + icsPackage.amount = initialBalanceAmount + '0'; // transfer from cosmos to oraichain, should pass const result = await cosmosChain.ibc.sendPacketReceive({ packet: { data: toBinary(icsPackage), - ...packetData, + ...packetData }, - relayer: relayerAddress, + relayer: relayerAddress }); - expect( - findWasmEvent(result.events, "action", "swap_ops_failure_id") - ).not.toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'swap_ops_failure_id')).not.toBeUndefined(); // ack should be successful - expect(result.acknowledgement).toEqual( - Buffer.from('{"result":"MQ=="}').toString("base64") - ); + expect(result.acknowledgement).toEqual(Buffer.from('{"result":"MQ=="}').toString('base64')); // refunding also fails because of not enough balance to refund - expect( - findWasmEvent(result.events, "action", "refund_failure_id") - ).not.toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'refund_failure_id')).not.toBeUndefined(); // other types of reply id must not be called - expect( - findWasmEvent(result.events, "action", "native_receive_id") - ).toBeUndefined(); - expect( - findWasmEvent(result.events, "action", "follow_up_failure_id") - ).toBeUndefined(); - expect( - findWasmEvent(result.events, "action", "ibc_transfer_native_error_id") - ).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'native_receive_id')).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'follow_up_failure_id')).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'ibc_transfer_native_error_id')).toBeUndefined(); }); - it("cw-ics20-test-single-step-cw20-success-FOLLOW_UP_IBC_SEND_FAILURE_ID-must-not-have-SWAP_OPS_FAILURE_ID-or-on_packet_failure-ack-SUCCESS", async () => { + it('cw-ics20-test-single-step-cw20-success-FOLLOW_UP_IBC_SEND_FAILURE_ID-must-not-have-SWAP_OPS_FAILURE_ID-or-on_packet_failure-ack-SUCCESS', async () => { // fixture // icsPackage.memo = `${channel}/${bobAddress}:${airiToken.contractAddress}`; icsPackage.memo = parseToIbcWasmMemo(bobAddress, channel, airiIbcDenom); @@ -1202,176 +916,120 @@ describe.only("IBCModule", () => { const result = await cosmosChain.ibc.sendPacketReceive({ packet: { data: toBinary(icsPackage), - ...packetData, + ...packetData }, - relayer: relayerAddress, + relayer: relayerAddress }); // all id types of reply id must not be called, especially swap_ops_failure_id - expect( - findWasmEvent(result.events, "action", "swap_ops_failure_id") - ).toBeUndefined(); - expect( - findWasmEvent(result.events, "action", "native_receive_id") - ).toBeUndefined(); - expect( - findWasmEvent(result.events, "action", "follow_up_failure_id") - ).toBeUndefined(); - expect( - findWasmEvent(result.events, "action", "refund_failure_id") - ).toBeUndefined(); - expect( - findWasmEvent(result.events, "action", "ibc_transfer_native_error_id") - ).toBeUndefined(); - expect( - findWasmEvent(result.events, "action", "acknowledge") - ).toBeUndefined(); - expect( - findWasmEvent(result.events, "undo_reduce_channel", channel) - ).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'swap_ops_failure_id')).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'native_receive_id')).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'follow_up_failure_id')).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'refund_failure_id')).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'ibc_transfer_native_error_id')).toBeUndefined(); + expect(findWasmEvent(result.events, 'action', 'acknowledge')).toBeUndefined(); + expect(findWasmEvent(result.events, 'undo_reduce_channel', channel)).toBeUndefined(); // ack should be successful - expect(result.acknowledgement).toEqual( - Buffer.from('{"result":"MQ=="}').toString("base64") - ); + expect(result.acknowledgement).toEqual(Buffer.from('{"result":"MQ=="}').toString('base64')); // for ibc native transfer case, we wont have refund either expect( - result.events.find( - (ev) => - ev.type === "wasm" && - ev.attributes.find( - (attr) => attr.key === "action" && attr.value === "transfer" - ) && - ev.attributes.find( - (attr) => attr.key === "to" && attr.value === bobAddress - ) - ) + result.events.find((ev) => ev.type === 'wasm' && ev.attributes.find((attr) => attr.key === 'action' && attr.value === 'transfer') && ev.attributes.find((attr) => attr.key === 'to' && attr.value === bobAddress)) ).toBeUndefined(); }); it.each([ - [channel, "abcd", usdtIbcDenom], // hard-coded usdt address - [channel, "0x", airiIbcDenom], - [channel, "0xabcd", usdtIbcDenom], - [channel, "tron-testnet0xabcd", airiIbcDenom], // bad evm address case - ])( - "cw-ics20-test-single-step-has-ibc-msg-dest-fail memo %s dest denom %s expected error", - async (destChannel: string, destReceiver: string, destDenom: string) => { - // now send ibc package - // icsPackage.memo = `${destChannel}/${destReceiver}:${destDenom}`; - icsPackage.memo = parseToIbcWasmMemo( - destReceiver, - destChannel, - destDenom - ); - // transfer from cosmos to oraichain, should pass - const result = await cosmosChain.ibc.sendPacketReceive({ - packet: { - data: toBinary(icsPackage), - ...packetData, - }, - relayer: relayerAddress, - }); - const ibcEvent = result.events.find( - (event) => - event.type === "transfer" && - event.attributes.find((attr) => attr.key === "channel") - ); - // get swap operation event - expect(ibcEvent).toBeUndefined(); - const ibcErrorMsg = result.attributes.find( - (attr) => - attr.key === "ibc_error_msg" && - attr.value === - "Generic error: The destination info is neither evm or cosmos based" - ); - expect(ibcErrorMsg).not.toBeUndefined(); - } - ); + [channel, 'abcd', usdtIbcDenom], // hard-coded usdt address + [channel, '0x', airiIbcDenom], + [channel, '0xabcd', usdtIbcDenom], + [channel, 'tron-testnet0xabcd', airiIbcDenom] // bad evm address case + ])('cw-ics20-test-single-step-has-ibc-msg-dest-fail memo %s dest denom %s expected error', async (destChannel: string, destReceiver: string, destDenom: string) => { + // now send ibc package + // icsPackage.memo = `${destChannel}/${destReceiver}:${destDenom}`; + icsPackage.memo = parseToIbcWasmMemo(destReceiver, destChannel, destDenom); + // transfer from cosmos to oraichain, should pass + const result = await cosmosChain.ibc.sendPacketReceive({ + packet: { + data: toBinary(icsPackage), + ...packetData + }, + relayer: relayerAddress + }); + const ibcEvent = result.events.find((event) => event.type === 'transfer' && event.attributes.find((attr) => attr.key === 'channel')); + // get swap operation event + expect(ibcEvent).toBeUndefined(); + const ibcErrorMsg = result.attributes.find((attr) => attr.key === 'ibc_error_msg' && attr.value === 'Generic error: The destination info is neither evm nor cosmos based'); + expect(ibcErrorMsg).not.toBeUndefined(); + }); it.each([ - [channel, bridgeReceiver, airiIbcDenom], // hard-coded airi - ])( - "cw-ics20-test-single-step-has-ibc-msg-dest-receiver-evm-based memo %s dest denom %s expected recipient %s", - async (destChannel: string, destReceiver: string, destDenom: string) => { - // now send ibc package - // icsPackage.memo = `${destChannel}/${destReceiver}:${destDenom}`; - icsPackage.memo = parseToIbcWasmMemo( - destReceiver, - destChannel, - destDenom - ); - - // transfer from cosmos to oraichain, should pass - let result = await cosmosChain.ibc.sendPacketReceive({ - packet: { - data: toBinary(icsPackage), - ...packetData, - }, - relayer: relayerAddress, - }); - const sendPacketEvent = result.events.find( - (event) => event.type === "send_packet" - ); - expect(sendPacketEvent).not.toBeUndefined(); - const packetHex = sendPacketEvent.attributes.find( - (attr) => attr.key === "packet_data_hex" - ).value; - expect(packetHex).not.toBeUndefined(); - const packet = JSON.parse( - Buffer.from(packetHex, "hex").toString("ascii") - ); - expect(packet.receiver).toEqual(icsPackage.sender); - expect(packet.sender).toEqual(ics20Contract.contractAddress); - // expect(packet.memo).toEqual(ics20Contract.contractAddress); + [channel, bridgeReceiver, airiIbcDenom] // hard-coded airi + ])('cw-ics20-test-single-step-has-ibc-msg-dest-receiver-evm-based memo %s dest denom %s expected recipient %s', async (destChannel: string, destReceiver: string, destDenom: string) => { + // now send ibc package + // icsPackage.memo = `${destChannel}/${destReceiver}:${destDenom}`; + icsPackage.memo = parseToIbcWasmMemo(destReceiver, destChannel, destDenom); - // pass 1 day with 86_400 seconds - cosmosChain.store.tx((setter) => - Ok(setter("time")(cosmosChain.time + 86_400 * 1e9)) - ); + // transfer from cosmos to oraichain, should pass + let result = await cosmosChain.ibc.sendPacketReceive({ + packet: { + data: toBinary(icsPackage), + ...packetData + }, + relayer: relayerAddress + }); + const sendPacketEvent = result.events.find((event) => event.type === 'send_packet'); + expect(sendPacketEvent).not.toBeUndefined(); + const packetHex = sendPacketEvent.attributes.find((attr) => attr.key === 'packet_data_hex').value; + expect(packetHex).not.toBeUndefined(); + const packet = JSON.parse(Buffer.from(packetHex, 'hex').toString('ascii')); + expect(packet.receiver).toEqual(icsPackage.sender); + expect(packet.sender).toEqual(ics20Contract.contractAddress); + // expect(packet.memo).toEqual(ics20Contract.contractAddress); + + // pass 1 day with 86_400 seconds + cosmosChain.store.tx((setter) => Ok(setter('time')(cosmosChain.time + 86_400 * 1e9))); - // transfer from cosmos to oraichain, should pass - result = await cosmosChain.ibc.sendPacketReceive({ - packet: { - data: toBinary(icsPackage), - ...packetData, - }, - relayer: relayerAddress, - }); - // expect( - // flatten(result.events.map((e) => e.attributes)).find((a) => a.key === 'error_follow_up_msgs').value - // ).toContain('Generic error: timeout at'); - } - ); + // transfer from cosmos to oraichain, should pass + result = await cosmosChain.ibc.sendPacketReceive({ + packet: { + data: toBinary(icsPackage), + ...packetData + }, + relayer: relayerAddress + }); + // expect( + // flatten(result.events.map((e) => e.attributes)).find((a) => a.key === 'error_follow_up_msgs').value + // ).toContain('Generic error: timeout at'); + }); - it("cw-ics20-test-single-step-ibc-msg-map-with-fee-denom-orai-and-airi-destination-denom-should-swap-normally", async () => { + it('cw-ics20-test-single-step-ibc-msg-map-with-fee-denom-orai-and-airi-destination-denom-should-swap-normally', async () => { await ics20Contract.updateMappingPair({ localAssetInfo: { native_token: { - denom: ORAI, - }, + denom: ORAI + } }, localAssetInfoDecimals: 6, denom: oraiIbcDenom, remoteDecimals: 6, - localChannelId: channel, + localChannelId: channel }); let packetData = { src: { port_id: cosmosPort, - channel_id: channel, + channel_id: channel }, dest: { port_id: oraiPort, - channel_id: channel, + channel_id: channel }, sequence: 27, timeout: { block: { revision: 1, - height: 12345678, - }, - }, + height: 12345678 + } + } }; const icsPackage: FungibleTokenPacketData = { amount: ibcTransferAmount, @@ -1379,64 +1037,51 @@ describe.only("IBCModule", () => { receiver: bobAddress, sender: cosmosSenderAddress, // memo: `${bobAddress}:${airiToken.contractAddress}`, - memo: parseToIbcWasmMemo(bobAddress, "", airiToken.contractAddress), + memo: parseToIbcWasmMemo(bobAddress, '', airiToken.contractAddress) }; // transfer from cosmos to oraichain, should pass let result = await cosmosChain.ibc.sendPacketReceive({ packet: { data: toBinary(icsPackage), - ...packetData, + ...packetData }, - relayer: relayerAddress, + relayer: relayerAddress }); - const swapEvent = result.events.find( - (event) => - event.type === "wasm" && - event.attributes.find((attr) => attr.value === "swap") - ); - expect( - swapEvent.attributes.filter( - (attr) => attr.key === "offer_asset" && attr.value === ORAI - ).length - ).toBeGreaterThan(0); - expect( - swapEvent.attributes.filter( - (attr) => - attr.key === "ask_asset" && attr.value === airiToken.contractAddress - ).length - ).toBeGreaterThan(0); + const swapEvent = result.events.find((event) => event.type === 'wasm' && event.attributes.find((attr) => attr.value === 'swap')); + expect(swapEvent.attributes.filter((attr) => attr.key === 'offer_asset' && attr.value === ORAI).length).toBeGreaterThan(0); + expect(swapEvent.attributes.filter((attr) => attr.key === 'ask_asset' && attr.value === airiToken.contractAddress).length).toBeGreaterThan(0); }); - it("cw-ics20-test-single-step-ibc-msg-map-with-fee-denom-orai-and-orai-destination-denom-should-transfer-normally", async () => { + it('cw-ics20-test-single-step-ibc-msg-map-with-fee-denom-orai-and-orai-destination-denom-should-transfer-normally', async () => { await ics20Contract.updateMappingPair({ localAssetInfo: { native_token: { - denom: ORAI, - }, + denom: ORAI + } }, localAssetInfoDecimals: 6, denom: oraiIbcDenom, remoteDecimals: 6, - localChannelId: channel, + localChannelId: channel }); let packetData = { src: { port_id: cosmosPort, - channel_id: channel, + channel_id: channel }, dest: { port_id: oraiPort, - channel_id: channel, + channel_id: channel }, sequence: 27, timeout: { block: { revision: 1, - height: 12345678, - }, - }, + height: 12345678 + } + } }; const icsPackage: FungibleTokenPacketData = { amount: ibcTransferAmount, @@ -1444,63 +1089,44 @@ describe.only("IBCModule", () => { receiver: bobAddress, sender: cosmosSenderAddress, // memo: `${bobAddress}:orai`, - memo: parseToIbcWasmMemo(bobAddress, "", "orai"), + memo: parseToIbcWasmMemo(bobAddress, '', 'orai') }; // transfer from cosmos to oraichain, should pass let result = await cosmosChain.ibc.sendPacketReceive({ packet: { data: toBinary(icsPackage), - ...packetData, + ...packetData }, - relayer: relayerAddress, + relayer: relayerAddress }); - const transferEvent = result.events.find( - (event) => event.type === "transfer" - ); - expect( - transferEvent.attributes.filter( - (attr) => attr.key === "recipient" && attr.value === bobAddress - ).length - ).toBeGreaterThan(0); - expect( - transferEvent.attributes.filter( - (attr) => - attr.key === "amount" && - attr.value === - JSON.stringify([{ denom: ORAI, amount: ibcTransferAmount }]) - ).length - ).toBeGreaterThan(0); + const transferEvent = result.events.find((event) => event.type === 'transfer'); + expect(transferEvent.attributes.filter((attr) => attr.key === 'recipient' && attr.value === bobAddress).length).toBeGreaterThan(0); + expect(transferEvent.attributes.filter((attr) => attr.key === 'amount' && attr.value === JSON.stringify([{ denom: ORAI, amount: ibcTransferAmount }])).length).toBeGreaterThan(0); }); - describe("test-single-step-cosmos-based-ibc-transfer-native", () => { + describe('test-single-step-cosmos-based-ibc-transfer-native', () => { // unknowChannel is channel to cosmos - const unknownChannel = "channel-15"; + const unknownChannel = 'channel-15'; beforeEach(async () => { // fixture // needs to fake a new ibc channel so that we can successfully do ibc transfer - oraiClient.app.ibc.relay( - unknownChannel, - oraiPort, - unknownChannel, - cosmosPort, - cosmosChain - ); + oraiClient.app.ibc.relay(unknownChannel, oraiPort, unknownChannel, cosmosPort, cosmosChain); await cosmosChain.ibc.sendChannelOpen({ open_init: { channel: { counterparty_endpoint: { port_id: oraiPort, - channel_id: unknownChannel, + channel_id: unknownChannel }, endpoint: { port_id: cosmosPort, - channel_id: unknownChannel, + channel_id: unknownChannel }, order: IbcOrder.Unordered, - version: "ics20-1", - connection_id: "connection-0", - }, - }, + version: 'ics20-1', + connection_id: 'connection-0' + } + } }); await cosmosChain.ibc.sendChannelConnect({ @@ -1508,107 +1134,84 @@ describe.only("IBCModule", () => { channel: { counterparty_endpoint: { port_id: oraiPort, - channel_id: unknownChannel, + channel_id: unknownChannel }, endpoint: { port_id: cosmosPort, - channel_id: unknownChannel, + channel_id: unknownChannel }, order: IbcOrder.Unordered, - version: "ics20-1", - connection_id: "connection-0", + version: 'ics20-1', + connection_id: 'connection-0' }, - counterparty_version: "ics20-1", - }, + counterparty_version: 'ics20-1' + } }); cosmosChain.ibc.addMiddleWare((msg, app) => { - if ("packet" in msg.data) { + if ('packet' in msg.data) { const data = msg.data.packet as IbcPacket; if (Number(data.timeout.timestamp) < cosmosChain.time) { - throw new GenericError("timeout at " + data.timeout.timestamp); + throw new GenericError('timeout at ' + data.timeout.timestamp); } } }); }); it.each([ - ["channel-15", "orai1g4h64yjt0fvzv5v2j8tyfnpe5kmnetejvfgs7g", "uatom"], // edge case, dest denom is also airi - ])( - "cw-ics20-test-single-step-has-ibc-msg-dest-receiver-cosmos-based dest channel %s dest denom %s expected recipient %s", - async ( - destChannel: string, - destReceiver: string, - destDenom: string - ) => { - // now send ibc package - // icsPackage.memo = `${destChannel}/${destReceiver}:${destDenom}`; - icsPackage.memo = parseToIbcWasmMemo( - destReceiver, - destChannel, - destDenom - ); - // transfer from cosmos to oraichain, should pass - const result = await cosmosChain.ibc.sendPacketReceive({ - packet: { - data: toBinary(icsPackage), - ...packetData, - }, - relayer: relayerAddress, - }); + ['channel-15', 'orai1g4h64yjt0fvzv5v2j8tyfnpe5kmnetejvfgs7g', 'uatom'] // edge case, dest denom is also airi + ])('cw-ics20-test-single-step-has-ibc-msg-dest-receiver-cosmos-based dest channel %s dest denom %s expected recipient %s', async (destChannel: string, destReceiver: string, destDenom: string) => { + // now send ibc package + // icsPackage.memo = `${destChannel}/${destReceiver}:${destDenom}`; + icsPackage.memo = parseToIbcWasmMemo(destReceiver, destChannel, destDenom); + // transfer from cosmos to oraichain, should pass + const result = await cosmosChain.ibc.sendPacketReceive({ + packet: { + data: toBinary(icsPackage), + ...packetData + }, + relayer: relayerAddress + }); - const ibcEvent = result.events.find( - (event) => - event.type === "transfer" && - event.attributes.find((attr) => attr.key === "channel") - ); - - // get swap operation event - expect(ibcEvent).not.toBeUndefined(); - expect( - ibcEvent.attributes.find((attr) => attr.key === "channel").value - ).toEqual(destChannel); - expect( - ibcEvent.attributes.find((attr) => attr.key === "recipient").value - ).toEqual(destReceiver); - expect( - ibcEvent.attributes.find((attr) => attr.key === "sender").value - ).toEqual(ics20Contract.contractAddress); - expect( - ibcEvent.attributes.find((attr) => attr.key === "amount").value - ).toContain(AtomDenom); - } - ); + const ibcEvent = result.events.find((event) => event.type === 'transfer' && event.attributes.find((attr) => attr.key === 'channel')); - it("cw-ics20-test-single-step-ibc-msg-SUCCESS-map-with-fee-denom-orai-and-orai-destination-denom-with-dest-channel-not-matched-with-mapping-pair-should-do-ibctransfer", async () => { + // get swap operation event + expect(ibcEvent).not.toBeUndefined(); + expect(ibcEvent.attributes.find((attr) => attr.key === 'channel').value).toEqual(destChannel); + expect(ibcEvent.attributes.find((attr) => attr.key === 'recipient').value).toEqual(destReceiver); + expect(ibcEvent.attributes.find((attr) => attr.key === 'sender').value).toEqual(ics20Contract.contractAddress); + expect(ibcEvent.attributes.find((attr) => attr.key === 'amount').value).toContain(AtomDenom); + }); + + it('cw-ics20-test-single-step-ibc-msg-SUCCESS-map-with-fee-denom-orai-and-orai-destination-denom-with-dest-channel-not-matched-with-mapping-pair-should-do-ibctransfer', async () => { await ics20Contract.updateMappingPair({ localAssetInfo: { native_token: { - denom: ORAI, - }, + denom: ORAI + } }, localAssetInfoDecimals: 6, denom: oraiIbcDenom, remoteDecimals: 6, - localChannelId: channel, + localChannelId: channel }); let packetData = { src: { port_id: cosmosPort, - channel_id: channel, + channel_id: channel }, dest: { port_id: oraiPort, - channel_id: channel, + channel_id: channel }, sequence: 27, timeout: { block: { revision: 1, - height: 12345678, - }, - }, + height: 12345678 + } + } }; const icsPackage: FungibleTokenPacketData = { amount: ibcTransferAmount, @@ -1616,46 +1219,30 @@ describe.only("IBCModule", () => { receiver: bobAddress, sender: cosmosSenderAddress, // memo: `${unknownChannel}/${bobAddress}:orai`, - memo: parseToIbcWasmMemo(bobAddress, atomChannel, "uatom"), + memo: parseToIbcWasmMemo(bobAddress, atomChannel, 'uatom') }; // transfer from cosmos to oraichain, should pass let result = await cosmosChain.ibc.sendPacketReceive({ packet: { data: toBinary(icsPackage), - ...packetData, + ...packetData }, - relayer: relayerAddress, + relayer: relayerAddress }); - const transferEvent = result.events.find( - (event) => - event.type === "transfer" && - event.attributes.find((attr) => attr.key === "channel") - ); + const transferEvent = result.events.find((event) => event.type === 'transfer' && event.attributes.find((attr) => attr.key === 'channel')); console.log(transferEvent); - expect( - transferEvent.attributes.filter( - (attr) => attr.key === "recipient" && attr.value === bobAddress - ).length - ).toBeGreaterThan(0); - expect( - transferEvent.attributes.filter( - (attr) => attr.key === "amount" && attr.value.includes(AtomDenom) - ).length - ).toBeGreaterThan(0); - expect( - transferEvent.attributes.filter( - (attr) => attr.key === "channel" && attr.value === unknownChannel - ).length - ).toBeGreaterThan(0); + expect(transferEvent.attributes.filter((attr) => attr.key === 'recipient' && attr.value === bobAddress).length).toBeGreaterThan(0); + expect(transferEvent.attributes.filter((attr) => attr.key === 'amount' && attr.value.includes(AtomDenom)).length).toBeGreaterThan(0); + expect(transferEvent.attributes.filter((attr) => attr.key === 'channel' && attr.value === unknownChannel).length).toBeGreaterThan(0); }); }); - it("cw-ics20-test-single-step-handle_ibc_packet_receive_native_remote_chain-has-relayer-fee-should-be-deducted", async () => { + it('cw-ics20-test-single-step-handle_ibc_packet_receive_native_remote_chain-has-relayer-fee-should-be-deducted', async () => { // setup relayer fee - const relayerFee = "100000"; + const relayerFee = '100000'; await ics20Contract.updateConfig({ - relayerFee: [{ prefix: "tron-testnet", fee: relayerFee }], + relayerFee: [{ prefix: 'tron-testnet', fee: relayerFee }] }); const icsPackage: FungibleTokenPacketData = { @@ -1663,107 +1250,75 @@ describe.only("IBCModule", () => { denom: airiIbcDenom, receiver: bobAddress, sender: oraibridgeSenderAddress, - memo: parseToIbcWasmMemo(bobAddress, channel, oraiIbcDenom), + memo: parseToIbcWasmMemo(bobAddress, channel, oraiIbcDenom) }; // transfer from cosmos to oraichain, should pass let result = await cosmosChain.ibc.sendPacketReceive({ packet: { data: toBinary(icsPackage), - ...packetData, + ...packetData }, - relayer: relayerAddress, + relayer: relayerAddress }); const hasRelayerFee = result.events.find( - (event) => - event.type === "wasm" && - event.attributes.find( - (attr) => attr.key === "to" && attr.value === relayerAddress - ) && - event.attributes.find( - (attr) => attr.key === "amount" && attr.value === relayerFee - ) + (event) => event.type === 'wasm' && event.attributes.find((attr) => attr.key === 'to' && attr.value === relayerAddress) && event.attributes.find((attr) => attr.key === 'amount' && attr.value === relayerFee) ); expect(hasRelayerFee).not.toBeUndefined(); - expect( - result.attributes.find( - (attr) => attr.key === "relayer_fee" && attr.value === relayerFee - ) - ).not.toBeUndefined(); + expect(result.attributes.find((attr) => attr.key === 'relayer_fee' && attr.value === relayerFee)).not.toBeUndefined(); }); it.each<[string, string]>([ - [parseToIbcWasmMemo(bobAddress, channel, oraiIbcDenom), "20000000"], - [parseToIbcWasmMemo(bobAddress, "", "orai"), "10000000"], - ])( - "cw-ics20-test-single-step-ibc-handle_ibc_packet_receive_native_remote_chain-has-token-fee-should-be-deducted", - async (memo, expectedTokenFee) => { - // setup relayer fee - await ics20Contract.updateConfig({ - tokenFee: [ - { - token_denom: airiIbcDenom, - ratio: { nominator: 1, denominator: 10 }, - }, - ], - }); + [parseToIbcWasmMemo(bobAddress, channel, oraiIbcDenom), '20000000'], + [parseToIbcWasmMemo(bobAddress, '', 'orai'), '10000000'] + ])('cw-ics20-test-single-step-ibc-handle_ibc_packet_receive_native_remote_chain-has-token-fee-should-be-deducted', async (memo, expectedTokenFee) => { + // setup relayer fee + await ics20Contract.updateConfig({ + tokenFee: [ + { + token_denom: airiIbcDenom, + ratio: { nominator: 1, denominator: 10 } + } + ] + }); - const icsPackage: FungibleTokenPacketData = { - amount: ibcTransferAmount, - denom: airiIbcDenom, - receiver: bobAddress, - sender: oraibridgeSenderAddress, - memo, - }; - // transfer from cosmos to oraichain, should pass - let result = await cosmosChain.ibc.sendPacketReceive({ - packet: { - data: toBinary(icsPackage), - ...packetData, - }, - relayer: relayerAddress, - }); - const hasTokenFee = result.events.filter( - (event) => - event.type === "wasm" && - event.attributes.find( - (attr) => attr.key === "to" && attr.value === senderAddress - ) - ); - expect(hasTokenFee).not.toBeUndefined(); - expect( - result.attributes.find( - (attr) => attr.key === "token_fee" && expectedTokenFee - ) - ).not.toBeUndefined(); - } - ); + const icsPackage: FungibleTokenPacketData = { + amount: ibcTransferAmount, + denom: airiIbcDenom, + receiver: bobAddress, + sender: oraibridgeSenderAddress, + memo + }; + // transfer from cosmos to oraichain, should pass + let result = await cosmosChain.ibc.sendPacketReceive({ + packet: { + data: toBinary(icsPackage), + ...packetData + }, + relayer: relayerAddress + }); + const hasTokenFee = result.events.filter((event) => event.type === 'wasm' && event.attributes.find((attr) => attr.key === 'to' && attr.value === senderAddress)); + expect(hasTokenFee).not.toBeUndefined(); + expect(result.attributes.find((attr) => attr.key === 'token_fee' && expectedTokenFee)).not.toBeUndefined(); + }); it.each<[string, string, string]>([ - [ - parseToIbcWasmMemo(bobAddress, channel, airiIbcDenom), - "20000000", - "100000", - ], - [ - parseToIbcWasmMemo(bridgeReceiver, channel, airiIbcDenom), - "20000000", - "200000", - ], // double deducted when there's an outgoing ibc msg after receiving the packet - [parseToIbcWasmMemo(bobAddress, "", "orai"), "10000000", "100000"], + [parseToIbcWasmMemo(bobAddress, channel, airiIbcDenom), '20000000', '100000'], + [parseToIbcWasmMemo(bridgeReceiver, channel, airiIbcDenom), '20000000', '200000'], // double deducted when there's an outgoing ibc msg after receiving the packet + [parseToIbcWasmMemo(bobAddress, '', 'orai'), '10000000', '100000'] ])( - "test-handle_ibc_packet_receive_native_remote_chain-has-both-token-fee-and-relayer-fee-should-be-both-deducted-given memo %s should give expected token fee %s and expected relayer fee %s", + 'test-handle_ibc_packet_receive_native_remote_chain-has-both-token-fee-and-relayer-fee-should-be-both-deducted-given memo %s should give expected token fee %s and expected relayer fee %s', async (memo, expectedTokenFee, expectedRelayerFee) => { // setup relayer fee - const relayerFee = "100000"; + const relayerFee = '100000'; await ics20Contract.updateConfig({ tokenFee: [ { token_denom: airiIbcDenom, - ratio: { nominator: 1, denominator: 10 }, + ratio: { nominator: 1, denominator: 10 } }, - { token_denom: "orai", ratio: { nominator: 1, denominator: 10 } }, + { token_denom: 'orai', ratio: { nominator: 1, denominator: 10 } } ], - relayerFee: [{ prefix: "tron-testnet", fee: relayerFee }], + relayerFee: [{ prefix: 'tron-testnet', fee: relayerFee }] }); const icsPackage: FungibleTokenPacketData = { @@ -1771,81 +1326,42 @@ describe.only("IBCModule", () => { denom: airiIbcDenom, receiver: bobAddress, sender: oraibridgeSenderAddress, - memo, + memo }; // transfer from cosmos to oraichain, should pass let result = await cosmosChain.ibc.sendPacketReceive({ packet: { data: toBinary(icsPackage), - ...packetData, + ...packetData }, - relayer: relayerAddress, + relayer: relayerAddress }); const hasRelayerFee = result.events.find( - (event) => - event.type === "wasm" && - event.attributes.find( - (attr) => attr.key === "to" && attr.value === relayerAddress - ) && - event.attributes.find( - (attr) => - attr.key === "amount" && attr.value === expectedRelayerFee - ) + (event) => event.type === 'wasm' && event.attributes.find((attr) => attr.key === 'to' && attr.value === relayerAddress) && event.attributes.find((attr) => attr.key === 'amount' && attr.value === expectedRelayerFee) ); expect(hasRelayerFee).not.toBeUndefined(); - expect( - result.attributes.find( - (attr) => - attr.key === "relayer_fee" && attr.value === expectedRelayerFee - ) - ).not.toBeUndefined(); + expect(result.attributes.find((attr) => attr.key === 'relayer_fee' && attr.value === expectedRelayerFee)).not.toBeUndefined(); const hasTokenFee = result.events.find( - (event) => - event.type === "wasm" && - event.attributes.find( - (attr) => attr.key === "to" && attr.value === senderAddress - ) && - event.attributes.find( - (attr) => attr.key === "amount" && attr.value === expectedTokenFee - ) + (event) => event.type === 'wasm' && event.attributes.find((attr) => attr.key === 'to' && attr.value === senderAddress) && event.attributes.find((attr) => attr.key === 'amount' && attr.value === expectedTokenFee) ); expect(hasTokenFee).not.toBeUndefined(); - expect( - result.attributes.find( - (attr) => - attr.key === "token_fee" && attr.value === expectedTokenFee - ) - ).not.toBeUndefined(); + expect(result.attributes.find((attr) => attr.key === 'token_fee' && attr.value === expectedTokenFee)).not.toBeUndefined(); } ); - it.each<[string, string, string, string, string]>([ - [ - ibcTransferAmount, - ibcTransferAmount, - "10000000", - "90000000", - ibcTransferAmount, - ], - ])( - "cw-ics20-test-single-step-handle_ibc_packet_receive_native_remote_chain-deducted-amount-is-zero-should-still-charge-fees", - async ( - transferAmount, - relayerFee, - expectedTokenFee, - expectedRelayerFee, - expectedTotalFee - ) => { + it.each<[string, string, string, string, string]>([[ibcTransferAmount, ibcTransferAmount, '10000000', '90000000', ibcTransferAmount]])( + 'cw-ics20-test-single-step-handle_ibc_packet_receive_native_remote_chain-deducted-amount-is-zero-should-still-charge-fees', + async (transferAmount, relayerFee, expectedTokenFee, expectedRelayerFee, expectedTotalFee) => { await ics20Contract.updateConfig({ tokenFee: [ { token_denom: airiIbcDenom, - ratio: { nominator: 1, denominator: 10 }, - }, + ratio: { nominator: 1, denominator: 10 } + } ], - relayerFee: [{ prefix: "tron-testnet", fee: relayerFee }], + relayerFee: [{ prefix: 'tron-testnet', fee: relayerFee }] }); const icsPackage: FungibleTokenPacketData = { @@ -1853,110 +1369,84 @@ describe.only("IBCModule", () => { denom: airiIbcDenom, receiver: bobAddress, sender: oraibridgeSenderAddress, - memo: parseToIbcWasmMemo(bobAddress, "", "orai"), + memo: parseToIbcWasmMemo(bobAddress, '', 'orai') }; // transfer from cosmos to oraichain, should pass let result = await cosmosChain.ibc.sendPacketReceive({ packet: { data: toBinary(icsPackage), - ...packetData, + ...packetData }, - relayer: relayerAddress, + relayer: relayerAddress }); const hasFees = result.events.find( - (event) => - event.type === "wasm" && - event.attributes.find( - (attr) => attr.key === "to" && attr.value === senderAddress - ) && - event.attributes.find( - (attr) => attr.key === "amount" && attr.value === expectedTotalFee - ) + (event) => event.type === 'wasm' && event.attributes.find((attr) => attr.key === 'to' && attr.value === senderAddress) && event.attributes.find((attr) => attr.key === 'amount' && attr.value === expectedTotalFee) ); expect(hasFees).not.toBeUndefined(); - expect( - result.attributes.find( - (attr) => - attr.key === "token_fee" && attr.value === expectedTokenFee - ) - ).not.toBeUndefined(); - expect( - result.attributes.find( - (attr) => - attr.key === "relayer_fee" && attr.value === expectedRelayerFee - ) - ).not.toBeUndefined(); + expect(result.attributes.find((attr) => attr.key === 'token_fee' && attr.value === expectedTokenFee)).not.toBeUndefined(); + expect(result.attributes.find((attr) => attr.key === 'relayer_fee' && attr.value === expectedRelayerFee)).not.toBeUndefined(); } ); // execute transfer to remote test cases - it("test-execute_transfer_back_to_remote_chain-native-FAILED-no-funds-sent", async () => { - oraiClient.app.bank.setBalance( - senderAddress, - coins(initialBalanceAmount, ORAI) - ); + it('test-execute_transfer_back_to_remote_chain-native-FAILED-no-funds-sent', async () => { + oraiClient.app.bank.setBalance(senderAddress, coins(initialBalanceAmount, ORAI)); try { await ics20Contract.transferToRemote( { - localChannelId: "1", + localChannelId: '1', memo: null, - remoteAddress: "a", - remoteDenom: "a", - timeout: 60, + remoteAddress: 'a', + remoteDenom: 'a', + timeout: 60 }, - "auto", + 'auto', null ); } catch (error) { - expect(error.toString()).toContain("No funds sent"); + expect(error.toString()).toContain('No funds sent'); } }); - it("test-execute_transfer_back_to_remote_chain-native-FAILED-no-mapping-found", async () => { - oraiClient.app.bank.setBalance( - senderAddress, - coins(initialBalanceAmount, ORAI) - ); + it('test-execute_transfer_back_to_remote_chain-native-FAILED-no-mapping-found', async () => { + oraiClient.app.bank.setBalance(senderAddress, coins(initialBalanceAmount, ORAI)); try { await ics20Contract.transferToRemote( { - localChannelId: "1", + localChannelId: '1', memo: null, - remoteAddress: "a", - remoteDenom: "a", - timeout: 60, + remoteAddress: 'a', + remoteDenom: 'a', + timeout: 60 }, - "auto", + 'auto', null, - [{ denom: ORAI, amount: "100" }] + [{ denom: ORAI, amount: '100' }] ); } catch (error) { - expect(error.toString()).toContain("Could not find the mapping pair"); + expect(error.toString()).toContain('Could not find the mapping pair'); } }); - it("test-execute_transfer_back_to_remote_chain-native-FAILED-no-mapping-found", async () => { - oraiClient.app.bank.setBalance( - senderAddress, - coins(initialBalanceAmount, ORAI) - ); + it('test-execute_transfer_back_to_remote_chain-native-FAILED-no-mapping-found', async () => { + oraiClient.app.bank.setBalance(senderAddress, coins(initialBalanceAmount, ORAI)); try { await ics20Contract.transferToRemote( { - localChannelId: "1", + localChannelId: '1', memo: null, - remoteAddress: "a", - remoteDenom: "a", - timeout: 60, + remoteAddress: 'a', + remoteDenom: 'a', + timeout: 60 }, - "auto", + 'auto', null, - [{ denom: ORAI, amount: "100" }] + [{ denom: ORAI, amount: '100' }] ); } catch (error) { - expect(error.toString()).toContain("Could not find the mapping pair"); + expect(error.toString()).toContain('Could not find the mapping pair'); } }); }); diff --git a/simulate-tests/package.json b/simulate-tests/package.json index 411d0f9..3533b81 100644 --- a/simulate-tests/package.json +++ b/simulate-tests/package.json @@ -11,7 +11,7 @@ "@cosmjs/stargate": "^0.31.0", "@oraichain/common-contracts-build": "^1.0.35", "@oraichain/common-contracts-sdk": "^1.0.31", - "@oraichain/cw-simulate": "^2.8.78", + "@oraichain/cw-simulate": "^2.8.82", "@oraichain/oraidex-common": "^1.0.76", "@oraichain/oraidex-contracts-build": "1.0.22", "@oraichain/oraidex-contracts-sdk": "^1.0.40", diff --git a/simulate-tests/yarn.lock b/simulate-tests/yarn.lock index ffbef77..05f7daa 100644 --- a/simulate-tests/yarn.lock +++ b/simulate-tests/yarn.lock @@ -1417,10 +1417,10 @@ resolved "https://registry.yarnpkg.com/@oraichain/common-contracts-sdk/-/common-contracts-sdk-1.0.31.tgz#595f93b168438d69d64896909b37855c9afc92fb" integrity sha512-s8H20RXy5gCnu3DnM7L5ClQyj2mdQpbSBpZrXCpIAX9qY0LKsDdZG3sYaDQ8+VN333jz9Pp/qGWdFSYD+6PBsg== -"@oraichain/cosmwasm-vm-js@^0.2.81": - version "0.2.81" - resolved "https://registry.yarnpkg.com/@oraichain/cosmwasm-vm-js/-/cosmwasm-vm-js-0.2.81.tgz#1c3350ce1eac169bd94a51641267ec96eef89114" - integrity sha512-fvBpZKxC/B5eEmn754AUrdU0OtRUOEIOMAXJiI1HLcYjfj5bjtbiLJVjpHeMMNEXdUYU9tUqYJlhaDO50+wK8A== +"@oraichain/cosmwasm-vm-js@^0.2.84": + version "0.2.84" + resolved "https://registry.yarnpkg.com/@oraichain/cosmwasm-vm-js/-/cosmwasm-vm-js-0.2.84.tgz#5b8edb42a2bc3ea1b92bc75839c016aa18595b47" + integrity sha512-KYe4jqB8ly2/5sb3LglkGmWt8YTAX7hqQulkjU93WByCIc7zGv4P7r8zmXHULFq2dD5i+eHHbeYXtz1VslTI4w== dependencies: "@cosmjs/amino" "^0.31.0" "@cosmjs/crypto" "^0.31.0" @@ -1431,17 +1431,17 @@ elliptic "^6.5.4" secp256k1 "^4.0.3" -"@oraichain/cw-simulate@^2.8.78": - version "2.8.78" - resolved "https://registry.yarnpkg.com/@oraichain/cw-simulate/-/cw-simulate-2.8.78.tgz#c448b4f18a0bbc982c1bc92e467babd07aaeb9f3" - integrity sha512-POYi7F2RFGOycoDTmBp6VJzYcWJjxu3w8Iv0hc7LdN4dc6YCrmDxxOepzFJlp9caJEWTH3CCMPaY4ieWM+k2hA== +"@oraichain/cw-simulate@^2.8.82": + version "2.8.82" + resolved "https://registry.yarnpkg.com/@oraichain/cw-simulate/-/cw-simulate-2.8.82.tgz#2daa4ed750b86c92075f8059f8409e0848677ff4" + integrity sha512-QLDQcs/hRItZqjpo5KVmWfxj/hI0mje38MIQ7nP7Ub156B8Tr2BU9VTniOGBp1jy00NFifgH43tRDV70lzjDBw== dependencies: "@cosmjs/amino" "^0.31.0" "@cosmjs/cosmwasm-stargate" "^0.31.0" "@cosmjs/crypto" "^0.31.0" "@cosmjs/encoding" "^0.31.0" "@kiruse/serde" "^0.8.0-rc.6" - "@oraichain/cosmwasm-vm-js" "^0.2.81" + "@oraichain/cosmwasm-vm-js" "^0.2.84" eventemitter3 "^5.0.0" protobufjs "^7.2.3" ts-results "^3.3.0"