diff --git a/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message.rs b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message.rs index b4d79f9c..dd84f6a0 100644 --- a/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message.rs +++ b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message.rs @@ -35,3 +35,25 @@ impl IMessage for CallMessage { Ok(rlp::encode(self).to_vec()) } } + +#[cfg(test)] +mod tests { + use super::*; + + use rlp::Rlp; + + #[test] + fn test_call_message() { + let msg = CallMessage { + data: vec![1, 2, 3], + }; + + let encoded = msg.rlp_bytes().to_vec(); + let decoded = CallMessage::decode(&Rlp::new(&encoded)).unwrap(); + + assert_eq!(msg, decoded); + assert_eq!(msg.rollback(), None); + assert_eq!(msg.data(), msg.data); + assert_eq!(msg.to_bytes().unwrap(), encoded) + } +} diff --git a/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message_persisted.rs b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message_persisted.rs index 8bdbfca6..ebec2203 100644 --- a/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message_persisted.rs +++ b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message_persisted.rs @@ -34,3 +34,25 @@ impl IMessage for CallMessagePersisted { Ok(rlp::encode(self).to_vec()) } } + +#[cfg(test)] +mod tests { + use common::rlp::Rlp; + + use super::*; + + #[test] + fn test_call_message_persisted() { + let msg = CallMessagePersisted { + data: vec![1, 2, 3], + }; + + let encoded = msg.rlp_bytes().to_vec(); + let decoded = CallMessagePersisted::decode(&Rlp::new(&encoded)).unwrap(); + + assert_eq!(msg, decoded); + assert_eq!(msg.rollback(), None); + assert_eq!(msg.data(), msg.data); + assert_eq!(msg.to_bytes().unwrap(), encoded) + } +} diff --git a/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message_rollback.rs b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message_rollback.rs index 26764b47..22df970b 100644 --- a/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message_rollback.rs +++ b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message_rollback.rs @@ -40,3 +40,26 @@ impl IMessage for CallMessageWithRollback { Ok(rlp::encode(self).to_vec()) } } + +#[cfg(test)] +mod tests { + use common::rlp::Rlp; + + use super::*; + + #[test] + fn test_call_message_with_rollback() { + let msg = CallMessageWithRollback { + data: vec![1, 2, 3], + rollback: vec![1, 2, 3], + }; + + let encoded = msg.rlp_bytes().to_vec(); + let decoded = CallMessageWithRollback::decode(&Rlp::new(&encoded)).unwrap(); + + assert_eq!(msg, decoded); + assert_eq!(msg.rollback().unwrap(), msg.rollback); + assert_eq!(msg.data(), msg.data); + assert_eq!(msg.to_bytes().unwrap(), encoded) + } +} diff --git a/contracts/cosmwasm-vm/cw-xcall-lib/src/message/envelope.rs b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/envelope.rs index f533714a..4960fa40 100644 --- a/contracts/cosmwasm-vm/cw-xcall-lib/src/message/envelope.rs +++ b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/envelope.rs @@ -81,43 +81,64 @@ mod tests { use super::*; #[test] - fn test_encoding_decoding_envelope_call_message() { - // Create a sample Envelope - let message = AnyMessage::CallMessage(CallMessage { + fn test_envelope_call_message() { + let msg = AnyMessage::CallMessage(CallMessage { data: vec![1, 2, 3], }); - let sources = vec!["source1".to_string(), "source2".to_string()]; - let destinations = vec!["dest1".to_string(), "dest2".to_string()]; - let envelope = Envelope::new(message, sources, destinations); - let encoded_data = rlp::encode(&envelope).to_vec(); + let sources = vec!["src".to_string()]; + let destinations = vec!["dst".to_string()]; + let envelope = Envelope::new(msg.clone(), sources, destinations); - assert_eq!( - "e50085c483010203d087736f757263653187736f7572636532cc856465737431856465737432", - hex::encode(&encoded_data) - ); - let decoded: Envelope = rlp::decode(&encoded_data).unwrap(); + let encoded = envelope.rlp_bytes().to_vec(); + let decoded = Envelope::decode(&rlp::Rlp::new(&encoded)).unwrap(); + assert_eq!(envelope, decoded); + + let encoded = msg.to_bytes().unwrap(); + let decoded = decode_message(MessageType::CallMessage, encoded.clone()).unwrap(); + assert_eq!(decoded.rollback(), None); + assert_eq!(decoded.data(), vec![1, 2, 3]); + assert_eq!(decoded.to_bytes().unwrap(), encoded) + } + + #[test] + fn test_envelope_call_message_persisted() { + let msg = AnyMessage::CallMessagePersisted(CallMessagePersisted { + data: vec![1, 2, 3], + }); + let sources = vec!["src".to_string()]; + let destinations = vec!["dst".to_string()]; + let envelope = Envelope::new(msg.clone(), sources, destinations); + let encoded = envelope.rlp_bytes().to_vec(); + let decoded = Envelope::decode(&rlp::Rlp::new(&encoded)).unwrap(); assert_eq!(envelope, decoded); + + let encoded = msg.to_bytes().unwrap(); + let decoded = decode_message(MessageType::CallMessagePersisted, encoded.clone()).unwrap(); + assert_eq!(decoded.rollback(), None); + assert_eq!(decoded.data(), vec![1, 2, 3]); + assert_eq!(decoded.to_bytes().unwrap(), encoded) } #[test] - fn test_encoding_decoding_envelope_call_message_rollback() { - // Create a sample Envelope - let message = AnyMessage::CallMessageWithRollback(CallMessageWithRollback { + fn test_envelope_call_message_with_rollback() { + let msg = AnyMessage::CallMessageWithRollback(CallMessageWithRollback { data: vec![1, 2, 3], rollback: vec![1, 2, 3], }); - let sources = vec!["source1".to_string(), "source2".to_string()]; - let destinations = vec!["dest1".to_string(), "dest2".to_string()]; - let envelope = Envelope::new(message, sources, destinations); - let encoded_data = rlp::encode(&envelope).to_vec(); - - assert_eq!( - "e90189c88301020383010203d087736f757263653187736f7572636532cc856465737431856465737432", - hex::encode(&encoded_data) - ); - let decoded: Envelope = rlp::decode(&encoded_data).unwrap(); + let sources = vec!["src".to_string()]; + let destinations = vec!["dst".to_string()]; + let envelope = Envelope::new(msg.clone(), sources, destinations); + let encoded = envelope.rlp_bytes().to_vec(); + let decoded = Envelope::decode(&rlp::Rlp::new(&encoded)).unwrap(); assert_eq!(envelope, decoded); + + let encoded = msg.to_bytes().unwrap(); + let decoded = + decode_message(MessageType::CallMessageWithRollback, encoded.clone()).unwrap(); + assert_eq!(decoded.rollback(), Some(vec![1, 2, 3])); + assert_eq!(decoded.data(), vec![1, 2, 3]); + assert_eq!(decoded.to_bytes().unwrap(), encoded) } } diff --git a/contracts/cosmwasm-vm/cw-xcall-lib/src/message/msg_type.rs b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/msg_type.rs index 2e9b8ac4..6ac91aad 100644 --- a/contracts/cosmwasm-vm/cw-xcall-lib/src/message/msg_type.rs +++ b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/msg_type.rs @@ -36,3 +36,37 @@ impl MessageType { MessageType::from(val) } } + +#[cfg(test)] +mod tests { + use crate::message::msg_type::MessageType; + + #[test] + fn test_message_type_for_u8() { + assert_eq!(MessageType::from(MessageType::CallMessage), 0.into()); + assert_eq!( + MessageType::from(MessageType::CallMessagePersisted), + 2.into() + ); + assert_eq!( + MessageType::from(MessageType::CallMessageWithRollback), + 1.into() + ) + } + + #[test] + fn test_message_type_from_int() { + assert_eq!(MessageType::from_int(0), MessageType::CallMessage); + assert_eq!( + MessageType::from_int(1), + MessageType::CallMessageWithRollback + ); + assert_eq!(MessageType::from_int(2), MessageType::CallMessagePersisted) + } + + #[test] + #[should_panic(expected = "unsupported message type")] + fn test_message_type_from_int_fail() { + MessageType::from_int(4); + } +} diff --git a/contracts/cosmwasm-vm/cw-xcall/src/contract.rs b/contracts/cosmwasm-vm/cw-xcall/src/contract.rs index b41239bb..b5f6743e 100644 --- a/contracts/cosmwasm-vm/cw-xcall/src/contract.rs +++ b/contracts/cosmwasm-vm/cw-xcall/src/contract.rs @@ -244,32 +244,3 @@ impl<'a> CwCallService<'a> { Ok(na) } } - -#[cfg(test)] -mod tests { - use cosmwasm_std::testing::{mock_dependencies, mock_env}; - use cw2::{get_contract_version, ContractVersion}; - - use crate::{ - contract::{CONTRACT_NAME, CONTRACT_VERSION}, - state::CwCallService, - MigrateMsg, - }; - - #[test] - fn test_migrate() { - let mut mock_deps = mock_dependencies(); - let env = mock_env(); - - let contract = CwCallService::default(); - let result = contract.migrate(mock_deps.as_mut(), env, MigrateMsg {}); - assert!(result.is_ok()); - let expected = ContractVersion { - contract: CONTRACT_NAME.to_string(), - version: CONTRACT_VERSION.to_string(), - }; - let version = get_contract_version(&mock_deps.storage).unwrap(); - println!("{version:?}"); - assert_eq!(expected, version); - } -} diff --git a/contracts/cosmwasm-vm/cw-xcall/tests/setup.rs b/contracts/cosmwasm-vm/cw-xcall/tests/setup.rs index 75de0d86..d9aef3c9 100644 --- a/contracts/cosmwasm-vm/cw-xcall/tests/setup.rs +++ b/contracts/cosmwasm-vm/cw-xcall/tests/setup.rs @@ -1,11 +1,107 @@ +use std::{collections::HashMap, str::FromStr}; + +use common::{rlp, utils::keccak256}; +use cosmwasm::encoding::Binary; use cosmwasm_std::{ coins, testing::{ mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR, }, - Addr, BlockInfo, ContractInfo, Empty, Env, MessageInfo, OwnedDeps, Timestamp, TransactionInfo, + to_binary, Addr, BlockInfo, ContractInfo, ContractResult, Empty, Env, MessageInfo, OwnedDeps, + Storage, SystemResult, Timestamp, TransactionInfo, WasmQuery, +}; +use cw_xcall::{ + state::CwCallService, + types::{ + config::Config, + message::CSMessage, + request::CSMessageRequest, + result::{CSMessageResult, CallServiceResponseType}, + rollback::Rollback, + }, }; +use cw_xcall_lib::{ + message::{ + call_message::CallMessage, call_message_rollback::CallMessageWithRollback, + envelope::Envelope, msg_trait::IMessage, msg_type::MessageType, AnyMessage, + }, + network_address::{NetId, NetworkAddress}, +}; + +pub fn get_dummy_network_address(nid: &str) -> NetworkAddress { + NetworkAddress::new(nid, "xcall") +} + +pub fn get_dummy_call_msg_envelop() -> Envelope { + let msg = AnyMessage::CallMessage(CallMessage { + data: vec![1, 2, 3], + }); + Envelope::new(msg, vec![], vec![]) +} + +pub fn get_dummy_req_msg() -> CSMessageRequest { + CSMessageRequest::new( + get_dummy_network_address("archway"), + Addr::unchecked("dapp"), + 1, + MessageType::CallMessage, + keccak256(&vec![1, 2, 3]).to_vec(), + vec![], + ) +} + +pub fn get_dummy_rollback_data() -> Rollback { + let msg = AnyMessage::CallMessageWithRollback(CallMessageWithRollback { + data: vec![1, 2, 3], + rollback: vec![1, 2, 3], + }); + let envelope = Envelope::new(msg, vec![], vec![]); + + Rollback::new( + Addr::unchecked("xcall"), + get_dummy_network_address("archway"), + envelope.sources, + envelope.message.rollback().unwrap(), + true, + ) +} + +pub fn get_dummy_request_message() -> CSMessage { + let payload = get_dummy_req_msg(); + CSMessage::new( + cw_xcall::types::message::CSMessageType::CSMessageRequest, + rlp::encode(&payload).to_vec(), + ) +} + +pub fn get_dummy_result_message() -> CSMessageResult { + let payload = get_dummy_req_msg(); + CSMessageResult::new( + 1, + CallServiceResponseType::CallServiceResponseSuccess, + Some(payload.as_bytes()), + ) +} + +pub fn get_dummy_result_message_failure() -> CSMessageResult { + let payload = get_dummy_req_msg(); + CSMessageResult::new( + 1, + CallServiceResponseType::CallServiceResponseFailure, + Some(payload.as_bytes()), + ) +} + +pub fn mock_connection_fee_query(deps: &mut OwnedDeps) { + deps.querier.update_wasm(|r| match r { + WasmQuery::Smart { + contract_addr: _, + msg: _, + } => SystemResult::Ok(ContractResult::Ok(to_binary(&10_u128).unwrap())), + _ => todo!(), + }); +} pub struct MockEnvBuilder { env: Env, @@ -93,3 +189,112 @@ fn test() { assert_ne!(mock, mock_env_builder) } + +pub struct TestContext { + pub nid: NetId, + pub network_address: NetworkAddress, + pub env: Env, + pub info: MessageInfo, + pub request_id: u128, + pub request_message: Option, + pub envelope: Option, + pub mock_queries: HashMap, +} + +impl TestContext { + pub fn default() -> Self { + Self { + nid: NetId::from_str("icon").unwrap(), + network_address: get_dummy_network_address("icon"), + env: MockEnvBuilder::new().env, + info: mock_info("admin", &coins(100, "icx")), + request_id: u128::default(), + request_message: Some(get_dummy_req_msg()), + envelope: None, + mock_queries: HashMap::::new(), + } + } + + pub fn init_context(&self, storage: &mut dyn Storage, contract: &CwCallService) { + self.store_config(storage, &contract); + self.set_admin(storage, &contract); + self.init_last_request_id(storage, &contract); + self.init_last_sequence_no(storage, &contract); + self.store_default_connection(storage, &contract); + self.store_protocol_fee_handler(storage, &contract) + } + + pub fn init_execute_call(&self, storage: &mut dyn Storage, contract: &CwCallService) { + self.init_context(storage, &contract); + self.store_proxy_request(storage, &contract); + } + + pub fn init_reply_state(&self, storage: &mut dyn Storage, contract: &CwCallService) { + self.init_context(storage, &contract); + self.store_proxy_request(storage, &contract); + self.store_execute_request_id(storage, &contract); + } + + pub fn set_admin(&self, storage: &mut dyn Storage, contract: &CwCallService) { + contract + .set_admin(storage, self.info.sender.clone()) + .unwrap(); + } + + pub fn store_config(&self, storage: &mut dyn Storage, contract: &CwCallService) { + let config = Config { + network_id: "icon".to_string(), + denom: "icx".to_string(), + }; + contract.store_config(storage, &config).unwrap(); + } + + pub fn store_protocol_fee_handler(&self, storage: &mut dyn Storage, contract: &CwCallService) { + contract + .store_protocol_fee_handler(storage, self.info.sender.to_string()) + .unwrap(); + } + + pub fn store_default_connection(&self, storage: &mut dyn Storage, contract: &CwCallService) { + let network = get_dummy_network_address("archway"); + let address = Addr::unchecked("centralized"); + contract + .store_default_connection(storage, network.nid(), address) + .unwrap(); + } + + pub fn store_proxy_request(&self, storage: &mut dyn Storage, contract: &CwCallService) { + if let Some(proxy_request) = &self.request_message { + contract + .store_proxy_request(storage, self.request_id, proxy_request) + .unwrap(); + } + } + + pub fn store_execute_request_id(&self, storage: &mut dyn Storage, contract: &CwCallService) { + contract + .store_execute_request_id(storage, self.request_id) + .unwrap(); + } + + pub fn init_last_request_id(&self, storage: &mut dyn Storage, contract: &CwCallService) { + contract + .init_last_request_id(storage, self.request_id) + .unwrap(); + } + + pub fn init_last_sequence_no(&self, storage: &mut dyn Storage, contract: &CwCallService) { + contract + .init_last_sequence_no(storage, u128::default()) + .unwrap(); + } + + pub fn set_successful_response( + &self, + storage: &mut dyn Storage, + contract: &CwCallService, + sn: u128, + ) { + contract.set_successful_response(storage, sn).unwrap(); + } +} diff --git a/contracts/cosmwasm-vm/cw-xcall/tests/test_call_message.rs b/contracts/cosmwasm-vm/cw-xcall/tests/test_call_message.rs index 07023047..af41ec05 100644 --- a/contracts/cosmwasm-vm/cw-xcall/tests/test_call_message.rs +++ b/contracts/cosmwasm-vm/cw-xcall/tests/test_call_message.rs @@ -1,16 +1,26 @@ mod account; mod setup; -use std::{collections::HashMap, str::FromStr, vec}; use crate::account::*; +use setup::{get_dummy_network_address, test::*, TestContext}; +use std::{collections::HashMap, str::FromStr, vec}; + use cosmwasm_std::{ testing::{mock_env, MOCK_CONTRACT_ADDR}, to_binary, Addr, Binary, ContractInfoResponse, ContractResult, SystemError, SystemResult, WasmQuery, }; -use cw_xcall::{state::CwCallService, types::config::Config}; -use cw_xcall_lib::network_address::{NetId, NetworkAddress}; -use setup::test::*; +use cw_xcall::{ + state::CwCallService, + types::{config::Config, request::CSMessageRequest}, +}; +use cw_xcall_lib::{ + message::{ + call_message_persisted::CallMessagePersisted, envelope::Envelope, msg_type::MessageType, + AnyMessage, + }, + network_address::{NetId, NetworkAddress}, +}; const MOCK_CONTRACT_TO_ADDR: &str = "cosmoscontract"; @@ -344,3 +354,94 @@ fn send_packet_fail_insufficient_funds() { assert!(!result.enabled()) } + +#[test] +fn test_send_message_on_reply_state() { + let mut deps = deps(); + let contract = CwCallService::new(); + + let ctx = TestContext::default(); + ctx.init_reply_state(deps.as_mut().storage, &contract); + + let from = ctx.request_message.unwrap().from().clone(); + let envelope = Envelope::new( + AnyMessage::CallMessagePersisted(CallMessagePersisted { + data: vec![1, 2, 3], + }), + vec![], + vec![], + ); + + let res = contract + .send_call(deps.as_mut(), ctx.info, from, envelope) + .unwrap(); + assert_eq!(res.attributes[0].value, "xcall-service"); + assert_eq!(res.attributes[1].value, "send_packet"); +} + +#[test] +fn test_is_reply_returns_false_on_mismatch_network_id() { + let mut deps = deps(); + let contract = CwCallService::new(); + + let ctx = TestContext::default(); + ctx.init_reply_state(deps.as_mut().storage, &contract); + + let res = contract.is_reply(deps.as_ref(), ctx.nid, &vec![]); + assert_eq!(res, false) +} + +#[test] +fn test_is_reply_returns_false_on_proxy_request_not_found() { + let deps = deps(); + let contract = CwCallService::new(); + + let ctx = TestContext::default(); + + let res = contract.is_reply(deps.as_ref(), ctx.nid, &vec![]); + assert_eq!(res, false) +} + +#[test] +fn test_is_reply_returns_false_on_mismatch_array_len() { + let mut deps = deps(); + let contract = CwCallService::new(); + + let ctx = TestContext::default(); + ctx.init_reply_state(deps.as_mut().storage, &contract); + + let res = contract.is_reply( + deps.as_ref(), + NetId::from_str("archway").unwrap(), + &vec!["src_1".to_string()], + ); + assert_eq!(res, false) +} + +#[test] +fn test_is_reply_returns_false_on_mismatch_protocols() { + let mut deps = deps(); + let contract = CwCallService::new(); + + let ctx = TestContext::default(); + ctx.init_reply_state(deps.as_mut().storage, &contract); + + let request = CSMessageRequest::new( + get_dummy_network_address("archway"), + Addr::unchecked("dapp"), + 1, + MessageType::CallMessagePersisted, + vec![], + vec!["src_2".to_string()], + ); + contract + .store_proxy_request(deps.as_mut().storage, ctx.request_id, &request) + .unwrap(); + + let res = contract.is_reply( + deps.as_ref(), + NetId::from_str("archway").unwrap(), + &vec!["src_1".to_string()], + ); + assert_eq!(res, false) +} diff --git a/contracts/cosmwasm-vm/cw-xcall/tests/test_call_service.rs b/contracts/cosmwasm-vm/cw-xcall/tests/test_call_service.rs index 7b21a7e3..b8eddb27 100644 --- a/contracts/cosmwasm-vm/cw-xcall/tests/test_call_service.rs +++ b/contracts/cosmwasm-vm/cw-xcall/tests/test_call_service.rs @@ -1,12 +1,30 @@ mod account; mod setup; -use cosmwasm_std::testing::{mock_env, MOCK_CONTRACT_ADDR}; -use cw_xcall::{instantiate, msg::InstantiateMsg, state::CwCallService}; -use setup::test::*; +use common::utils::keccak256; +use cw2::{get_contract_version, ContractVersion}; +use cw_xcall::MigrateMsg; +use setup::{test::*, *}; +use std::str::FromStr; -#[test] +use cosmwasm_std::{ + testing::{mock_env, MOCK_CONTRACT_ADDR}, + to_binary, Addr, Event, Reply, SubMsgResponse, SubMsgResult, +}; +use cw_xcall::{ + execute, instantiate, migrate, + msg::{InstantiateMsg, QueryMsg}, + query, reply, + state::CwCallService, + types::{request::CSMessageRequest, rollback::Rollback}, +}; +use cw_xcall_lib::{ + message::msg_type::MessageType, + network_address::{NetId, NetworkAddress}, + xcall_msg::ExecuteMsg, +}; +#[test] fn proper_instantiate() { let mut mock_deps = deps(); let mock_info = create_mock_info(MOCK_CONTRACT_ADDR, "umlg", 2000); @@ -50,3 +68,319 @@ fn improper_instantiate() { assert_eq!(0, last_request_id); } + +#[test] +fn test_migrate() { + let ctx = TestContext::default(); + let mut deps = deps(); + + const CONTRACT_NAME: &str = "crates.io:cw-xcall"; + const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + + migrate(deps.as_mut(), ctx.env, MigrateMsg {}).unwrap(); + let expected = ContractVersion { + contract: CONTRACT_NAME.to_string(), + version: CONTRACT_VERSION.to_string(), + }; + let version = get_contract_version(deps.as_ref().storage).unwrap(); + assert_eq!(expected, version); +} + +#[test] +#[should_panic(expected = "ReplyError { code: 5, msg: \"Unknown\" }")] +fn test_reply_fail_for_unknown_reply_id() { + let mut deps = deps(); + let ctx = TestContext::default(); + + let msg = Reply { + id: 5, + result: SubMsgResult::Ok(SubMsgResponse { + events: vec![Event::new("empty")], + data: Some(to_binary(&vec![1]).unwrap()), + }), + }; + reply(deps.as_mut(), ctx.env, msg).unwrap(); +} + +#[test] +fn test_execute_set_admin() { + let mut deps = deps(); + let contract = CwCallService::new(); + + let ctx = TestContext::default(); + ctx.init_context(deps.as_mut().storage, &contract); + + let msg = ExecuteMsg::SetAdmin { + address: "new".to_string(), + }; + let res = execute(deps.as_mut(), ctx.env, ctx.info, msg); + assert!(res.is_ok()) +} + +#[test] +fn test_execute_set_protocol_fee_handler() { + let mut deps = deps(); + let contract = CwCallService::new(); + + let ctx = TestContext::default(); + ctx.init_context(deps.as_mut().storage, &contract); + + let msg = ExecuteMsg::SetProtocolFeeHandler { + address: "fee_handler".to_string(), + }; + let res = execute(deps.as_mut(), ctx.env, ctx.info, msg).unwrap(); + + assert_eq!(res.attributes[0].value, "set_protocol_feehandler"); +} + +#[test] +fn test_execute_set_default_connection() { + let mut deps = deps(); + let contract = CwCallService::new(); + + let ctx = TestContext::default(); + ctx.init_context(deps.as_mut().storage, &contract); + + let msg = ExecuteMsg::SetDefaultConnection { + nid: ctx.nid, + address: Addr::unchecked("icon_contract"), + }; + let res = execute(deps.as_mut(), ctx.env, ctx.info, msg); + assert!(res.is_ok()) +} + +#[test] +fn test_execute_send_call_message() { + let mut deps = deps(); + let contract = CwCallService::new(); + + let ctx = TestContext::default(); + ctx.init_context(deps.as_mut().storage, &contract); + + let msg = ExecuteMsg::SendCallMessage { + to: get_dummy_network_address("archway"), + data: vec![1, 2, 3], + rollback: None, + sources: Some(vec![]), + destinations: Some(vec![]), + }; + + mock_connection_fee_query(&mut deps); + + let res = execute(deps.as_mut(), ctx.env, ctx.info, msg).unwrap(); + assert_eq!(res.attributes[0].value, "xcall-service"); + assert_eq!(res.attributes[1].value, "send_packet"); + assert_eq!(res.attributes[2].value, "1"); +} + +#[test] +fn test_execute_send_call() { + let mut deps = deps(); + let contract = CwCallService::new(); + + let ctx = TestContext::default(); + ctx.init_context(deps.as_mut().storage, &contract); + + mock_connection_fee_query(&mut deps); + + let envelope = get_dummy_call_msg_envelop(); + let msg = ExecuteMsg::SendCall { + envelope, + to: get_dummy_network_address("archway"), + }; + let res = execute(deps.as_mut(), ctx.env, ctx.info, msg).unwrap(); + assert_eq!(res.attributes[0].value, "xcall-service"); + assert_eq!(res.attributes[1].value, "send_packet"); + assert_eq!(res.attributes[2].value, "1"); +} + +#[test] +fn test_execute_handle_request_message_with_default_connection() { + let mut deps = deps(); + let contract = CwCallService::new(); + let info = create_mock_info("centralized", "icx", 100); + + let ctx = TestContext::default(); + ctx.init_context(deps.as_mut().storage, &contract); + + let from_nid = NetId::from_str("archway").unwrap(); + let msg = ExecuteMsg::HandleMessage { + from_nid, + msg: get_dummy_request_message().as_bytes(), + }; + let res = execute(deps.as_mut(), ctx.env, info, msg).unwrap(); + assert_eq!(res.attributes[0].value, "call_service"); + assert_eq!(res.attributes[1].value, "handle_response") +} + +#[test] +fn test_execute_call() { + let mut deps = deps(); + let contract = CwCallService::new(); + + let ctx = TestContext::default(); + ctx.init_execute_call(deps.as_mut().storage, &contract); + + let msg = ExecuteMsg::ExecuteCall { + request_id: ctx.request_id, + data: vec![1, 2, 3], + }; + let res = execute(deps.as_mut(), ctx.env, ctx.info, msg).unwrap(); + assert_eq!(res.attributes[1].value, "execute_call") +} + +#[test] +fn test_execute_handle_error() { + let mut deps = deps(); + let contract = CwCallService::new(); + let info = create_mock_info("centralized", "arch", 100); + + let ctx = TestContext::default(); + ctx.init_execute_call(deps.as_mut().storage, &contract); + + let rollback = get_dummy_rollback_data(); + contract + .store_call_request(deps.as_mut().storage, ctx.request_id, &rollback) + .unwrap(); + + let msg = ExecuteMsg::HandleError { sn: 0 }; + let res = execute(deps.as_mut(), ctx.env, info, msg).unwrap(); + assert_eq!(res.attributes[1].value, "handle_response") +} + +#[test] +fn test_execute_rollback() { + let mut deps = deps(); + let contract = CwCallService::new(); + + let ctx = TestContext::default(); + ctx.init_execute_call(deps.as_mut().storage, &contract); + + let rollback = Rollback::new( + Addr::unchecked("dapp"), + get_dummy_network_address("archway"), + vec!["src".to_string()], + vec![1, 2, 3], + true, + ); + contract + .store_call_request(deps.as_mut().storage, ctx.request_id, &rollback) + .unwrap(); + + let msg = ExecuteMsg::ExecuteRollback { + sequence_no: ctx.request_id, + }; + let res = execute(deps.as_mut(), ctx.env, ctx.info, msg).unwrap(); + assert_eq!(res.attributes[1].value, "execute_rollback"); +} + +#[test] +fn test_query_get_admin() { + let mut deps = deps(); + let contract = CwCallService::new(); + + let ctx = TestContext::default(); + ctx.init_context(deps.as_mut().storage, &contract); + + let msg = QueryMsg::GetAdmin {}; + let res = query(deps.as_ref(), ctx.env, msg).unwrap(); + assert_eq!(res, to_binary("admin").unwrap()) +} + +#[test] +fn test_query_get_network_address() { + let mut deps = deps(); + let contract = CwCallService::new(); + + let ctx = TestContext::default(); + ctx.init_context(deps.as_mut().storage, &contract); + + let msg = QueryMsg::GetNetworkAddress {}; + let res = query(deps.as_ref(), ctx.env.clone(), msg).unwrap(); + + let expected_network_address = + NetworkAddress::new("icon", ctx.env.contract.address.clone().as_str()); + assert_eq!(res, to_binary(&expected_network_address).unwrap()) +} + +#[test] +fn test_query_verify_success() { + let mut deps = deps(); + let contract = CwCallService::new(); + + let ctx = TestContext::default(); + ctx.init_context(deps.as_mut().storage, &contract); + + ctx.set_successful_response(deps.as_mut().storage, &contract, 1); + + let msg = QueryMsg::VerifySuccess { sn: 1 }; + let res = query(deps.as_ref(), ctx.env, msg); + assert!(res.is_ok()) +} + +#[test] +fn test_query_get_default_connection() { + let mut deps = deps(); + let contract = CwCallService::new(); + + let ctx = TestContext::default(); + ctx.init_context(deps.as_mut().storage, &contract); + + let msg = QueryMsg::GetDefaultConnection { + nid: NetId::from_str("archway").unwrap(), + }; + let res = query(deps.as_ref(), ctx.env, msg).unwrap(); + assert_eq!(res, to_binary(&Addr::unchecked("centralized")).unwrap()); +} + +#[test] +fn test_get_all_connection() { + let mut deps = deps(); + let contract = CwCallService::new(); + + let ctx = TestContext::default(); + ctx.init_context(deps.as_mut().storage, &contract); + + let res = contract.get_all_connections(deps.as_ref().storage).unwrap(); + assert_eq!(res, vec!["centralized".to_string()]); +} + +#[test] +fn test_execute_call_reply() { + let mut deps = deps(); + let contract = CwCallService::new(); + + let ctx = TestContext::default(); + ctx.init_context(deps.as_mut().storage, &contract); + + let request: CSMessageRequest = CSMessageRequest::new( + get_dummy_network_address("archway"), + Addr::unchecked("xcall"), + u128::default(), + MessageType::CallMessageWithRollback, + keccak256(&vec![1, 2, 3]).to_vec(), + vec![], + ); + contract + .store_proxy_request(deps.as_mut().storage, ctx.request_id, &request) + .unwrap(); + contract + .save_call_reply(deps.as_mut().storage, &ctx.request_message.unwrap()) + .unwrap(); + contract + .store_execute_request_id(deps.as_mut().storage, ctx.request_id) + .unwrap(); + + let msg = Reply { + id: 1, + result: SubMsgResult::Ok(SubMsgResponse { + events: vec![Event::new("empty")], + data: Some(to_binary(&vec![1]).unwrap()), + }), + }; + + let res = contract + .execute_call_reply(deps.as_mut(), ctx.env, msg) + .unwrap(); + assert_eq!(res.attributes[1].value, "execute_callback") +} diff --git a/contracts/cosmwasm-vm/cw-xcall/tests/test_handle_call_message.rs b/contracts/cosmwasm-vm/cw-xcall/tests/test_handle_call_message.rs index 8310030f..3ae84995 100644 --- a/contracts/cosmwasm-vm/cw-xcall/tests/test_handle_call_message.rs +++ b/contracts/cosmwasm-vm/cw-xcall/tests/test_handle_call_message.rs @@ -1,21 +1,30 @@ +mod account; +mod setup; +use crate::account::alice; + use common::utils::keccak256; +use schemars::_serde_json::to_string; +use setup::test::*; +use setup::*; +use std::str::FromStr; + use cosmwasm_std::{ from_binary, testing::{mock_dependencies, mock_env, mock_info}, to_binary, Addr, Coin, CosmosMsg, Reply, SubMsgResponse, SubMsgResult, WasmMsg, }; - use cw_xcall::{ state::{CwCallService, EXECUTE_CALL_ID}, - types::{request::CSMessageRequest, rollback::Rollback}, + types::{ + message::{CSMessage, CSMessageType}, + request::CSMessageRequest, + rollback::Rollback, + }, +}; +use cw_xcall_lib::{ + message::msg_type::MessageType, + network_address::{NetId, NetworkAddress}, }; -use cw_xcall_lib::{message::msg_type::MessageType, network_address::NetworkAddress}; -mod account; -mod setup; -use crate::account::alice; - -use schemars::_serde_json::to_string; -use setup::test::*; #[test] #[should_panic(expected = "InvalidRequestId")] @@ -353,3 +362,194 @@ fn test_persisted_message_removed_on_success() { .ok(); assert_eq!(req, None); } + +#[test] +fn test_handle_reply() { + let ctx = TestContext::default(); + let mut deps = deps(); + let contract = CwCallService::new(); + + ctx.init_context(deps.as_mut().storage, &contract); + + let request = get_dummy_req_msg(); + let rollback = get_dummy_rollback_data(); + + let res = contract.handle_reply(deps.as_mut(), rollback, request); + assert!(res.is_ok()) +} + +#[test] +#[should_panic(expected = "InvalidReplyReceived")] +fn test_handle_reply_fail() { + let ctx = TestContext::default(); + let mut deps = deps(); + let contract = CwCallService::new(); + + ctx.init_context(deps.as_mut().storage, &contract); + + let request = CSMessageRequest::new( + get_dummy_network_address("icon"), + Addr::unchecked("dapp"), + 1, + MessageType::CallMessage, + keccak256(&vec![1, 2, 3]).to_vec(), + vec![], + ); + let rollback = get_dummy_rollback_data(); + + contract + .handle_reply(deps.as_mut(), rollback, request) + .unwrap(); +} + +#[test] +#[should_panic(expected = "ProtocolsMismatch")] +fn test_handle_request_fail_on_mismatch_protocols_request() { + let ctx = TestContext::default(); + let mut deps = deps(); + let contract = CwCallService::new(); + + ctx.init_context(deps.as_mut().storage, &contract); + + let data = ctx.request_message.unwrap().as_bytes(); + let src_net = NetId::from_str("evm").unwrap(); + contract + .handle_request(deps.as_mut(), ctx.info, src_net, &data) + .unwrap(); +} + +#[test] +#[should_panic(expected = "ProtocolsMismatch")] +fn test_handle_request_fail_on_invalid_source() { + let ctx = TestContext::default(); + let mut deps = deps(); + let contract = CwCallService::new(); + + ctx.init_context(deps.as_mut().storage, &contract); + + let nid = NetId::from_str("archway").unwrap(); + let data = ctx.request_message.unwrap().as_bytes(); + contract + .handle_request(deps.as_mut(), ctx.info, nid, &data) + .unwrap(); +} + +#[test] +fn test_handle_request_from_multiple_protocols() { + let ctx = TestContext::default(); + let mut deps = deps(); + let contract = CwCallService::new(); + + ctx.init_context(deps.as_mut().storage, &contract); + + let request = CSMessageRequest::new( + get_dummy_network_address("archway"), + Addr::unchecked("dapp"), + 1, + MessageType::CallMessage, + keccak256(&vec![1, 2, 3]).to_vec(), + vec!["centralized".to_string(), "ibc".to_string()], + ); + + let nid = NetId::from_str("archway").unwrap(); + for protocol in request.protocols() { + let info = create_mock_info(&protocol, "icx", 100); + let res = contract + .handle_request(deps.as_mut(), info, nid.clone(), &request.as_bytes()) + .unwrap(); + if protocol == "ibc" { + assert_eq!(res.attributes[0].value, "call_service"); + } else { + assert_eq!(res.attributes.len(), 0) + } + } +} + +#[test] +#[should_panic(expected = "CallRequestNotFound { sn: 1 }")] +fn test_handle_call_message_fail_on_invalid_request() { + let ctx = TestContext::default(); + let mut deps = deps(); + let contract = CwCallService::new(); + + ctx.init_context(deps.as_mut().storage, &contract); + + let message_result = get_dummy_result_message(); + let msg = CSMessage::new(CSMessageType::CSMessageResult, message_result.as_bytes()); + + let nid = NetId::from_str("archway").unwrap(); + contract + .handle_message(deps.as_mut(), ctx.info, nid, msg.as_bytes()) + .unwrap(); +} + +#[test] +#[should_panic(expected = "ProtocolsMismatch")] +fn test_handle_result_fail_on_invalid_source() { + let ctx = TestContext::default(); + let mut deps = deps(); + let contract = CwCallService::new(); + + ctx.init_context(deps.as_mut().storage, &contract); + + let rollback = get_dummy_rollback_data(); + contract + .store_call_request(deps.as_mut().storage, 1, &rollback) + .unwrap(); + + let msg = get_dummy_result_message().as_bytes(); + contract + .handle_result(deps.as_mut(), ctx.info, &msg) + .unwrap(); +} + +#[test] +fn test_handle_result_from_multiple_protocols() { + let ctx = TestContext::default(); + let mut deps = deps(); + let contract = CwCallService::new(); + + ctx.init_context(deps.as_mut().storage, &contract); + + let rollback = Rollback::new( + Addr::unchecked("xcall"), + get_dummy_network_address("archway"), + vec!["centralized".to_string(), "ibc".to_string()], + vec![1, 2, 3], + false, + ); + contract + .store_call_request(deps.as_mut().storage, 1, &rollback) + .unwrap(); + + let msg = get_dummy_result_message().as_bytes(); + + for protocol in rollback.protocols() { + let info = create_mock_info(&protocol, "arch", 100); + let res = contract.handle_result(deps.as_mut(), info, &msg).unwrap(); + if protocol == "centralized" { + assert_eq!(res.attributes.len(), 0); + } else { + assert_eq!(res.attributes[1].value, "handle_response") + } + } +} + +#[test] +fn test_handle_result_on_error_response() { + let ctx = TestContext::default(); + let mut deps = deps(); + let contract = CwCallService::new(); + let info = create_mock_info("centralized", "arch", 100); + + ctx.init_reply_state(deps.as_mut().storage, &contract); + + let rollback = get_dummy_rollback_data(); + contract + .store_call_request(deps.as_mut().storage, 1, &rollback) + .unwrap(); + + let msg = get_dummy_result_message_failure().as_bytes(); + let res = contract.handle_result(deps.as_mut(), info, &msg).unwrap(); + assert_eq!(res.attributes[1].value, "handle_response") +} diff --git a/contracts/evm/test/xcall/CallService.t.sol b/contracts/evm/test/xcall/CallService.t.sol index 365f5c88..8baf1da0 100644 --- a/contracts/evm/test/xcall/CallService.t.sol +++ b/contracts/evm/test/xcall/CallService.t.sol @@ -165,6 +165,52 @@ contract CallServiceTest is Test { callService.setProtocolFeeHandler(user); } + function testGetNetworkId() public { + assertEq(callService.getNetworkId(), ethNid); + } + + function testGetDefaultConnection() public { + callService.setDefaultConnection(iconNid, address(baseConnection)); + + address defaultConnection = callService.getDefaultConnection(iconNid); + assertEq(defaultConnection, address(baseConnection)); + } + + function testGetConnectionFee() public { + connection1 = IConnection(address(0x0000000000000000000000000000000000000011)); + + callService.setDefaultConnection(iconNid, address(connection1)); + + vm.mockCall(address(connection1), abi.encodeWithSelector(connection1.getFee.selector), abi.encode(30)); + + uint256 fee = callService.getFee(iconNid, true); + assertEq(fee, 30); + } + + function testGetFeeMultipleProtocols() public { + connection1 = IConnection(address(0x0000000000000000000000000000000000000011)); + connection2 = IConnection(address(0x0000000000000000000000000000000000000012)); + + vm.mockCall(address(connection1), abi.encodeWithSelector(connection1.getFee.selector), abi.encode(10)); + vm.mockCall(address(connection2), abi.encodeWithSelector(connection2.getFee.selector), abi.encode(20)); + + string[] memory sources = new string[](2); + sources[0] = ParseAddress.toString(address(connection1)); + sources[1] = ParseAddress.toString(address(connection2)); + + uint256 fee = callService.getFee(iconNid, true, sources); + assertEq(fee, 30); + } + + function testHandleMessageUnknownMsgType() public { + bytes memory data = bytes("data"); + + Types.CSMessage memory message = Types.CSMessage(3, data); + + vm.expectRevert("UnknownMsgType(3)"); + callService.handleMessage(iconNid, message.encodeCSMessage()); + } + function testSendMessageSingleProtocol() public { bytes memory data = bytes("test"); bytes memory rollbackData = bytes(""); @@ -296,6 +342,16 @@ contract CallServiceTest is Test { assertEq(sn, 1); } + function testSendInvalidMessageType() public { + bytes memory data = bytes("test"); + + bytes memory _msg = Types.XCallEnvelope(4, data, new string[](0), new string[](0)).encodeXCallEnvelope(); + + vm.expectRevert("Message type is not supported"); + vm.prank(address(dapp)); + uint256 sn = callService.sendCall{value: 0 ether}(iconDapp, _msg); + } + function testSendMessageResponse() public { bytes memory data = bytes("test"); bytes memory data2 = bytes("test2"); @@ -448,6 +504,13 @@ contract CallServiceTest is Test { callService.handleBTPMessage(iconNid, "xcallM", 1, RLPEncodeStruct.encodeCSMessage(message)); } + function testHandleBTPError() public { + string memory data = "data"; + + vm.expectRevert("CallRequestNotFound"); + callService.handleBTPError(iconNid, Types.NAME, 1, 1, data); + } + function testInvalidNid() public { bytes memory data = bytes("test"); callService.setDefaultConnection(iconNid, address(baseConnection)); @@ -545,6 +608,20 @@ contract CallServiceTest is Test { callService.executeCall(1, data); } + function testExecuteCallUnsupportedMessageType() public { + bytes memory data = bytes("test"); + + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, ParseAddress.toString(address(receiver)), 1, 4, data, _baseSource); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); + + vm.prank(address(baseConnection)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + vm.expectRevert("Message type is not yet supported"); + vm.prank(user); + callService.executeCall(1, data); + } + function testExecuteCallDefaultProtocol() public { bytes memory data = bytes("test"); diff --git a/contracts/evm/test/xcall/CallServiceV1.t.sol b/contracts/evm/test/xcall/CallServiceV1.t.sol new file mode 100644 index 00000000..851462c8 --- /dev/null +++ b/contracts/evm/test/xcall/CallServiceV1.t.sol @@ -0,0 +1,788 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "@xcall/contracts/xcall/CallServiceV1.sol"; +import "@xcall/contracts/xcall/interfaces/IConnection.sol"; +import "@xcall/utils/Types.sol"; +import "@xcall/contracts/mocks/dapp/DAppProxySample.sol"; + +contract CallServiceV1Test is Test { + CallServiceV1 public callService; + DAppProxySample public dapp; + + IConnection public baseConnection; + IConnection public connection1; + IConnection public connection2; + + ICallServiceReceiver public receiver; + IDefaultCallServiceReceiver public defaultServiceReceiver; + + using Strings for string; + using Integers for uint; + using ParseAddress for address; + using ParseAddress for string; + using NetworkAddress for string; + using RLPEncodeStruct for Types.CSMessage; + using RLPEncodeStruct for Types.CSMessageRequest; + using RLPEncodeStruct for Types.CSMessageResponse; + using RLPDecodeStruct for bytes; + + address public owner = address(0x1111); + address public user = address(0x1234); + + address public xcall; + string public iconNid = "0x2.ICON"; + string public ethNid = "0x1.ETH"; + string public iconDapp = NetworkAddress.networkAddress(iconNid, "0xa"); + + string public netTo; + string public dstAccount; + string public ethDappAddress; + + string public baseIconConnection = "0xb"; + + string[] _baseSource; + string[] _baseDestination; + + event CallMessage( + string indexed _from, + string indexed _to, + uint256 indexed _sn, + uint256 _reqId, + bytes _data + ); + + event CallExecuted( + uint256 indexed _reqId, + int _code, + string _msg + ); + + event CallMessageSent( + address indexed _from, + string indexed _to, + uint256 indexed _sn + ); + + event ResponseMessage( + uint256 indexed _sn, + int _code + ); + + event RollbackMessage( + uint256 indexed _sn + ); + + event RollbackExecuted( + uint256 indexed _sn + ); + + function setUp() public { + dapp = new DAppProxySample(); + ethDappAddress = NetworkAddress.networkAddress(ethNid, ParseAddress.toString(address(dapp))); + (netTo, dstAccount) = NetworkAddress.parseNetworkAddress(iconDapp); + + baseConnection = IConnection(address(0x01)); + + _baseSource = new string[](1); + _baseSource[0] = ParseAddress.toString(address(baseConnection)); + _baseDestination = new string[](1); + _baseDestination[0] = baseIconConnection; + vm.mockCall(address(baseConnection), abi.encodeWithSelector(baseConnection.getFee.selector), abi.encode(0)); + + callService = new CallServiceV1(); + callService.initialize(ethNid); + } + + function testSetAdmin() public { + callService.setAdmin(user); + assertEq(callService.admin(), user); + } + + function testSetAdminUnauthorized() public { + vm.prank(user); + vm.expectRevert("OnlyAdmin"); + callService.setAdmin(user); + } + + function testSetProtocolFees() public { + callService.setProtocolFee(10); + assertEq(callService.getProtocolFee(), 10); + } + + function testSetProtocolFeesAdmin() public { + callService.setAdmin(user); + vm.prank(user); + callService.setProtocolFee(10); + + assertEq(callService.getProtocolFee(), 10); + } + + function testSetProtocolFeesUnauthorized() public { + vm.prank(user); + vm.expectRevert("OnlyAdmin"); + callService.setProtocolFee(10); + } + + function testSetProtocolFeeFeeHandler() public { + callService.setProtocolFeeHandler(user); + assertEq(callService.getProtocolFeeHandler(), user); + } + + function testSetProtocolFeeHandlerUnauthorized() public { + vm.prank(user); + vm.expectRevert("OnlyAdmin"); + callService.setProtocolFeeHandler(user); + } + + function testGetNetworkId() public { + assertEq(callService.getNetworkId(), ethNid); + } + + function testGetDefaultConnection() public { + callService.setDefaultConnection(iconNid, address(baseConnection)); + + address defaultConnection = callService.getDefaultConnection(iconNid); + assertEq(defaultConnection, address(baseConnection)); + } + + function testGetConnectionFee() public { + connection1 = IConnection(address(0x0000000000000000000000000000000000000011)); + + callService.setDefaultConnection(iconNid, address(connection1)); + + vm.mockCall(address(connection1), abi.encodeWithSelector(connection1.getFee.selector), abi.encode(30)); + + uint256 fee = callService.getFee(iconNid, true); + assertEq(fee, 30); + } + + function testGetFeeMultipleProtocols() public { + connection1 = IConnection(address(0x0000000000000000000000000000000000000011)); + connection2 = IConnection(address(0x0000000000000000000000000000000000000012)); + + vm.mockCall(address(connection1), abi.encodeWithSelector(connection1.getFee.selector), abi.encode(10)); + vm.mockCall(address(connection2), abi.encodeWithSelector(connection2.getFee.selector), abi.encode(20)); + + string[] memory sources = new string[](2); + sources[0] = ParseAddress.toString(address(connection1)); + sources[1] = ParseAddress.toString(address(connection2)); + + uint256 fee = callService.getFee(iconNid, true, sources); + assertEq(fee, 30); + } + + function testHandleMessageUnknownMsgType() public { + bytes memory data = bytes("data"); + + Types.CSMessage memory message = Types.CSMessage(3, data); + + vm.expectRevert("UnknownMsgType(3)"); + callService.handleMessage(iconNid, message.encodeCSMessage()); + } + + function testSendMessageSingleProtocol() public { + bytes memory data = bytes("test"); + bytes memory rollbackData = bytes(""); + receiver = ICallServiceReceiver(address(0x02)); + + vm.prank(address(dapp)); + vm.expectEmit(); + emit CallMessageSent(address(dapp), iconDapp, 1); + + Types.CSMessageRequest memory request = Types.CSMessageRequest(ethDappAddress, dstAccount, 1, false, data, _baseDestination); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST, request.encodeCSMessageRequest()); + + vm.expectCall(address(baseConnection), abi.encodeCall(baseConnection.sendMessage, (iconNid, Types.NAME, 0, message.encodeCSMessage()))); + + uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData, _baseSource, _baseDestination); + assertEq(sn, 1); + + } + + function testSendMessageMultiProtocol() public { + bytes memory data = bytes("test"); + bytes memory rollbackData = bytes(""); + + connection1 = IConnection(address(0x0000000000000000000000000000000000000011)); + connection2 = IConnection(address(0x0000000000000000000000000000000000000012)); + + vm.mockCall(address(connection1), abi.encodeWithSelector(connection1.getFee.selector), abi.encode(0)); + vm.mockCall(address(connection2), abi.encodeWithSelector(connection2.getFee.selector), abi.encode(0)); + + string[] memory destinations = new string[](2); + destinations[0] = "0x1icon"; + destinations[1] = "0x2icon"; + + string[] memory sources = new string[](2); + sources[0] = ParseAddress.toString(address(connection1)); + sources[1] = ParseAddress.toString(address(connection2)); + + vm.expectEmit(); + emit CallMessageSent(address(dapp), iconDapp, 1); + + Types.CSMessageRequest memory request = Types.CSMessageRequest(ethDappAddress, dstAccount, 1, false, data, destinations); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + + vm.expectCall(address(connection1), abi.encodeCall(connection1.sendMessage, (iconNid, Types.NAME, 0, message.encodeCSMessage()))); + vm.expectCall(address(connection2), abi.encodeCall(connection2.sendMessage, (iconNid, Types.NAME, 0, message.encodeCSMessage()))); + + vm.prank(address(dapp)); + uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData, sources, destinations); + assertEq(sn, 1); + } + + function testSendMessageDefaultProtocol() public { + bytes memory data = bytes("test"); + bytes memory rollbackData = bytes("rollback"); + + callService.setDefaultConnection(iconNid, address(baseConnection)); + + vm.expectEmit(); + emit CallMessageSent(address(dapp), iconDapp, 1); + + Types.CSMessageRequest memory request = Types.CSMessageRequest(ethDappAddress, dstAccount, 1, true, data, new string[](0)); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + vm.expectCall(address(baseConnection), abi.encodeCall(baseConnection.sendMessage, (iconNid, Types.NAME, 1, message.encodeCSMessage()))); + + vm.prank(address(dapp)); + uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData); + assertEq(sn, 1); + } + + function testSendMessageDefaultProtocolNotSet() public { + bytes memory data = bytes("test"); + bytes memory rollbackData = bytes(""); + + vm.expectRevert("NoDefaultConnection"); + callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData); + } + + function testHandleResponseDefaultProtocol() public { + bytes memory data = bytes("test"); + + callService.setDefaultConnection(iconNid, address(baseConnection)); + + Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(dapp)), 1, false, data, new string[](0)); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + + vm.expectEmit(); + emit CallMessage(iconDapp, ParseAddress.toString(address(dapp)), 1, 1, data); + + vm.prank(address(baseConnection)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + } + + function testHandleBTPMessageWithInvalidService() public { + bytes memory data = bytes("test"); + + callService.setDefaultConnection(iconNid, address(baseConnection)); + + Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(dapp)), 1, false, data, new string[](0)); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + + vm.prank(address(baseConnection)); + vm.expectRevert("InvalidServiceName"); + callService.handleBTPMessage(iconNid, "btp", 1, RLPEncodeStruct.encodeCSMessage(message)); + } + + function testHandleBTPMessage() public { + bytes memory data = bytes("test"); + + callService.setDefaultConnection(iconNid, address(baseConnection)); + + Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(dapp)), 1, false, data, new string[](0)); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + + vm.expectEmit(); + emit CallMessage(iconDapp, ParseAddress.toString(address(dapp)), 1, 1, data); + + vm.prank(address(baseConnection)); + callService.handleBTPMessage(iconNid, "xcallM", 1, RLPEncodeStruct.encodeCSMessage(message)); + } + + function testHandleBTPError() public { + string memory data = "data"; + + callService.handleBTPError(iconNid, Types.NAME, 1, 1, data); + } + + function testInvalidNid() public { + bytes memory data = bytes("test"); + callService.setDefaultConnection(iconNid, address(baseConnection)); + + Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(dapp)), 1, false, data, new string[](0)); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + + vm.prank(address(baseConnection)); + vm.expectRevert("Invalid Network ID"); + callService.handleMessage(ethNid, RLPEncodeStruct.encodeCSMessage(message)); + } + + function testHandleResponseDefaultProtocolInvalidSender() public { + bytes memory data = bytes("test"); + + callService.setDefaultConnection(iconNid, address(baseConnection)); + Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(dapp)), 1, false, data, new string[](0)); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + + vm.prank(address(user)); + vm.expectRevert("NotAuthorized"); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + } + + function testHandleResponseSingleProtocol() public { + bytes memory data = bytes("test"); + + string[] memory sources = new string[](1); + sources[0] = ParseAddress.toString(address(baseConnection)); + + Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(dapp)), 1, false, data, sources); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + vm.prank(address(baseConnection)); + + vm.expectEmit(); + emit CallMessage(iconDapp, ParseAddress.toString(address(dapp)), 1, 1, data); + + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + } + + function testHandleResponseSingleProtocolInvalidSender() public { + bytes memory data = bytes("test"); + + string[] memory sources = new string[](1); + sources[0] = ParseAddress.toString(address(baseConnection)); + + Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(dapp)), 1, false, data, sources); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + + vm.prank(address(connection1)); + vm.expectRevert("NotAuthorized"); + + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + } + + function testHandleResponseMultiProtocol() public { + bytes memory data = bytes("test"); + + connection1 = IConnection(address(0x0000000000000000000000000000000000000011)); + connection2 = IConnection(address(0x0000000000000000000000000000000000000012)); + + vm.mockCall(address(connection1), abi.encodeWithSelector(connection1.getFee.selector), abi.encode(0)); + vm.mockCall(address(connection2), abi.encodeWithSelector(connection2.getFee.selector), abi.encode(0)); + + string[] memory connections = new string[](2); + connections[0] = ParseAddress.toString(address(connection1)); + connections[1] = ParseAddress.toString(address(connection2)); + + Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(dapp)), 1, false, data, connections); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + + vm.prank(address(connection1)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + vm.expectEmit(); + emit CallMessage(iconDapp, ParseAddress.toString(address(dapp)), 1, 1, data); + vm.prank(address(connection2)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + } + + function testExecuteCallSingleProtocol() public { + bytes memory data = bytes("test"); + + Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(receiver)), 1, false, data, _baseSource); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + + vm.prank(address(baseConnection)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + vm.expectEmit(); + emit CallExecuted(1, 1, ""); + + vm.prank(user); + vm.mockCall(address(receiver), abi.encodeWithSelector(receiver.handleCallMessage.selector, iconDapp, data, _baseSource), abi.encode(1)); + callService.executeCall(1, data); + } + + function testExecuteCallDefaultProtocol() public { + bytes memory data = bytes("test"); + + defaultServiceReceiver = IDefaultCallServiceReceiver(address(0x5678)); + callService.setDefaultConnection(netTo, address(baseConnection)); + + Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(defaultServiceReceiver)), 1, false, data, _baseSource); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + + vm.prank(address(baseConnection)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + vm.expectEmit(); + emit CallExecuted(1, 1, ""); + + vm.prank(user); + vm.mockCall(address(defaultServiceReceiver), abi.encodeWithSelector(defaultServiceReceiver.handleCallMessage.selector, iconDapp, data), abi.encode(1)); + callService.executeCall(1, data); + } + + function testExecuteCallMultiProtocol() public { + bytes memory data = bytes("test"); + + defaultServiceReceiver = IDefaultCallServiceReceiver(address(0x5678)); + connection1 = IConnection(address(0x0000000000000000000000000000000000000011)); + connection2 = IConnection(address(0x0000000000000000000000000000000000000012)); + + string[] memory connections = new string[](2); + connections[0] = ParseAddress.toString(address(connection1)); + connections[1] = ParseAddress.toString(address(connection2)); + + vm.mockCall(address(connection1), abi.encodeWithSelector(connection1.getFee.selector), abi.encode(0)); + vm.mockCall(address(connection2), abi.encodeWithSelector(connection2.getFee.selector), abi.encode(0)); + + Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(receiver)), 1, false, data, connections); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + + vm.prank(address(connection1)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + vm.prank(address(connection2)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + vm.expectEmit(); + emit CallExecuted(1, 1, ""); + + vm.prank(user); + vm.mockCall(address(receiver), abi.encodeWithSelector(receiver.handleCallMessage.selector, iconDapp, data, connections), abi.encode(1)); + callService.executeCall(1, data); + } + + function testRollBackSingleProtocol() public { + bytes memory data = bytes("test"); + bytes memory rollbackData = bytes("rollback"); + + vm.prank(address(dapp)); + vm.expectEmit(); + emit CallMessageSent(address(dapp), iconDapp, 1); + + uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData, _baseSource, _baseDestination); + assertEq(sn, 1); + + Types.CSMessageResponse memory response = Types.CSMessageResponse(1, Types.CS_RESP_FAILURE); + Types.CSMessage memory message = Types.CSMessage(Types.CS_RESPONSE, RLPEncodeStruct.encodeCSMessageResponse(response)); + + vm.expectEmit(); + emit ResponseMessage(1, Types.CS_RESP_FAILURE); + emit RollbackMessage(1); + + vm.prank(address(baseConnection)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + assertEq(callService.verifySuccess(sn),false); + } + + function testRollBackDefaultProtocol() public { + bytes memory data = bytes("test"); + bytes memory rollbackData = bytes("rollback"); + + callService.setDefaultConnection(netTo, address(baseConnection)); + + vm.prank(address(dapp)); + vm.expectEmit(); + emit CallMessageSent(address(dapp), iconDapp, 1); + + uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData, _baseSource, _baseDestination); + assertEq(sn, 1); + + Types.CSMessageResponse memory response = Types.CSMessageResponse(1, Types.CS_RESP_FAILURE); + Types.CSMessage memory message = Types.CSMessage(Types.CS_RESPONSE, RLPEncodeStruct.encodeCSMessageResponse(response)); + + vm.expectEmit(); + emit ResponseMessage(1, Types.CS_RESP_FAILURE); + + vm.prank(address(baseConnection)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + assertEq(callService.verifySuccess(sn),false); + } + + function testRollBackDefaultProtocolInvalidSender() public { + bytes memory data = bytes("test"); + bytes memory rollbackData = bytes("rollback"); + + callService.setDefaultConnection(netTo, address(baseConnection)); + + vm.prank(address(dapp)); + vm.expectEmit(); + emit CallMessageSent(address(dapp), iconDapp, 1); + + uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData, _baseSource, _baseDestination); + assertEq(sn, 1); + + Types.CSMessageResponse memory response = Types.CSMessageResponse(1, Types.CS_RESP_FAILURE); + Types.CSMessage memory message = Types.CSMessage(Types.CS_RESPONSE, RLPEncodeStruct.encodeCSMessageResponse(response)); + + vm.prank(address(user)); + vm.expectRevert("NotAuthorized"); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + assertEq(callService.verifySuccess(sn),false); + } + + function testRollbackMultiProtocol() public { + bytes memory data = bytes("test"); + bytes memory rollbackData = bytes("rollback"); + + connection1 = IConnection(address(0x0000000000000000000000000000000000000011)); + connection2 = IConnection(address(0x0000000000000000000000000000000000000012)); + + vm.mockCall(address(connection1), abi.encodeWithSelector(connection1.getFee.selector), abi.encode(0)); + vm.mockCall(address(connection2), abi.encodeWithSelector(connection2.getFee.selector), abi.encode(0)); + + string[] memory connections = new string[](2); + connections[0] = ParseAddress.toString(address(connection1)); + connections[1] = ParseAddress.toString(address(connection2)); + + string[] memory destinations = new string[](2); + destinations[0] = "0x1icon"; + destinations[1] = "0x2icon"; + + vm.prank(address(dapp)); + vm.expectEmit(); + emit CallMessageSent(address(dapp), iconDapp, 1); + + uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData, connections, destinations); + assertEq(sn, 1); + + Types.CSMessageResponse memory response = Types.CSMessageResponse(1, Types.CS_RESP_FAILURE); + Types.CSMessage memory message = Types.CSMessage(Types.CS_RESPONSE, RLPEncodeStruct.encodeCSMessageResponse(response)); + + vm.prank(address(connection1)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + vm.expectEmit(); + emit ResponseMessage(1, Types.CS_RESP_FAILURE); + + vm.prank(address(connection2)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + assertEq(callService.verifySuccess(sn),false); + } + + function testRollBackSuccess() public { + bytes memory data = bytes("test"); + bytes memory rollbackData = bytes("rollback"); + + vm.prank(address(dapp)); + vm.expectEmit(); + emit CallMessageSent(address(dapp), iconDapp, 1); + + uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData, _baseSource, _baseDestination); + assertEq(sn, 1); + + Types.CSMessageResponse memory response = Types.CSMessageResponse(1, Types.CS_RESP_SUCCESS); + Types.CSMessage memory message = Types.CSMessage(Types.CS_RESPONSE, RLPEncodeStruct.encodeCSMessageResponse(response)); + + vm.expectEmit(); + emit ResponseMessage(1, Types.CS_RESP_SUCCESS); + + vm.prank(address(baseConnection)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + assertEq(callService.verifySuccess(sn),true); + } + + function testExecuteRollBackDefaultProtocol() public { + bytes memory data = bytes("test"); + bytes memory rollbackData = bytes("rollback"); + + string memory xcallAddr = NetworkAddress.networkAddress(ethNid, ParseAddress.toString(address(callService))); + + callService.setDefaultConnection(iconNid, address(baseConnection)); + + vm.startPrank(address(dapp)); + + string[] memory connections = new string[](1); + connections[0] = ""; + + + uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData); + assertEq(sn, 1); + vm.stopPrank(); + + Types.CSMessageResponse memory msgRes = Types.CSMessageResponse(1, Types.CS_RESP_FAILURE); + Types.CSMessage memory message = Types.CSMessage(Types.CS_RESPONSE, msgRes.encodeCSMessageResponse()); + + vm.prank(address(baseConnection)); + callService.handleMessage(iconNid, message.encodeCSMessage()); + + vm.expectEmit(); + emit RollbackExecuted(1); + + vm.mockCall(address(dapp), abi.encodeWithSelector(dapp.handleCallMessage.selector, xcallAddr, rollbackData), abi.encode(1)); + vm.prank(user); + callService.executeRollback(1); + + assertEq(callService.verifySuccess(sn),false); + } + + function testExecuteRollBackSingleProtocol() public { + bytes memory data = bytes("test"); + bytes memory rollbackData = bytes("rollback"); + + string memory xcallAddr = NetworkAddress.networkAddress(ethNid, ParseAddress.toString(address(callService))); + + vm.prank(address(dapp)); + vm.expectEmit(); + emit CallMessageSent(address(dapp), iconDapp, 1); + + uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData, _baseSource, _baseDestination); + assertEq(sn, 1); + + Types.CSMessageResponse memory msgRes = Types.CSMessageResponse(1, Types.CS_RESP_FAILURE); + Types.CSMessage memory message = Types.CSMessage(Types.CS_RESPONSE, msgRes.encodeCSMessageResponse()); + + vm.prank(address(baseConnection)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + vm.expectEmit(); + emit RollbackExecuted(1); + + vm.mockCall(address(dapp), abi.encodeWithSelector(receiver.handleCallMessage.selector, xcallAddr, rollbackData, _baseSource), abi.encode(1)); + vm.prank(user); + callService.executeRollback(1); + + assertEq(callService.verifySuccess(sn),false); + } + + function testExecuteRollbackMultiProtocol() public { + bytes memory data = bytes("test"); + bytes memory rollbackData = bytes("rollback"); + + string memory xcallAddr = NetworkAddress.networkAddress(ethNid, ParseAddress.toString(address(callService))); + + connection1 = IConnection(address(0x0000000000000000000000000000000000000011)); + connection2 = IConnection(address(0x0000000000000000000000000000000000000012)); + + vm.mockCall(address(connection1), abi.encodeWithSelector(connection1.getFee.selector), abi.encode(0)); + vm.mockCall(address(connection2), abi.encodeWithSelector(connection2.getFee.selector), abi.encode(0)); + + string[] memory connections = new string[](2); + connections[0] = ParseAddress.toString(address(connection1)); + connections[1] = ParseAddress.toString(address(connection2)); + + string[] memory destinations = new string[](2); + destinations[0] = "0x1icon"; + destinations[1] = "0x2icon"; + + vm.prank(address(dapp)); + vm.expectEmit(); + emit CallMessageSent(address(dapp), iconDapp, 1); + + uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData, connections, destinations); + assertEq(sn, 1); + + Types.CSMessageResponse memory response = Types.CSMessageResponse(1, Types.CS_RESP_FAILURE); + Types.CSMessage memory message = Types.CSMessage(Types.CS_RESPONSE, RLPEncodeStruct.encodeCSMessageResponse(response)); + + vm.prank(address(connection1)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + vm.expectEmit(); + emit ResponseMessage(1, Types.CS_RESP_FAILURE); + emit RollbackMessage(1); + + vm.prank(address(connection2)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + vm.prank(user); + vm.mockCall(address(dapp), abi.encodeWithSelector(receiver.handleCallMessage.selector, xcallAddr, rollbackData, connections), abi.encode(1)); + callService.executeRollback(sn); + + assertEq(callService.verifySuccess(sn),false); + } + + function testExecuteCallMultiProtocolRollback() public { + bytes memory data = bytes("test"); + + defaultServiceReceiver = IDefaultCallServiceReceiver(address(0x5678)); + connection1 = IConnection(address(0x0000000000000000000000000000000000000011)); + connection2 = IConnection(address(0x0000000000000000000000000000000000000012)); + + string[] memory connections = new string[](2); + connections[0] = ParseAddress.toString(address(connection1)); + connections[1] = ParseAddress.toString(address(connection2)); + + vm.mockCall(address(connection1), abi.encodeWithSelector(connection1.getFee.selector), abi.encode(0)); + vm.mockCall(address(connection2), abi.encodeWithSelector(connection2.getFee.selector), abi.encode(0)); + + Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(receiver)), 1, true, data, connections); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + + vm.prank(address(connection1)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + vm.prank(address(connection2)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + vm.expectEmit(); + emit CallExecuted(1, 1, ""); + + vm.prank(user); + vm.mockCall(address(receiver), abi.encodeWithSelector(receiver.handleCallMessage.selector, iconDapp, data, connections), abi.encode(1)); + + Types.CSMessageResponse memory msgResponse = Types.CSMessageResponse(1, Types.CS_RESP_SUCCESS); + message = Types.CSMessage(Types.CS_RESPONSE, RLPEncodeStruct.encodeCSMessageResponse(msgResponse)); + + vm.expectCall(address(connection1), abi.encodeCall(connection1.sendMessage, (iconNid, Types.NAME, -1, message.encodeCSMessage()))); + vm.expectCall(address(connection2), abi.encodeCall(connection2.sendMessage, (iconNid, Types.NAME, -1, message.encodeCSMessage()))); + + callService.executeCall(1, data); + } + + function testExecuteCallDefaultProtocolRollback() public { + bytes memory data = bytes("test"); + + defaultServiceReceiver = IDefaultCallServiceReceiver(address(0x5678)); + callService.setDefaultConnection(netTo, address(baseConnection)); + + Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(defaultServiceReceiver)), 1, true, data, _baseSource); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + + vm.prank(address(baseConnection)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + vm.expectEmit(); + emit CallExecuted(1, 1, ""); + + vm.prank(user); + vm.mockCall(address(defaultServiceReceiver), abi.encodeWithSelector(defaultServiceReceiver.handleCallMessage.selector, iconDapp, data), abi.encode(0)); + + Types.CSMessageResponse memory msgResponse = Types.CSMessageResponse(1, Types.CS_RESP_SUCCESS); + message = Types.CSMessage(Types.CS_RESPONSE, RLPEncodeStruct.encodeCSMessageResponse(msgResponse)); + vm.expectCall(address(baseConnection), abi.encodeCall(baseConnection.sendMessage, (iconNid, Types.NAME, -1, message.encodeCSMessage()))); + callService.executeCall(1, data); + } + + + function testExecuteCallFailedExecution() public { + bytes memory data = bytes("test"); + + Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(receiver)), 1, true, data, _baseSource); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + + vm.prank(address(baseConnection)); + vm.mockCallRevert(address(baseConnection), abi.encodeWithSelector(receiver.handleCallMessage.selector, iconDapp, data, _baseSource), bytes("UserRevert")); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + + Types.CSMessageResponse memory msgResponse = Types.CSMessageResponse(1, Types.CS_RESP_FAILURE); + message = Types.CSMessage(Types.CS_RESPONSE, RLPEncodeStruct.encodeCSMessageResponse(msgResponse)); + + vm.expectEmit(); + emit CallExecuted(1, 0, "unknownError"); + + vm.expectCall(address(baseConnection), abi.encodeCall(baseConnection.sendMessage, (iconNid, Types.NAME, -1, message.encodeCSMessage()))); + + callService.executeCall(1, data); + } + +}