diff --git a/Cargo.lock b/Cargo.lock index cf755476..b194a86f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -341,6 +341,7 @@ dependencies = [ "cw-storage-plus 1.1.0", "cw2", "cw20", + "cw20-base", "getrandom", "hex", "schemars 0.8.12", @@ -430,6 +431,24 @@ dependencies = [ "serde", ] +[[package]] +name = "cw20-base" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcd279230b08ed8afd8be5828221622bd5b9ce25d0b01d58bad626c6ce0169c" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.0.1", + "cw-utils", + "cw2", + "cw20", + "schemars 0.8.12", + "semver", + "serde", + "thiserror", +] + [[package]] name = "debug_print" version = "1.0.0" diff --git a/contracts/cosmwasm-vm/cw-intents-v1/Cargo.toml b/contracts/cosmwasm-vm/cw-intents-v1/Cargo.toml index 865ac971..b550a142 100644 --- a/contracts/cosmwasm-vm/cw-intents-v1/Cargo.toml +++ b/contracts/cosmwasm-vm/cw-intents-v1/Cargo.toml @@ -39,9 +39,10 @@ common ={ git = "https://github.com/icon-project/IBC-Integration.git",branch="ma serde-json-wasm = {workspace=true} hex="*" cw20="*" +cw20-base="*" [dev-dependencies] cosmwasm = "0.7.2" -cw-multi-test = "0.16.2" +cw-multi-test = "*" getrandom = {version = "0.2", default-features = false, features = ["custom"]} diff --git a/contracts/cosmwasm-vm/cw-intents-v1/src/contract.rs b/contracts/cosmwasm-vm/cw-intents-v1/src/contract.rs index a788edf6..95b11e19 100644 --- a/contracts/cosmwasm-vm/cw-intents-v1/src/contract.rs +++ b/contracts/cosmwasm-vm/cw-intents-v1/src/contract.rs @@ -7,7 +7,7 @@ use common::{ }; use cosmwasm_std::{to_binary, Addr, BankMsg, Coin}; use cw20::Cw20ExecuteMsg; -use events::create_send_message_event; +use events::{create_order_fill_event, create_send_message_event}; use super::*; @@ -27,6 +27,8 @@ impl<'a> CwIntentV1Service<'a> { self.set_nid(deps.storage, msg.nid)?; self.set_fee_handler(deps.storage, deps.api.addr_validate(&msg.fee_handler)?)?; self.set_deposit_id(deps.storage, 0)?; + let relayer = deps.api.addr_validate(&msg.relayer)?; + self.set_relayer(deps.storage, relayer)?; Ok(Response::new()) } @@ -54,15 +56,13 @@ impl<'a> CwIntentV1Service<'a> { env: Env, info: MessageInfo, ) -> Result { - let stored = self.get_order(deps.storage, order.id)?; + let self_nid = self.get_nid(deps.storage)?; + + if order.dst_nid != self_nid { + return Err(ContractError::InvalidDstNId); + } let order_bytes = order.rlp_bytes(); let order_hash = keccak256(&order_bytes); - if keccak256(&stored.rlp_bytes()) != order_hash { - return Err(ContractError::DecodeError { - error: "Hash Mismatch".to_string(), - }); - } - if self.is_order_finished(deps.storage, &order_hash) { return Err(ContractError::OrderAlreadyComplete); } @@ -117,6 +117,9 @@ impl<'a> CwIntentV1Service<'a> { }; let mut response = Response::new(); + let order_fill_event = + create_order_fill_event(&order_fill, remaining_amount, fee, fill_amount); + if order.src_nid == order.dst_nid { response = self.resolve_fill(deps, env, order.src_nid, order_fill)?; } else { @@ -128,7 +131,12 @@ impl<'a> CwIntentV1Service<'a> { let event = create_send_message_event(order.src_nid, sn, msg.rlp_bytes().to_vec()); response = response.add_event(event); } - response = response.add_messages(vec![fee_transfer, receiver_transfer]); + + response = response.add_message(receiver_transfer); + if fee > 0 { + response = response.add_message(fee_transfer); + } + response = response.add_event(order_fill_event); Ok(response) } @@ -141,6 +149,11 @@ impl<'a> CwIntentV1Service<'a> { conn_sn: u128, order_msg: OrderMsg, ) -> Result { + let relayer = self.get_relayer(deps.storage)?; + if info.sender != relayer { + return Err(ContractError::Unauthorized {}); + } + if self.have_received(deps.storage, (src_network.clone(), conn_sn)) { return Err(ContractError::MessageAlreadyReceived); } @@ -321,9 +334,9 @@ impl<'a> CwIntentV1Service<'a> { pub fn is_native(&self, deps: Deps, denom: &String) -> bool { if let Some(addr) = deps.api.addr_validate(denom).ok() { - return self.is_contract(deps, &addr); + return !self.is_contract(deps, &addr); } - return false; + return true; } pub fn get_next_deposit_id(&self, storage: &mut dyn Storage) -> StdResult { diff --git a/contracts/cosmwasm-vm/cw-intents-v1/src/errors.rs b/contracts/cosmwasm-vm/cw-intents-v1/src/errors.rs index 7b8498ec..8541eb75 100644 --- a/contracts/cosmwasm-vm/cw-intents-v1/src/errors.rs +++ b/contracts/cosmwasm-vm/cw-intents-v1/src/errors.rs @@ -25,4 +25,6 @@ pub enum ContractError { InvalidMessageType, #[error("MessageAlreadyReceived")] MessageAlreadyReceived, + #[error("InvalidDstNId")] + InvalidDstNId, } diff --git a/contracts/cosmwasm-vm/cw-intents-v1/src/events.rs b/contracts/cosmwasm-vm/cw-intents-v1/src/events.rs index 4294d5d6..9ae74ce0 100644 --- a/contracts/cosmwasm-vm/cw-intents-v1/src/events.rs +++ b/contracts/cosmwasm-vm/cw-intents-v1/src/events.rs @@ -17,13 +17,21 @@ pub fn create_swap_order_event(order: &SwapOrder) -> Event { .add_attribute("token", order.token.to_string()) } -pub fn create_order_fill_event(fill: &OrderFill) -> Event { +pub fn create_order_fill_event( + fill: &OrderFill, + remaining_amount: u128, + fee: u128, + fill_amount: u128, +) -> Event { Event::new("OrderFill") .add_attribute("id", fill.id.to_string()) - .add_attribute("amount", fill.amount.to_string()) + .add_attribute("payout", fill.amount.to_string()) .add_attribute("solver_address", fill.solver_address.to_string()) .add_attribute("order_bytes", hex::encode(&fill.order_bytes)) .add_attribute("closed", fill.closed.to_string()) + .add_attribute("remaining_amount", remaining_amount.to_string()) + .add_attribute("fee", fee.to_string()) + .add_attribute("fill_amount", fill_amount.to_string()) } pub fn create_send_message_event(nid: String, conn_sn: u128, msg: Vec) -> Event { diff --git a/contracts/cosmwasm-vm/cw-intents-v1/src/lib.rs b/contracts/cosmwasm-vm/cw-intents-v1/src/lib.rs index dd5cf79b..f1675e0d 100644 --- a/contracts/cosmwasm-vm/cw-intents-v1/src/lib.rs +++ b/contracts/cosmwasm-vm/cw-intents-v1/src/lib.rs @@ -15,7 +15,7 @@ use cosmwasm_std::{ use cw2::set_contract_version; use cw_storage_plus::{Item, Map}; pub use errors::*; -use msg::{ExecuteMsg, QueryMsg}; +use msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use state::CwIntentV1Service; use thiserror::Error; pub use types::*; @@ -119,5 +119,8 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { to_json_binary(&call_service.get_order(deps.storage, id).unwrap()) } QueryMsg::GetDepositId {} => to_json_binary(&call_service.get_deposit_id(deps.storage)), + QueryMsg::GetFeeHandler {} => { + to_json_binary(&call_service.get_fee_handler(deps.storage).unwrap()) + } } } diff --git a/contracts/cosmwasm-vm/cw-intents-v1/src/msg.rs b/contracts/cosmwasm-vm/cw-intents-v1/src/msg.rs index 54c9764e..7ab5832a 100644 --- a/contracts/cosmwasm-vm/cw-intents-v1/src/msg.rs +++ b/contracts/cosmwasm-vm/cw-intents-v1/src/msg.rs @@ -1,5 +1,13 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; +#[cw_serde] +pub struct InstantiateMsg { + pub fee_handler: String, + pub nid: String, + pub fee: u8, + pub relayer: String, +} + #[cw_serde] pub enum ExecuteMsg { Swap { @@ -44,4 +52,6 @@ pub enum QueryMsg { #[returns(u64)] GetDepositId {}, + #[returns(String)] + GetFeeHandler {}, } diff --git a/contracts/cosmwasm-vm/cw-intents-v1/src/state.rs b/contracts/cosmwasm-vm/cw-intents-v1/src/state.rs index 7628231a..e0d5d0d8 100644 --- a/contracts/cosmwasm-vm/cw-intents-v1/src/state.rs +++ b/contracts/cosmwasm-vm/cw-intents-v1/src/state.rs @@ -14,6 +14,7 @@ pub struct CwIntentV1Service<'a> { finished_orders: Map<'a, Vec, bool>, conn_sn: Item<'a, u128>, receipts: Map<'a, (String, u128), bool>, + relayer: Item<'a, Addr>, } impl<'a> Default for CwIntentV1Service<'a> { @@ -34,6 +35,7 @@ impl<'a> CwIntentV1Service<'a> { finished_orders: Map::new(StorageKey::FinishedOrders.as_str()), conn_sn: Item::new(StorageKey::ConnectionSN.as_str()), receipts: Map::new(StorageKey::Receipts.as_str()), + relayer: Item::new(StorageKey::Relayer.as_str()), } } @@ -41,6 +43,10 @@ impl<'a> CwIntentV1Service<'a> { self.deposit_id.load(storage).unwrap_or(0) } + pub fn get_relayer(&self, storage: &dyn Storage) -> StdResult { + self.relayer.load(storage) + } + pub fn get_nid(&self, storage: &dyn Storage) -> StdResult { self.nid.load(storage) } @@ -80,6 +86,10 @@ impl<'a> CwIntentV1Service<'a> { self.deposit_id.save(storage, &value) } + pub fn set_relayer(&self, storage: &mut dyn Storage, value: Addr) -> StdResult<()> { + self.relayer.save(storage, &value) + } + pub fn set_conn_sn(&self, storage: &mut dyn Storage, value: u128) -> StdResult<()> { self.conn_sn.save(storage, &value) } diff --git a/contracts/cosmwasm-vm/cw-intents-v1/src/types.rs b/contracts/cosmwasm-vm/cw-intents-v1/src/types.rs index e01dfd48..38da0cfd 100644 --- a/contracts/cosmwasm-vm/cw-intents-v1/src/types.rs +++ b/contracts/cosmwasm-vm/cw-intents-v1/src/types.rs @@ -6,12 +6,6 @@ use serde::Serialize; pub const ORDER_FILL: u8 = 1; pub const ORDER_CANCEL: u8 = 2; -#[cw_serde] -pub struct InstantiateMsg { - pub fee_handler: String, - pub nid: String, - pub fee: u8, -} #[cw_serde] pub enum StorageKey { @@ -25,6 +19,7 @@ pub enum StorageKey { FinishedOrders, ConnectionSN, Receipts, + Relayer, } impl StorageKey { @@ -40,6 +35,7 @@ impl StorageKey { StorageKey::FinishedOrders => "finished_orders", StorageKey::ConnectionSN => "conn_sn", StorageKey::Receipts => "receipts", + StorageKey::Relayer => "relayer", } } } diff --git a/contracts/cosmwasm-vm/cw-intents-v1/tests/test_contract.rs b/contracts/cosmwasm-vm/cw-intents-v1/tests/test_contract.rs new file mode 100644 index 00000000..53aa4aa2 --- /dev/null +++ b/contracts/cosmwasm-vm/cw-intents-v1/tests/test_contract.rs @@ -0,0 +1,536 @@ +use common::rlp::Encodable; +use cosmwasm_std::{ + testing::{mock_dependencies, mock_env, mock_info}, + to_binary, Addr, Coin, CosmosMsg, StdError, SubMsg, Uint128, WasmMsg, +}; +use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; +use cw_intents_v1::{ + errors::ContractError, + execute, instantiate, + msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, + query, + state::CwIntentV1Service, + types::{OrderFill, OrderMsg, SwapOrder}, + OrderCancel, +}; +use cw_multi_test::{App, ContractWrapper, Executor}; + +const MOCK_CONTRACT_ADDR: &str = "contract"; +const USER1: &str = "user1"; +const USER2: &str = "user2"; +const FEE_HANDLER: &str = "fee_handler"; +const NATIVE_DENOM: &str = "uatom"; +const CW20_TOKEN: &str = "cw20_token"; + +fn mock_app() -> App { + App::default() +} + +fn store_code(app: &mut App) -> u64 { + let contract = ContractWrapper::new(execute, instantiate, query); + app.store_code(Box::new(contract)) +} + +fn instantiate_contract(app: &mut App, code_id: u64, sender: &str) -> Addr { + app.instantiate_contract( + code_id, + Addr::unchecked(sender), + &InstantiateMsg { + nid: "src-nid".to_string(), + fee_handler: FEE_HANDLER.to_string(), + fee: 1, + relayer: sender.to_string(), + }, + &[], + "CwIntentV1", + None, + ) + .unwrap() +} + +#[test] +fn proper_initialization() { + let mut app = mock_app(); + let code_id = store_code(&mut app); + let contract_addr = instantiate_contract(&mut app, code_id, USER1); + + // Query the deposit ID + let deposit_id: u128 = app + .wrap() + .query_wasm_smart(contract_addr, &QueryMsg::GetDepositId {}) + .unwrap(); + assert_eq!(deposit_id, 0); +} + +fn fund_account(app: &mut App, account: String, amount: u128, denom: String) { + app.sudo(cw_multi_test::SudoMsg::Bank( + cw_multi_test::BankSudo::Mint { + to_address: account, + amount: vec![Coin { + denom: denom, + amount: Uint128::new(amount), + }], + }, + )) + .unwrap(); +} + +fn to_swap_msg(order: &SwapOrder) -> ExecuteMsg { + ExecuteMsg::Swap { + dst_nid: order.dst_nid.clone(), + token: order.token.clone(), + amount: order.amount, + to_token: order.to_token.clone(), + destination_address: order.destination_address.clone(), + min_receive: order.min_receive, + data: order.data.clone(), + } +} + +#[test] +fn test_swap_native_token() { + let mut app = mock_app(); + let code_id = store_code(&mut app); + let contract_addr = instantiate_contract(&mut app, code_id, USER1); + + // Fund USER1 with native tokens + fund_account(&mut app, USER1.to_string(), 1000, NATIVE_DENOM.to_string()); + + // Execute swap + let swap_msg = ExecuteMsg::Swap { + dst_nid: "dst-nid".to_string(), + token: NATIVE_DENOM.to_string(), + amount: 100, + to_token: "to_token".to_string(), + destination_address: USER2.to_string(), + min_receive: 90, + data: hex::decode("deadbeef").unwrap(), + }; + + let res = app.execute_contract( + Addr::unchecked(USER1), + contract_addr.clone(), + &swap_msg, + &[Coin { + denom: NATIVE_DENOM.to_string(), + amount: Uint128::new(100), + }], + ); + println!("{:?}", res); + let response = res.unwrap(); + + // Check if the swap event is emitted + assert!(response.events.iter().any( + |e| e.ty == "wasm-SwapOrder" && e.attributes.iter().any(|attr| attr.key == "to_token") + )); + + // Query the deposit ID (should be incremented) + let deposit_id: u128 = app + .wrap() + .query_wasm_smart(contract_addr, &QueryMsg::GetDepositId {}) + .unwrap(); + assert_eq!(deposit_id, 1); +} + +fn init_cw20_contract(app: &mut App, owner: String, denom: String) -> Addr { + let cw20_code_id = app.store_code(Box::new(ContractWrapper::new( + cw20_base::contract::execute, + cw20_base::contract::instantiate, + cw20_base::contract::query, + ))); + let cw20_addr = app + .instantiate_contract( + cw20_code_id, + Addr::unchecked(owner.clone()), + &cw20_base::msg::InstantiateMsg { + name: "Test Token".to_string(), + symbol: denom, + decimals: 6, + initial_balances: vec![cw20::Cw20Coin { + address: owner.to_string(), + amount: Uint128::new(1000), + }], + mint: None, + marketing: None, + }, + &[], + "Test Token", + None, + ) + .unwrap(); + cw20_addr +} +#[test] +fn test_swap_cw20_token() { + let mut app = mock_app(); + let code_id = store_code(&mut app); + let contract_addr = instantiate_contract(&mut app, code_id, USER1); + + let cw20_addr = init_cw20_contract(&mut app, USER1.to_string(), "test".to_owned()); + + // Approve the contract to spend USER1's tokens + app.execute_contract( + Addr::unchecked(USER1), + cw20_addr.clone(), + &cw20::Cw20ExecuteMsg::IncreaseAllowance { + spender: contract_addr.to_string(), + amount: Uint128::new(100), + expires: None, + }, + &[], + ) + .unwrap(); + + // Execute swap + let swap_msg = ExecuteMsg::Swap { + dst_nid: "dst-nid".to_string(), + token: cw20_addr.to_string(), + amount: 100, + to_token: "to_token".to_string(), + destination_address: USER2.to_string(), + min_receive: 90, + data: hex::decode("deadbeef").unwrap(), + }; + + let res = app + .execute_contract( + Addr::unchecked(USER1), + contract_addr.clone(), + &swap_msg, + &[], + ) + .unwrap(); + + // Check if the swap event is emitted + assert!(res + .events + .iter() + .any(|e| e.ty == "wasm-SwapOrder" && e.attributes.iter().any(|attr| attr.key == "amount"))); + + // Query the deposit ID (should be incremented) + let deposit_id: u128 = app + .wrap() + .query_wasm_smart(contract_addr, &QueryMsg::GetDepositId {}) + .unwrap(); + assert_eq!(deposit_id, 1); +} + +#[test] +fn test_fill_order() { + let mut app = mock_app(); + let code_id = store_code(&mut app); + let contract_addr = instantiate_contract(&mut app, code_id, USER1); + fund_account(&mut app, USER2.to_string(), 200, "to_token".to_string()); + // Fill the order + let fill_msg = ExecuteMsg::Fill { + id: 1, + emitter: contract_addr.to_string(), + src_nid: "dst-nid".to_string(), + dst_nid: "src-nid".to_string(), + creator: USER1.to_string(), + destination_address: USER2.to_string(), + token: NATIVE_DENOM.to_string(), + amount: 100, + to_token: "to_token".to_string(), + min_receive: 90, + data: hex::decode("deadbeef").unwrap(), + fill_amount: 90, + solver_address: USER2.to_string(), + }; + + let res = app.execute_contract( + Addr::unchecked(USER2), + contract_addr.clone(), + &fill_msg, + &[Coin { + denom: "to_token".to_string(), + amount: Uint128::new(100), + }], + ); + assert!(res.is_ok()); +} + +#[test] +fn test_receive_msg() { + let mut app = mock_app(); + let code_id = store_code(&mut app); + let contract_addr = instantiate_contract(&mut app, code_id, USER1); + + let order = SwapOrder { + id: 1, + emitter: contract_addr.to_string(), + src_nid: "src-nid".to_string(), + dst_nid: "src-nid".to_string(), + creator: USER1.to_string(), + destination_address: USER2.to_string(), + token: NATIVE_DENOM.to_string(), + amount: 100, + to_token: "to_token".to_string(), + min_receive: 10, + data: hex::decode("deadbeef").unwrap(), + }; + fund_account(&mut app, USER1.to_string(), 200, NATIVE_DENOM.to_string()); + fund_account( + &mut app, + contract_addr.to_string(), + 200, + "to_token".to_string(), + ); + let swap_msg = to_swap_msg(&order); + + app.execute_contract( + Addr::unchecked(USER1), + contract_addr.clone(), + &swap_msg, + &[Coin { + denom: NATIVE_DENOM.to_string(), + amount: Uint128::new(100), + }], + ) + .unwrap(); + + // Create a mock OrderFill + let order_fill = OrderFill { + id: 1, + order_bytes: order.rlp_bytes().to_vec(), + solver_address: USER2.to_string(), + amount: 100, + closed: true, + }; + + // Create a mock OrderMsg + let order_msg = OrderMsg { + msg_type: 1, // ORDER_FILL + message: order_fill.rlp_bytes().to_vec(), + }; + + // Execute RecvMessage + let recv_msg = ExecuteMsg::RecvMessage { + src_network: "src-nid".to_string(), + conn_sn: 1, + msg: order_msg.rlp_bytes().to_vec(), + }; + + let result = app.execute_contract( + Addr::unchecked(USER1), + contract_addr.clone(), + &recv_msg, + &[], + ); + println!("{:?}", &result); + let res = result.unwrap(); + + assert!(res.events.iter().any(|e| e.ty == "transfer" + && e.attributes + .iter() + .any(|attr| attr.key == "recipient" && attr.value == "user2"))); +} + +#[test] +#[should_panic(expected = "InsufficientFunds")] +fn test_insufficient_native_funds() { + let mut app = mock_app(); + let code_id = store_code(&mut app); + let contract_addr = instantiate_contract(&mut app, code_id, USER1); + + // Test insufficient funds error + let swap_msg = ExecuteMsg::Swap { + dst_nid: "dst-nid".to_string(), + token: NATIVE_DENOM.to_string(), + amount: 1000, + to_token: "to_token".to_string(), + destination_address: USER2.to_string(), + min_receive: 900, + data: vec![], + }; + fund_account(&mut app, USER1.to_string(), 200, NATIVE_DENOM.to_string()); + let err = app + .execute_contract( + Addr::unchecked(USER1), + contract_addr.clone(), + &swap_msg, + &[Coin { + denom: NATIVE_DENOM.to_string(), + amount: Uint128::new(100), + }], + ) + .unwrap(); +} + +#[test] +#[should_panic(expected = "InvalidMessageType")] +fn test_invalid_message_type_panics() { + let mut app = mock_app(); + let code_id = store_code(&mut app); + let contract_addr = instantiate_contract(&mut app, code_id, USER1); + + // Test invalid message type error + let invalid_order_msg = OrderMsg { + msg_type: 5, // Invalid message type + message: vec![], + }; + + let recv_msg = ExecuteMsg::RecvMessage { + src_network: "src-nid".to_string(), + conn_sn: 1, + msg: invalid_order_msg.rlp_bytes().to_vec(), + }; + + let err = app + .execute_contract( + Addr::unchecked(USER1), + contract_addr.clone(), + &recv_msg, + &[], + ) + .unwrap(); +} + +#[test] +#[should_panic(expected = "OrderAlreadyComplete")] +fn test_fill_already_finished_order() { + let mut app = mock_app(); + let code_id = store_code(&mut app); + let contract_addr = instantiate_contract(&mut app, code_id, USER1); + + fund_account(&mut app, USER2.to_string(), 200, "to_token".to_string()); + + let fill_msg = ExecuteMsg::Fill { + id: 1, + emitter: contract_addr.to_string(), + src_nid: "test-nid".to_string(), + dst_nid: "src-nid".to_string(), + creator: USER1.to_string(), + destination_address: USER2.to_string(), + token: NATIVE_DENOM.to_string(), + amount: 100, + to_token: "to_token".to_string(), + min_receive: 90, + data: hex::decode("deadbeef").unwrap(), + fill_amount: 90, + solver_address: USER2.to_string(), + }; + + app.execute_contract( + Addr::unchecked(USER2), + contract_addr.clone(), + &fill_msg, + &[Coin { + denom: "to_token".to_string(), + amount: Uint128::new(90), + }], + ) + .unwrap(); + + // Try to fill the same order again + let err = app + .execute_contract( + Addr::unchecked(USER2), + contract_addr.clone(), + &fill_msg, + &[Coin { + denom: "to_token".to_string(), + amount: Uint128::new(90), + }], + ) + .unwrap(); +} + +#[test] +#[should_panic(expected = "OrderAlreadyComplete")] +fn test_cancel_already_finished_order() { + let mut app = mock_app(); + let code_id = store_code(&mut app); + let contract_addr = instantiate_contract(&mut app, code_id, USER1); + + fund_account(&mut app, USER2.to_string(), 100, "to_token".to_string()); + let fill_msg = ExecuteMsg::Fill { + id: 1, + emitter: contract_addr.to_string(), + src_nid: "dst-nid".to_string(), + dst_nid: "src-nid".to_string(), + creator: USER1.to_string(), + destination_address: USER2.to_string(), + token: NATIVE_DENOM.to_string(), + amount: 100, + to_token: "to_token".to_string(), + min_receive: 90, + data: hex::decode("deadbeef").unwrap(), + fill_amount: 90, + solver_address: USER2.to_string(), + }; + + app.execute_contract( + Addr::unchecked(USER2), + contract_addr.clone(), + &fill_msg, + &[Coin { + denom: "to_token".to_string(), + amount: Uint128::new(90), + }], + ) + .unwrap(); + + // Try to cancel the already finished order + let cancel_order = SwapOrder { + id: 1, + emitter: contract_addr.to_string(), + src_nid: "dst-nid".to_string(), + dst_nid: "src-nid".to_string(), + creator: USER1.to_string(), + destination_address: USER2.to_string(), + token: NATIVE_DENOM.to_string(), + amount: 100, + to_token: "to_token".to_string(), + min_receive: 90, + data: hex::decode("deadbeef").unwrap(), + }; + + let cancel_msg = OrderMsg { + msg_type: 2, // ORDER_CANCEL + message: OrderCancel { + order_bytes: cancel_order.rlp_bytes().to_vec(), + } + .rlp_bytes() + .to_vec(), + }; + + let recv_msg = ExecuteMsg::RecvMessage { + src_network: "dst-nid".to_string(), + conn_sn: 1, + msg: cancel_msg.rlp_bytes().to_vec(), + }; + + let err = app + .execute_contract( + Addr::unchecked(USER1), + contract_addr.clone(), + &recv_msg, + &[], + ) + .unwrap(); +} + +#[test] +fn test_internal_functions() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info(USER1, &[]); + let service = CwIntentV1Service::default(); + + // Test is_contract + assert!(!service.is_contract(deps.as_ref(), &Addr::unchecked(USER1))); + + // Test is_native + assert!(service.is_native(deps.as_ref(), &NATIVE_DENOM.to_string())); + + // Test get_next_deposit_id + service.set_deposit_id(deps.as_mut().storage, 5).unwrap(); + let next_id = service.get_next_deposit_id(deps.as_mut().storage).unwrap(); + assert_eq!(next_id, 6); + + // Test get_next_conn_sn + service.set_conn_sn(deps.as_mut().storage, 10).unwrap(); + let next_sn = service.get_next_conn_sn(deps.as_mut().storage).unwrap(); + assert_eq!(next_sn, 11); +}