diff --git a/.github/workflows/cw-codecov.yml b/.github/workflows/cw-codecov.yml index 084e123..a32f1f7 100644 --- a/.github/workflows/cw-codecov.yml +++ b/.github/workflows/cw-codecov.yml @@ -32,7 +32,7 @@ jobs: uses: taiki-e/install-action@cargo-llvm-cov - name: Generate code coverage - run: cargo llvm-cov --lcov --output-path lcov.info --package cw-asset-manager --package cw-hub-bnusd + run: cargo llvm-cov --lcov --output-path lcov.info --package cw-asset-manager --package cw-hub-bnusd --package cw-xcall-manager - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 diff --git a/Cargo.lock b/Cargo.lock index 288eceb..6470f7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -368,6 +368,7 @@ dependencies = [ "cw-utils", "cw-xcall 0.1.0 (git+https://github.com/icon-project/xcall-multi.git?branch=main)", "cw-xcall-lib 0.1.0 (git+https://github.com/icon-project/xcall-multi.git?branch=main)", + "cw-xcall-manager", "cw2", "cw20", "cw20-base", @@ -462,6 +463,7 @@ dependencies = [ "cw-storage-plus 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "cw-xcall 0.1.0 (git+https://github.com/icon-project/xcall-multi.git?branch=main)", "cw-xcall-lib 0.1.0 (git+https://github.com/icon-project/xcall-multi.git?branch=main)", + "cw-xcall-manager", "cw2", "cw20", "cw20-base", @@ -657,6 +659,32 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw-xcall-manager" +version = "0.1.0" +dependencies = [ + "bytes", + "common 0.1.0 (git+https://github.com/icon-project/IBC-Integration.git?branch=main)", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-common 0.1.0", + "cw-common 0.1.0 (git+https://github.com/icon-project/IBC-Integration.git?branch=main)", + "cw-mock-ibc-connection 0.1.0 (git+https://github.com/icon-project/IBC-Integration.git?branch=main)", + "cw-mock-ibc-core", + "cw-multi-test", + "cw-storage-plus 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cw-utils", + "cw-xcall 0.1.0 (git+https://github.com/icon-project/xcall-multi.git?branch=main)", + "cw-xcall-lib 0.1.0 (git+https://github.com/icon-project/xcall-multi.git?branch=main)", + "cw2", + "cw20-base", + "debug_print", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw2" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index e740c3c..9be2277 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ # "contracts/core-contracts/*", "contracts/token-contracts/cw-hub-bnusd", "contracts/core-contracts/cw-asset-manager", + "contracts/core-contracts/cw-xcall-manager", "contracts/cw-common", ] diff --git a/contracts/core-contracts/cw-asset-manager/Cargo.toml b/contracts/core-contracts/cw-asset-manager/Cargo.toml index 6f5ddeb..1dcc558 100644 --- a/contracts/core-contracts/cw-asset-manager/Cargo.toml +++ b/contracts/core-contracts/cw-asset-manager/Cargo.toml @@ -49,6 +49,7 @@ debug_print = {workspace=true} [dev-dependencies] cw-multi-test = "0.16.4" cw20-base = "1.0.1" +cw-xcall-manager = { path = "../cw-xcall-manager" } cw-xcall-multi = {package="cw-xcall", git="https://github.com/icon-project/xcall-multi.git", branch="main", features=["library"]} cw-xcall-lib={package="cw-xcall-lib", git="https://github.com/icon-project/xcall-multi.git", branch="main", features = ["library"]} cw-common-ibc = { git = "https://github.com/icon-project/IBC-Integration.git", branch = "main", package = "cw-common"} diff --git a/contracts/core-contracts/cw-asset-manager/src/contract.rs b/contracts/core-contracts/cw-asset-manager/src/contract.rs index 945d320..6468941 100644 --- a/contracts/core-contracts/cw-asset-manager/src/contract.rs +++ b/contracts/core-contracts/cw-asset-manager/src/contract.rs @@ -2,16 +2,17 @@ use std::str::FromStr; use cosmwasm_std::{ensure, ensure_eq, entry_point}; use cosmwasm_std::{ - to_binary, Addr, Binary, Deps, DepsMut, Env, Event, MessageInfo, QueryRequest, Response, - StdResult, SubMsg, Uint128, WasmMsg, WasmQuery, + to_binary, Addr, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, SubMsg, + Uint128, WasmMsg, }; use cw2::set_contract_version; use cw20::{AllowanceResponse, Cw20ExecuteMsg, Cw20QueryMsg}; use cw_common::asset_manager_msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use cw_common::helpers::{get_protocols, verify_protocol}; use cw_common::network_address::IconAddressValidation; use cw_common::network_address::NetworkAddress; -use cw_common::x_call_msg::{GetNetworkAddress, XCallMsg}; +use cw_common::x_call_msg::XCallMsg; use cw_common::xcall_data_types::Deposit; use crate::constants::SUCCESS_REPLY_MSG; @@ -35,7 +36,12 @@ pub fn instantiate( .map_err(ContractError::Std)?; OWNER.save(deps.storage, &info.sender)?; - setup(deps, msg.source_xcall, msg.destination_asset_manager) + setup( + deps, + msg.source_xcall, + msg.destination_asset_manager, + msg.manager, + ) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -49,12 +55,23 @@ pub fn execute( ExecuteMsg::ConfigureXcall { source_xcall, destination_asset_manager, + manager, } => { let owner = OWNER.load(deps.storage).map_err(ContractError::Std)?; ensure_eq!(owner, info.sender, ContractError::OnlyOwner); - setup(deps, source_xcall, destination_asset_manager) + setup(deps, source_xcall, destination_asset_manager, manager) } - ExecuteMsg::HandleCallMessage { from, data } => { + ExecuteMsg::HandleCallMessage { + from, + data, + protocols, + } => { + let xcall_manger = X_CALL_MANAGER.load(deps.storage)?; + let res = verify_protocol(&deps, xcall_manger, protocols); + if res.is_err() { + return Err(ContractError::Unauthorized); + } + exec::handle_xcall_msg(deps, env, info, from, data) } ExecuteMsg::ConfigureNative { @@ -117,27 +134,15 @@ mod exec { use cosmwasm_std::CosmosMsg; use cw_ibc_rlp_lib::rlp::Encodable; - use cw_common::xcall_data_types::DepositRevert; + use cw_common::{helpers::query_network_address, xcall_data_types::DepositRevert}; use super::*; - fn query_network_address( - deps: &DepsMut, - x_call_addr: &Addr, - ) -> Result { - let query_msg = GetNetworkAddress {}; - let query = QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: x_call_addr.to_string(), - msg: to_binary(&query_msg).map_err(ContractError::Std)?, - }); - - deps.querier.query(&query).map_err(ContractError::Std) - } - pub fn setup( deps: DepsMut, source_xcall: String, destination_asset_manager: String, + xcall_manager: Addr, ) -> Result { // validate source xcall let x_call_addr = deps @@ -145,7 +150,8 @@ mod exec { .addr_validate(&source_xcall) .map_err(ContractError::Std)?; - let xcall_network_address: NetworkAddress = query_network_address(&deps, &x_call_addr)?; + let xcall_network_address: NetworkAddress = + query_network_address(&deps, &x_call_addr).unwrap(); if xcall_network_address.to_string().is_empty() { return Err(ContractError::XAddressNotFound); @@ -169,6 +175,7 @@ mod exec { NID.save(deps.storage, &nid)?; ICON_ASSET_MANAGER.save(deps.storage, &icon_asset_manager)?; ICON_NET_ID.save(deps.storage, &icon_asset_manager.nid())?; + X_CALL_MANAGER.save(deps.storage, &xcall_manager)?; Ok(Response::default()) } @@ -253,6 +260,7 @@ mod exec { let source_xcall = SOURCE_XCALL.load(deps.storage)?; //create xcall msg for dispatching send call + let protocol_config = get_protocols(&deps, X_CALL_MANAGER.load(deps.storage)?).unwrap(); let xcall_message = XCallMsg::SendCallMessage { to: dest_am.to_string().parse()?, data: xcall_data.rlp_bytes().to_vec(), @@ -265,8 +273,8 @@ mod exec { .rlp_bytes() .to_vec(), ), - sources: None, - destinations: None, + sources: Some(protocol_config.sources), + destinations: Some(protocol_config.destinations), }; let xcall_msg = CosmosMsg::Wasm(WasmMsg::Execute { @@ -497,10 +505,12 @@ mod tests { ContractInfoResponse, ContractResult, MemoryStorage, OwnedDeps, SystemResult, Uint128, WasmQuery, }; + use cw_common::xcall_manager_msg::QueryMsg::GetProtocols; use cw_ibc_rlp_lib::rlp::Encodable; + use std::vec; - use cw_common::xcall_data_types::DepositRevert; use cw_common::{asset_manager_msg::InstantiateMsg, xcall_data_types::WithdrawTo}; + use cw_common::{xcall_data_types::DepositRevert, xcall_manager_msg::ProtocolConfig}; use super::*; @@ -516,17 +526,26 @@ mod tests { let info = mock_info("user", &[]); //to pretend us as xcall contract during handle call execution testing let xcall = "xcall"; + let manager = "manager"; // mocking response for external query i.e. allowance deps.querier.update_wasm(|r: &WasmQuery| match r { - WasmQuery::Smart { - contract_addr, - msg: _, - } => { + WasmQuery::Smart { contract_addr, msg } => { if contract_addr == &xcall.to_owned() { SystemResult::Ok(ContractResult::Ok( to_binary(&"0x44.archway/xcall".to_owned()).unwrap(), )) + } else if contract_addr == &manager.to_owned() { + if msg == &to_binary(&GetProtocols {}).unwrap() { + return SystemResult::Ok(ContractResult::Ok( + to_binary(&ProtocolConfig { + sources: vec![], + destinations: vec![], + }) + .unwrap(), + )); + } + SystemResult::Ok(ContractResult::Ok(to_binary(&true).unwrap())) } else { //mock allowance resp let allowance_resp = AllowanceResponse { @@ -553,6 +572,7 @@ mod tests { source_xcall: xcall.to_owned(), destination_asset_manager: "0x01.icon/cxc2d01de5013778d71d99f985e4e2ff3a9b48a66c" .to_owned(), + manager: Addr::unchecked(manager), }, ) .unwrap(); @@ -696,6 +716,7 @@ mod tests { let msg = ExecuteMsg::HandleCallMessage { from: xcall_nw.to_string(), data: x_deposit_revert.rlp_bytes().to_vec(), + protocols: vec![], }; let result = execute(deps.as_mut(), env.clone(), mocked_xcall_info.clone(), msg); @@ -715,6 +736,7 @@ mod tests { let exe_msg = ExecuteMsg::HandleCallMessage { from: am_nw.to_string(), data: withdraw_msg.rlp_bytes().to_vec(), + protocols: vec![], }; let resp = execute( deps.as_mut(), @@ -739,6 +761,7 @@ mod tests { let unknown_msg = ExecuteMsg::HandleCallMessage { from: xcall_nw.to_string(), data: x_msg.rlp_bytes().to_vec(), + protocols: vec![], }; //check for error due to unknown xcall handle data @@ -763,25 +786,30 @@ mod tests { deps.querier.update_wasm(|r: &WasmQuery| match r { WasmQuery::Smart { - contract_addr: _, + contract_addr, msg: _, - } => SystemResult::Ok(ContractResult::Ok( - to_binary(&ConfigResponse { - admin: "".to_string(), - pause_admin: "".to_string(), - bond_denom: "".to_string(), - liquid_token_addr: "".to_string(), - swap_contract_addr: swap.to_string(), - treasury_contract_addr: "".to_string(), - team_wallet_addr: "".to_string(), - commission_percentage: 1, - team_percentage: 1, - liquidity_percentage: 1, - delegations: vec![], - contract_state: false, - }) - .unwrap(), - )), + } => { + if contract_addr == &"manager".to_owned() { + return SystemResult::Ok(ContractResult::Ok(to_binary(&true).unwrap())); + } + SystemResult::Ok(ContractResult::Ok( + to_binary(&ConfigResponse { + admin: "".to_string(), + pause_admin: "".to_string(), + bond_denom: "".to_string(), + liquid_token_addr: "".to_string(), + swap_contract_addr: swap.to_string(), + treasury_contract_addr: "".to_string(), + team_wallet_addr: "".to_string(), + commission_percentage: 1, + team_percentage: 1, + liquidity_percentage: 1, + delegations: vec![], + contract_state: false, + }) + .unwrap(), + )) + } _ => todo!(), }); @@ -806,6 +834,7 @@ mod tests { let exe_msg = ExecuteMsg::HandleCallMessage { from: am_nw.to_string(), data: withdraw_msg.rlp_bytes().to_vec(), + protocols: vec![], }; let resp = execute( deps.as_mut(), @@ -828,6 +857,7 @@ mod tests { let msg = ExecuteMsg::ConfigureXcall { source_xcall: source_xcall.to_owned(), destination_asset_manager: destination_asset_manager.to_owned(), + manager: Addr::unchecked("manager".to_owned()), }; let res = execute(deps.as_mut(), env.clone(), info, msg.clone()); diff --git a/contracts/core-contracts/cw-asset-manager/src/error.rs b/contracts/core-contracts/cw-asset-manager/src/error.rs index d3db2cb..3be0a2c 100644 --- a/contracts/core-contracts/cw-asset-manager/src/error.rs +++ b/contracts/core-contracts/cw-asset-manager/src/error.rs @@ -64,6 +64,9 @@ pub enum ContractError { #[error("Rlp Error: {error}")] DecoderError { error: DecoderError }, + + #[error("Unauthorized")] + Unauthorized, } impl From for ContractError { diff --git a/contracts/core-contracts/cw-asset-manager/src/state.rs b/contracts/core-contracts/cw-asset-manager/src/state.rs index 3621369..d5f1a75 100644 --- a/contracts/core-contracts/cw-asset-manager/src/state.rs +++ b/contracts/core-contracts/cw-asset-manager/src/state.rs @@ -8,10 +8,11 @@ pub const OWNER: Item = Item::new("contract_owner"); pub const SOURCE_XCALL: Item = Item::new("source_xcall_address"); pub const X_CALL_NETWORK_ADDRESS: Item = Item::new("source_xcall_network_address"); pub const NID: Item = Item::new("network_id"); - pub const ICON_ASSET_MANAGER: Item = Item::new("icon_asset_manager_network_address"); pub const ICON_NET_ID: Item = Item::new("icon_asset_manager_network_id"); pub const NATIVE_TOKEN_ADDRESS: Item = Item::new("native_token_address"); pub const NATIVE_TOKEN_MANAGER: Item = Item::new("native_token_manager"); + +pub const X_CALL_MANAGER: Item = Item::new("xcall_manager"); diff --git a/contracts/core-contracts/cw-asset-manager/tests/setup.rs b/contracts/core-contracts/cw-asset-manager/tests/setup.rs index 0fa18bd..0568f03 100644 --- a/contracts/core-contracts/cw-asset-manager/tests/setup.rs +++ b/contracts/core-contracts/cw-asset-manager/tests/setup.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::str::FromStr; +use cw_common::xcall_manager_msg::{self, ProtocolConfig}; use cw_multi_test::{App, AppResponse}; use cw_asset_manager::contract::{execute, instantiate, query}; @@ -32,6 +33,7 @@ use cw_common::{ #[derive(Debug, PartialEq, Eq, Hash)] pub enum TestApps { XCall, + XCallManager, AssetManager, CW20Token, XcallConnection, @@ -89,6 +91,13 @@ impl TestContext { pub fn get_ibc_core(&self) -> Addr { return self.contracts.get(&TestApps::IbcCore).unwrap().clone(); } + + pub fn set_xcall_manager(&mut self, addr: Addr) -> Option { + self.contracts.insert(TestApps::XCallManager, addr) + } + pub fn get_xcall_manager(&self) -> Addr { + return self.contracts.get(&TestApps::XCallManager).unwrap().clone(); + } } //initialize test context at the initial test state @@ -135,6 +144,14 @@ pub fn ibc_mock_core_setup() -> Box> { ) } +pub fn xcall_manager_setup() -> Box> { + Box::new(ContractWrapper::new( + cw_xcall_manager::contract::execute, + cw_xcall_manager::contract::instantiate, + cw_xcall_manager::contract::query, + )) +} + //--------------------------------INITIALIZER FUNCTION HELPERS--------------------------------------------------------- pub fn init_x_call(mut ctx: TestContext) -> TestContext { let code: Box> = x_call_contract_setup(); @@ -199,6 +216,32 @@ pub fn init_mock_ibc_core(mut ctx: TestContext) -> TestContext { ctx } +pub fn init_xcall_manager(mut ctx: TestContext) -> TestContext { + let code: Box> = xcall_manager_setup(); + let code_id = ctx.app.store_code(code); + + let addr = ctx + .app + .instantiate_contract( + code_id, + ctx.sender.clone(), + &xcall_manager_msg::InstantiateMsg { + xcall: ctx.get_xcall_app(), + icon_governance: "ICON_GOVERNANCE".to_string(), + protocols: ProtocolConfig { + sources: vec![ctx.get_xcall_connection().to_string()], + destinations: vec![], + }, + }, + &[], + "IbcCore", + None, + ) + .unwrap(); + ctx.set_xcall_manager(addr); + ctx +} + pub fn init_cw20_token_contract(mut ctx: TestContext) -> TestContext { let code: Box> = cw20_contract_setup(); let cw20_id = ctx.app.store_code(code); @@ -239,6 +282,7 @@ pub fn init_asset_manager(mut ctx: TestContext, x_call: Addr) -> TestContext { source_xcall: Addr::unchecked(x_call).into_string(), destination_asset_manager: "0x01.icon/cx7866543210fedcba9876543210fedcba987654df" .to_owned(), + manager: ctx.get_xcall_manager(), }, &[], "XCall", @@ -255,6 +299,7 @@ pub fn instantiate_contracts(mut ctx: TestContext) -> TestContext { ctx = init_xcall_connection_contract(ctx); ctx = init_cw20_token_contract(ctx); let xcall = ctx.get_xcall_app(); + ctx = init_xcall_manager(ctx); ctx = init_asset_manager(ctx, xcall); ctx } @@ -271,6 +316,7 @@ pub fn execute_config_x_call(mut ctx: TestContext, x_call: Addr) -> TestContext source_xcall: Addr::unchecked(x_call).into_string(), destination_asset_manager: "0x01.icon/cx7866543210fedcba9876543210fedcba987654df" .to_owned(), + manager: ctx.get_xcall_manager(), }, &[], ) diff --git a/contracts/core-contracts/cw-asset-manager/tests/x_handle_msg_test.rs b/contracts/core-contracts/cw-asset-manager/tests/x_handle_msg_test.rs index 76978ba..af74926 100644 --- a/contracts/core-contracts/cw-asset-manager/tests/x_handle_msg_test.rs +++ b/contracts/core-contracts/cw-asset-manager/tests/x_handle_msg_test.rs @@ -10,21 +10,18 @@ use cw_common::{ x_call_msg::XCallMsg, xcall_data_types::WithdrawTo, }; -use setup::{get_event, instantiate_contracts, set_default_connection, setup_context, TestContext}; +use setup::{get_event, instantiate_contracts, setup_context, TestContext}; mod setup; fn execute_handle_msg_on_asset_manager_from_relayer(mut ctx: TestContext) -> TestContext { - let relay = Addr::unchecked("relayer"); + let relay = ctx.get_xcall_connection(); let asset_manager = ctx.get_asset_manager_app(); let token = ctx.get_cw20token_app(); let user = Addr::unchecked("archway1user"); // ---------------------------- execution flow from RELAYER------> XCALL -------------------------------------------- - //pretend relayer for the connection such that relay can call ExecuteCall msg on xcall - ctx = set_default_connection(ctx, relay.clone()); - let call_data = WithdrawTo { token_address: token.to_string(), user_address: user.to_string(), @@ -49,7 +46,8 @@ fn execute_handle_msg_on_asset_manager_from_relayer(mut ctx: TestContext) -> Tes stream.append(&sn); stream.append(&false); stream.append(&data); - stream.begin_list(0); + stream.begin_list(1); + stream.append(&relay.to_string()); let encoded_data = stream.out().to_vec(); diff --git a/contracts/core-contracts/cw-xcall-manager/.cargo/config b/contracts/core-contracts/cw-xcall-manager/.cargo/config new file mode 100644 index 0000000..af5698e --- /dev/null +++ b/contracts/core-contracts/cw-xcall-manager/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --bin schema" diff --git a/contracts/core-contracts/cw-xcall-manager/Cargo.toml b/contracts/core-contracts/cw-xcall-manager/Cargo.toml new file mode 100644 index 0000000..7f21566 --- /dev/null +++ b/contracts/core-contracts/cw-xcall-manager/Cargo.toml @@ -0,0 +1,67 @@ +[package] +name = "cw-xcall-manager" +version = "0.1.0" +authors = ["Anton Andell "] +edition = "2021" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] +default = ["archway"] +archway = [] + +[package.metadata.scripts] +optimize = """docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.12.10 +""" + +[dependencies] +cosmwasm-schema = "1.2.6" +cosmwasm-std = "1.2.6" +cosmwasm-storage = "1.2.6" +cw-storage-plus = "1.0.1" +cw2 = "1.0.1" +schemars = "0.8.12" +serde = { version = "1.0.163", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.40" } +cw-common = { path = "../../cw-common" } +cw_ibc_rlp_lib = {git = "https://github.com/icon-project/IBC-Integration.git", branch="main", package = "common"} +cw-utils = "1.0.1" +debug_print = {workspace=true} +cw-xcall-lib={package="cw-xcall-lib", git="https://github.com/icon-project/xcall-multi.git", branch="main", features = ["library"]} +bytes = "1.0" +cw-xcall-multi = {package="cw-xcall", git="https://github.com/icon-project/xcall-multi.git", branch="main", features=["library"]} + +[dev-dependencies] +cw-multi-test = "0.16.4" +cw20-base = "1.0.1" +cw-xcall-lib={package="cw-xcall-lib", git="https://github.com/icon-project/xcall-multi.git", branch="main", features = ["library"]} +cw-common-ibc = { git = "https://github.com/icon-project/IBC-Integration.git", branch = "main", package = "cw-common"} +cw_mock_ibc_connection = { git = "https://github.com/icon-project/IBC-Integration.git",branch = "main", package = "cw-mock-ibc-connection"} +cw_mock_ibc_core = { git = "https://github.com/icon-project/IBC-Integration.git", branch="main", package="cw-mock-ibc-core" } + +[profile.release] +# Do not perform backtrace for panic on release builds. +panic = 'abort' +# Perform optimizations on all codegen units. +codegen-units = 1 +# Optimize for size. +opt-level = 'z' # or 'z' to optimize "aggressively" for size +# Enable link time optimization. +lto = true +strip = true diff --git a/contracts/core-contracts/cw-xcall-manager/README.md b/contracts/core-contracts/cw-xcall-manager/README.md new file mode 100644 index 0000000..054ea48 --- /dev/null +++ b/contracts/core-contracts/cw-xcall-manager/README.md @@ -0,0 +1,99 @@ +# CosmWasm Starter Pack + +This is a template to build smart contracts in Rust to run inside a +[Cosmos SDK](https://github.com/cosmos/cosmos-sdk) module on all chains that enable it. +To understand the framework better, please read the overview in the +[cosmwasm repo](https://github.com/CosmWasm/cosmwasm/blob/master/README.md), +and dig into the [cosmwasm docs](https://www.cosmwasm.com). +This assumes you understand the theory and just want to get coding. + +## Creating a new repo from template + +Assuming you have a recent version of Rust and Cargo installed +(via [rustup](https://rustup.rs/)), +then the following should get you a new repo to start a contract: + +Install [cargo-generate](https://github.com/ashleygwilliams/cargo-generate) and cargo-run-script. +Unless you did that before, run this line now: + +```sh +cargo install cargo-generate --features vendored-openssl +cargo install cargo-run-script +``` + +Now, use it to create your new contract. +Go to the folder in which you want to place it and run: + +**Latest** + +```sh +cargo generate --git https://github.com/CosmWasm/cw-template.git --name PROJECT_NAME +``` + +For cloning minimal code repo: + +```sh +cargo generate --git https://github.com/CosmWasm/cw-template.git --name PROJECT_NAME -d minimal=true +``` + +**Older Version** + +Pass version as branch flag: + +```sh +cargo generate --git https://github.com/CosmWasm/cw-template.git --branch --name PROJECT_NAME +``` + +Example: + +```sh +cargo generate --git https://github.com/CosmWasm/cw-template.git --branch 0.16 --name PROJECT_NAME +``` + +You will now have a new folder called `PROJECT_NAME` (I hope you changed that to something else) +containing a simple working contract and build system that you can customize. + +## Create a Repo + +After generating, you have a initialized local git repo, but no commits, and no remote. +Go to a server (eg. github) and create a new upstream repo (called `YOUR-GIT-URL` below). +Then run the following: + +```sh +# this is needed to create a valid Cargo.lock file (see below) +cargo check +git branch -M main +git add . +git commit -m 'Initial Commit' +git remote add origin YOUR-GIT-URL +git push -u origin main +``` + +## CI Support + +We have template configurations for both [GitHub Actions](.github/workflows/Basic.yml) +and [Circle CI](.circleci/config.yml) in the generated project, so you can +get up and running with CI right away. + +One note is that the CI runs all `cargo` commands +with `--locked` to ensure it uses the exact same versions as you have locally. This also means +you must have an up-to-date `Cargo.lock` file, which is not auto-generated. +The first time you set up the project (or after adding any dep), you should ensure the +`Cargo.lock` file is updated, so the CI will test properly. This can be done simply by +running `cargo check` or `cargo unit-test`. + +## Using your project + +Once you have your custom repo, you should check out [Developing](./Developing.md) to explain +more on how to run tests and develop code. Or go through the +[online tutorial](https://docs.cosmwasm.com/) to get a better feel +of how to develop. + +[Publishing](./Publishing.md) contains useful information on how to publish your contract +to the world, once you are ready to deploy it on a running blockchain. And +[Importing](./Importing.md) contains information about pulling in other contracts or crates +that have been published. + +Please replace this README file with information about your specific project. You can keep +the `Developing.md` and `Publishing.md` files as useful referenced, but please set some +proper description in the README. diff --git a/contracts/core-contracts/cw-xcall-manager/src/bin/schema.rs b/contracts/core-contracts/cw-xcall-manager/src/bin/schema.rs new file mode 100644 index 0000000..174ece8 --- /dev/null +++ b/contracts/core-contracts/cw-xcall-manager/src/bin/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; + +use cw_common::asset_manager_msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} diff --git a/contracts/core-contracts/cw-xcall-manager/src/contract.rs b/contracts/core-contracts/cw-xcall-manager/src/contract.rs new file mode 100644 index 0000000..e7a275a --- /dev/null +++ b/contracts/core-contracts/cw-xcall-manager/src/contract.rs @@ -0,0 +1,244 @@ +use std::collections::HashSet; +use std::hash::Hash; + +use cosmwasm_std::{entry_point, CosmosMsg, QuerierWrapper}; +use cosmwasm_std::{ + to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, SubMsg, WasmMsg, +}; +use cw2::set_contract_version; + +use cw_common::xcall_manager_msg::{ + ConfigureProtocols, Execute, ExecuteMsg, InstantiateMsg, Migrate, MigrateMsg, ProtocolConfig, + QueryMsg, UpdateAdmin, CONFIGURE_PROTOCOLS, EXECUTE, MIGRATE, UPDATE_ADMIN, +}; +use cw_ibc_rlp_lib::rlp::decode; +use cw_ibc_rlp_lib::rlp::Rlp; + +use crate::error::ContractError; +use crate::state::*; + +// version info for migration info +const CONTRACT_NAME: &str = "crates.io:cw-asset-manager"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION) + .map_err(ContractError::Std)?; + PROPOSER.save(deps.storage, &info.sender)?; + X_CALL + .save(deps.storage, &msg.xcall.to_string()) + .map_err(ContractError::Std)?; + ICON_GOVERNANCE + .save(deps.storage, &msg.icon_governance) + .map_err(ContractError::Std)?; + PROTOCOLS + .save(deps.storage, &msg.protocols) + .map_err(ContractError::Std)?; + Ok(Response::new()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::HandleCallMessage { + from, + data, + protocols, + } => handle_call_message(deps, info, from, data, protocols), + ExecuteMsg::ProposeChange { protocol } => { + let proposer = PROPOSER.load(deps.storage)?; + if info.sender != proposer { + return Err(ContractError::OnlyProposer); + } + + PROPOSED_REMOVAL + .save(deps.storage, &protocol) + .map_err(ContractError::Std)?; + + Ok(Response::new()) + } + ExecuteMsg::RemoveProposal {} => { + let proposer = PROPOSER.load(deps.storage)?; + if info.sender != proposer { + return Err(ContractError::OnlyProposer); + } + + PROPOSED_REMOVAL.remove(deps.storage); + + Ok(Response::new()) + } + ExecuteMsg::ChangeProposer { proposer } => { + let current_proposer = PROPOSER.load(deps.storage)?; + if info.sender != current_proposer { + return Err(ContractError::OnlyProposer); + } + + PROPOSER.save(deps.storage, &proposer)?; + Ok(Response::new()) + } + } +} + +pub fn handle_call_message( + deps: DepsMut, + info: MessageInfo, + from: String, + data: Vec, + protocols: Vec, +) -> Result { + let xcall = X_CALL.load(deps.storage)?; + if info.sender != xcall { + return Err(ContractError::OnlyXCall); + } + + let governance = ICON_GOVERNANCE.load(deps.storage)?; + if from != governance { + return Err(ContractError::OnlyGovernance); + } + + let rlp: Rlp = Rlp::new(&data); + let method: String = rlp.val_at(0).unwrap(); + let res = verify_protocols(&deps, protocols.clone(), method.to_string()); + if res.is_err() { + return Err(res.err().unwrap()); + } + + match method.as_str() { + CONFIGURE_PROTOCOLS => { + let configure_protocols: ConfigureProtocols = decode(&data).unwrap(); + for address in &configure_protocols.sources { + if !is_contract(deps.querier, &Addr::unchecked(address)) { + return Err(ContractError::InvalidProtocol); + } + } + + let cfg = ProtocolConfig { + sources: configure_protocols.sources, + destinations: configure_protocols.destinations, + }; + + PROTOCOLS + .save(deps.storage, &cfg) + .map_err(ContractError::Std)?; + PROPOSED_REMOVAL.remove(deps.storage); + Ok(Response::new()) + } + EXECUTE => { + let execute: Execute = decode(&data).unwrap(); + let wasm_execute_message: CosmosMsg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: execute.contract_addr, + msg: Binary::from_base64(&execute.message)?, + funds: vec![], + }); + + let sub_message = SubMsg::new(wasm_execute_message); + Ok(Response::new().add_submessage(sub_message)) + } + MIGRATE => { + let migrate: Migrate = decode(&data).unwrap(); + let wasm_execute_message: CosmosMsg = CosmosMsg::Wasm(WasmMsg::Migrate { + contract_addr: migrate.contract_addr, + new_code_id: migrate.code_id, + msg: Binary::from_base64(&migrate.message)?, + }); + + let sub_message = SubMsg::new(wasm_execute_message); + Ok(Response::new().add_submessage(sub_message)) + } + UPDATE_ADMIN => { + let update_admin: UpdateAdmin = decode(&data).unwrap(); + let wasm_execute_message: CosmosMsg = CosmosMsg::Wasm(WasmMsg::UpdateAdmin { + contract_addr: update_admin.contract_addr, + admin: update_admin.admin, + }); + + let sub_message = SubMsg::new(wasm_execute_message); + Ok(Response::new().add_submessage(sub_message)) + } + _ => Err(ContractError::InvalidMethod), + } +} + +fn verify_protocols( + deps: &DepsMut, + protocols: Vec, + method: String, +) -> Result<(), ContractError> { + let allowed_protocols = PROTOCOLS.load(deps.storage); + println!("{:?}", allowed_protocols); + if allowed_protocols.is_err() { + return Err(ContractError::NetworkNotConfigured); + } + + let allowed_protocols = allowed_protocols?.sources; + if array_eq(&allowed_protocols, &protocols) { + return Ok(()); + } + + if method != CONFIGURE_PROTOCOLS { + return Err(ContractError::InvalidProtocol); + } + + let proposed_protocol_to_remove = PROPOSED_REMOVAL.load(deps.storage)?; + let protocols = protocols.clone(); + let joined_protocols = { + let mut tmp = protocols.clone(); + tmp.push(proposed_protocol_to_remove); + tmp + }; + + if !array_eq(&joined_protocols, &allowed_protocols) { + return Err(ContractError::InvalidProtocol); + } + + Ok(()) +} + +fn array_eq(a: &[T], b: &[T]) -> bool +where + T: Eq + Hash, +{ + let a: HashSet<_> = a.iter().collect(); + let b: HashSet<_> = b.iter().collect(); + + a == b +} + +pub fn is_contract(querier: QuerierWrapper, address: &Addr) -> bool { + querier.query_wasm_contract_info(address).is_ok() +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION) + .map_err(ContractError::Std)?; + + Ok(Response::default().add_attribute("migrate", "successful")) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::VerifyProtocols { protocols } => { + let allowed_protocols = PROTOCOLS.load(deps.storage); + if allowed_protocols.is_err() { + return to_binary(&false); + } + + to_binary(&(allowed_protocols?.sources == protocols)) + } + + QueryMsg::GetProtocols {} => to_binary(&PROTOCOLS.load(deps.storage)?), + } +} diff --git a/contracts/core-contracts/cw-xcall-manager/src/error.rs b/contracts/core-contracts/cw-xcall-manager/src/error.rs new file mode 100644 index 0000000..5d34848 --- /dev/null +++ b/contracts/core-contracts/cw-xcall-manager/src/error.rs @@ -0,0 +1,37 @@ +use cosmwasm_std::StdError; +use cw_ibc_rlp_lib::rlp::DecoderError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + //If a StdError is encountered and returned, it will be automatically converted into a ContractError using the #[from] attribute + #[error("{0}")] + Std(#[from] StdError), + + #[error("Only proposer is allowed")] + OnlyProposer, + + #[error("Only governance is allowed")] + OnlyGovernance, + + #[error("Only xcall is allowed")] + OnlyXCall, + + #[error("Invalid Method")] + InvalidMethod, + + #[error("Network has not been configured")] + NetworkNotConfigured, + + #[error("Invalid protocols")] + InvalidProtocol, + + #[error("Rlp Error: {error}")] + DecoderError { error: DecoderError }, +} + +impl From for ContractError { + fn from(err: DecoderError) -> Self { + ContractError::DecoderError { error: err } + } +} diff --git a/contracts/core-contracts/cw-xcall-manager/src/lib.rs b/contracts/core-contracts/cw-xcall-manager/src/lib.rs new file mode 100644 index 0000000..3c89f23 --- /dev/null +++ b/contracts/core-contracts/cw-xcall-manager/src/lib.rs @@ -0,0 +1,4 @@ +pub mod contract; +mod error; +pub mod state; +pub use crate::error::ContractError; diff --git a/contracts/core-contracts/cw-xcall-manager/src/state.rs b/contracts/core-contracts/cw-xcall-manager/src/state.rs new file mode 100644 index 0000000..c96fbd8 --- /dev/null +++ b/contracts/core-contracts/cw-xcall-manager/src/state.rs @@ -0,0 +1,9 @@ +use cosmwasm_std::Addr; +use cw_common::xcall_manager_msg::ProtocolConfig; +use cw_storage_plus::Item; + +pub const X_CALL: Item = Item::new("xcall_address"); +pub const ICON_GOVERNANCE: Item = Item::new("icon_governance_network_address"); +pub const PROPOSER: Item = Item::new("admin_wallet"); +pub const PROTOCOLS: Item = Item::new("protocols"); +pub const PROPOSED_REMOVAL: Item = Item::new("proposed_removal"); diff --git a/contracts/core-contracts/cw-xcall-manager/tests/test.rs b/contracts/core-contracts/cw-xcall-manager/tests/test.rs new file mode 100644 index 0000000..956acb4 --- /dev/null +++ b/contracts/core-contracts/cw-xcall-manager/tests/test.rs @@ -0,0 +1,431 @@ +use cosmwasm_std::testing::{ + mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, +}; +use cosmwasm_std::{ + from_binary, to_binary, Addr, Binary, ContractInfoResponse, ContractResult, CosmosMsg, Deps, + Empty, OwnedDeps, SystemResult, WasmMsg, WasmQuery, +}; +use cw_common::asset_manager_msg::{ + ExecuteMsg as AssetManagerExecuteMessage, MigrateMsg as AssetManageMigrateMsg, +}; +use cw_common::xcall_manager_msg::{ + ConfigureProtocols, Execute, ExecuteMsg, InstantiateMsg, Migrate, ProtocolConfig, QueryMsg, + UpdateAdmin, +}; +use cw_ibc_rlp_lib::rlp::encode; +use cw_xcall_manager::contract::{execute, instantiate, query}; +use cw_xcall_manager::ContractError; + +pub const XCALL_NETWORK_ADDRESS: &str = "archway/xcall"; +pub const XCALL_ADDR: &str = "xcall"; +pub const GOVERNANCE: &str = "icon/governance"; +pub const PROPOSER: &str = "proposer"; + +fn setup( + mut deps: OwnedDeps, + protocols: Vec, +) -> OwnedDeps { + deps.querier.update_wasm(|r: &WasmQuery| match r { + WasmQuery::Smart { + contract_addr: _, + msg: _, + } => SystemResult::Ok(ContractResult::Ok( + to_binary(&XCALL_NETWORK_ADDRESS).unwrap(), + )), + WasmQuery::ContractInfo { contract_addr: _ } => SystemResult::Ok(ContractResult::Ok( + to_binary(&ContractInfoResponse::default()).unwrap(), + )), + _ => todo!(), + }); + + let msg = InstantiateMsg { + xcall: Addr::unchecked("xcall"), + icon_governance: GOVERNANCE.to_string(), + protocols: ProtocolConfig { + sources: protocols.clone(), + destinations: protocols.clone(), + }, + }; + + let info = mock_info(PROPOSER, &[]); + // we can just call .unwrap() to assert this was a success + let res = instantiate(deps.as_mut(), mock_env(), info, msg); + assert!(res.is_ok()); + deps +} + +#[test] +fn verify_protocol_empty() { + // Arrange + let mut deps = mock_dependencies(); + deps = setup(deps, vec![]); + + // Act + verify_protocol(deps.as_ref(), vec![], true); +} + +#[test] +fn verify_protocol_base() { + // Arrange + let mut deps = mock_dependencies(); + let protocols = vec!["Protocol1".to_string(), "Protocol2".to_string()]; + deps = setup(deps, protocols.clone()); + + // Act & Assert + verify_protocol(deps.as_ref(), protocols, true); +} + +#[test] +fn verify_protocol_invalid() { + // Arrange + let mut deps = mock_dependencies(); + let protocols = vec!["Protocol1".to_string(), "Protocol2".to_string()]; + let invalid_protocols = vec!["Protocol3".to_string(), "Protocol2".to_string()]; + deps = setup(deps, protocols.clone()); + + // Act + verify_protocol(deps.as_ref(), invalid_protocols, false); +} + +#[test] +fn verify_protocol_invalid_empty() { + // Arrange + let mut deps = mock_dependencies(); + let protocols = vec!["Protocol1".to_string()]; + deps = setup(deps, vec![]); + + // Act & Assert + verify_protocol(deps.as_ref(), protocols, false); +} + +#[test] +fn verify_only_xcall() { + // Arrange + let mut deps = mock_dependencies(); + deps = setup(deps, vec![]); + + let msg = ExecuteMsg::HandleCallMessage { + from: GOVERNANCE.to_string(), + data: vec![], + protocols: vec![], + }; + + // Act + let res = execute(deps.as_mut(), mock_env(), mock_info("not_xcall", &[]), msg); + + // Assert + assert!(res.is_err_and(|e| e == ContractError::OnlyXCall)); +} + +#[test] +fn verify_only_governance() { + // Arrange + let mut deps = mock_dependencies(); + deps = setup(deps, vec![]); + + let non_governance = "icon/fake_governance".to_string(); + let msg = ExecuteMsg::HandleCallMessage { + from: non_governance, + data: vec![], + protocols: vec![], + }; + + // Act + let res = execute(deps.as_mut(), mock_env(), mock_info(XCALL_ADDR, &[]), msg); + + // Assert + assert!(res.is_err_and(|e| e == ContractError::OnlyGovernance)); +} + +#[test] +fn configure_protocol() { + // Arrange + let mut deps = mock_dependencies(); + let protocols = vec!["Protocol1".to_string()]; + let new_sources = vec!["Protocol2".to_string()]; + let new_destination = vec!["icon/dst".to_string()]; + deps = setup(deps, protocols.clone()); + + let xcall_message = ConfigureProtocols { + sources: new_sources.clone(), + destinations: new_destination.clone(), + }; + let encoded_message = encode(&xcall_message).to_vec(); + let msg = ExecuteMsg::HandleCallMessage { + from: GOVERNANCE.to_string(), + data: encoded_message, + protocols: protocols.clone(), + }; + + // Act + let res = execute(deps.as_mut(), mock_env(), mock_info(XCALL_ADDR, &[]), msg); + + // Assert + assert!(res.is_ok()); + + verify_protocol(deps.as_ref(), protocols, false); + verify_protocol(deps.as_ref(), new_sources.clone(), true); + let res = query(deps.as_ref(), mock_env(), QueryMsg::GetProtocols {}); + let current_protocols: ProtocolConfig = from_binary(&res.unwrap()).unwrap(); + assert!(current_protocols.sources == new_sources); + assert!(current_protocols.destinations == new_destination); +} + +#[test] +fn execute_message() { + // Arrange + let mut deps = mock_dependencies(); + let protocols = vec!["Protocol1".to_string()]; + deps = setup(deps, protocols.clone()); + + let to = "contract".to_string(); + let execute_message = AssetManagerExecuteMessage::ConfigureNative { + native_token_address: "a".to_string(), + native_token_manager: "b".to_string(), + }; + let xcall_message = Execute { + contract_addr: to.clone(), + message: Binary::to_base64(&to_binary(&execute_message).unwrap()), + }; + let expected_message = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: to, + msg: to_binary(&execute_message).unwrap(), + funds: vec![], + }); + let encoded_message = encode(&xcall_message).to_vec(); + let msg = ExecuteMsg::HandleCallMessage { + from: GOVERNANCE.to_string(), + data: encoded_message, + protocols: protocols.clone(), + }; + + // Act + let res = execute(deps.as_mut(), mock_env(), mock_info(XCALL_ADDR, &[]), msg); + + // Assert + assert!(res.is_ok_and(|res| { res.messages[0].msg.eq(&expected_message) })); +} + +#[test] +fn migrate_message() { + // Arrange + let mut deps = mock_dependencies(); + let protocols = vec!["Protocol1".to_string()]; + deps = setup(deps, protocols.clone()); + + let to = "contract".to_string(); + let code_id = 2; + let migrate_message = AssetManageMigrateMsg {}; + let xcall_message = Migrate { + contract_addr: to.clone(), + code_id, + message: Binary::to_base64(&to_binary(&migrate_message).unwrap()), + }; + + let expected_message = CosmosMsg::Wasm(WasmMsg::Migrate { + contract_addr: to, + new_code_id: code_id, + msg: to_binary(&migrate_message).unwrap(), + }); + + let encoded_message = encode(&xcall_message).to_vec(); + let msg = ExecuteMsg::HandleCallMessage { + from: GOVERNANCE.to_string(), + data: encoded_message, + protocols: protocols.clone(), + }; + + // Act + let res = execute(deps.as_mut(), mock_env(), mock_info(XCALL_ADDR, &[]), msg); + + // Assert + assert!(res.is_ok_and(|res| { res.messages[0].msg.eq(&expected_message) })); +} + +#[test] +fn update_admin() { + // Arrange + let mut deps = mock_dependencies(); + let protocols = vec!["Protocol1".to_string()]; + deps = setup(deps, protocols.clone()); + + let to = "contract".to_string(); + let admin = "admin".to_string(); + let xcall_message = UpdateAdmin { + contract_addr: to.clone(), + admin: admin.clone(), + }; + + let expected_message = CosmosMsg::Wasm(WasmMsg::UpdateAdmin { + contract_addr: to, + admin, + }); + + let encoded_message = encode(&xcall_message).to_vec(); + let msg = ExecuteMsg::HandleCallMessage { + from: GOVERNANCE.to_string(), + data: encoded_message, + protocols: protocols.clone(), + }; + + // Act + let res = execute(deps.as_mut(), mock_env(), mock_info(XCALL_ADDR, &[]), msg); + + // Assert + assert!(res.is_ok_and(|res| { res.messages[0].msg.eq(&expected_message) })); +} + +#[test] +fn only_proposer() { + // Arrange + let mut deps = mock_dependencies(); + deps = setup(deps, vec![]); + + // Act & Assert + let msg = ExecuteMsg::ProposeChange { + protocol: "a".to_string(), + }; + let res = execute( + deps.as_mut(), + mock_env(), + mock_info("not_proposer", &[]), + msg, + ); + assert!(res.is_err_and(|e| e == ContractError::OnlyProposer)); + + // Act & Assert + let msg = ExecuteMsg::RemoveProposal {}; + let res = execute( + deps.as_mut(), + mock_env(), + mock_info("not_proposer", &[]), + msg, + ); + assert!(res.is_err_and(|e| e == ContractError::OnlyProposer)); + + // Act & Assert + let msg = ExecuteMsg::ChangeProposer { + proposer: Addr::unchecked("a".to_string()), + }; + let res = execute( + deps.as_mut(), + mock_env(), + mock_info("not_proposer", &[]), + msg, + ); + assert!(res.is_err_and(|e| e == ContractError::OnlyProposer)); +} + +#[test] +fn propose_and_enact_removal() { + // Arrange + let mut deps = mock_dependencies(); + let protocols = vec!["Protocol1".to_string(), "Protocol2".to_string()]; + let new_protocols = vec!["Protocol2".to_string()]; + deps = setup(deps, protocols.clone()); + + let msg = ExecuteMsg::ProposeChange { + protocol: protocols[0].clone(), + }; + let res = execute(deps.as_mut(), mock_env(), mock_info(PROPOSER, &[]), msg); + assert!(res.is_ok()); + + let xcall_message = ConfigureProtocols { + sources: new_protocols.clone(), + destinations: new_protocols.clone(), + }; + let encoded_message = encode(&xcall_message).to_vec(); + let msg = ExecuteMsg::HandleCallMessage { + from: GOVERNANCE.to_string(), + data: encoded_message, + protocols: new_protocols.clone(), + }; + + // Act + let res = execute(deps.as_mut(), mock_env(), mock_info(XCALL_ADDR, &[]), msg); + + // Assert + assert!(res.is_ok()); + + verify_protocol(deps.as_ref(), protocols, false); + verify_protocol(deps.as_ref(), new_protocols, true) +} + +#[test] +fn invalid_proposal() { + // Arrange + let mut deps = mock_dependencies(); + let protocols = vec!["Protocol1".to_string(), "Protocol2".to_string()]; + let new_protocols = vec!["Protocol2".to_string()]; + deps = setup(deps, protocols.clone()); + + let msg = ExecuteMsg::ProposeChange { + protocol: "Protocol3".to_string(), + }; + let res = execute(deps.as_mut(), mock_env(), mock_info(PROPOSER, &[]), msg); + assert!(res.is_ok()); + + let xcall_message = ConfigureProtocols { + sources: new_protocols.clone(), + destinations: new_protocols.clone(), + }; + let encoded_message = encode(&xcall_message).to_vec(); + let msg = ExecuteMsg::HandleCallMessage { + from: GOVERNANCE.to_string(), + data: encoded_message, + protocols: new_protocols.clone(), + }; + + // Act + let res = execute(deps.as_mut(), mock_env(), mock_info(XCALL_ADDR, &[]), msg); + + // Assert + assert!(res.is_err_and(|e| e == ContractError::InvalidProtocol)); +} + +#[test] +fn change_proposer() { + // Arrange + let mut deps = mock_dependencies(); + deps = setup(deps, vec![]); + let new_proposer = Addr::unchecked("new proposer".to_string()); + + let msg = ExecuteMsg::ChangeProposer { + proposer: new_proposer.clone(), + }; + + // Act + let res = execute(deps.as_mut(), mock_env(), mock_info(PROPOSER, &[]), msg); + + // Assert + assert!(res.is_ok()); + let msg = ExecuteMsg::ProposeChange { + protocol: "Protocol1".to_string(), + }; + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(new_proposer.as_ref(), &[]), + msg, + ); + assert!(res.is_ok()); +} + +fn verify_protocol(deps: Deps<'_, Empty>, protocols: Vec, valid: bool) { + let res = query( + deps, + mock_env(), + QueryMsg::VerifyProtocols { + protocols: protocols.clone(), + }, + ); + assert!(res.is_ok()); + assert!(res.unwrap() == to_binary(&valid).unwrap()); + if !valid { + return; + } + + let res = query(deps, mock_env(), QueryMsg::GetProtocols {}); + let current_protocols: ProtocolConfig = from_binary(&res.unwrap()).unwrap(); + assert!(current_protocols.sources == protocols); +} diff --git a/contracts/cw-common/src/asset_manager_msg.rs b/contracts/cw-common/src/asset_manager_msg.rs index 8468d07..d0628b7 100644 --- a/contracts/cw-common/src/asset_manager_msg.rs +++ b/contracts/cw-common/src/asset_manager_msg.rs @@ -5,6 +5,7 @@ use cosmwasm_std::{Addr, Uint128}; pub struct InstantiateMsg { pub source_xcall: String, pub destination_asset_manager: String, + pub manager: Addr, } #[cw_serde] @@ -23,6 +24,7 @@ pub enum ExecuteMsg { ConfigureXcall { source_xcall: String, destination_asset_manager: String, + manager: Addr, }, ConfigureNative { @@ -33,6 +35,7 @@ pub enum ExecuteMsg { HandleCallMessage { from: String, data: Vec, + protocols: Vec, }, } diff --git a/contracts/cw-common/src/helpers.rs b/contracts/cw-common/src/helpers.rs new file mode 100644 index 0000000..57e68a5 --- /dev/null +++ b/contracts/cw-common/src/helpers.rs @@ -0,0 +1,49 @@ +use crate::xcall_manager_msg::{ + ProtocolConfig, + QueryMsg::{GetProtocols, VerifyProtocols}, +}; +use cosmwasm_std::{to_binary, Addr, DepsMut, QueryRequest, WasmQuery}; +use cw_xcall_lib::network_address::NetworkAddress; +use cw_xcall_multi::{error::ContractError, msg::QueryMsg::GetNetworkAddress}; + +pub fn verify_protocol( + deps: &DepsMut, + xcall_manager: Addr, + protocols: Vec, +) -> Result<(), ContractError> { + let query_msg = VerifyProtocols { protocols }; + let query = QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: xcall_manager.to_string(), + msg: to_binary(&query_msg).map_err(ContractError::Std)?, + }); + + let res: bool = deps.querier.query(&query).map_err(ContractError::Std)?; + if res { + return Ok(()); + } + + Err(ContractError::Unauthorized {}) +} + +pub fn get_protocols(deps: &DepsMut, xcall_manager: Addr) -> Result { + let query_msg = GetProtocols {}; + let query = QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: xcall_manager.to_string(), + msg: to_binary(&query_msg).map_err(ContractError::Std)?, + }); + + deps.querier.query(&query).map_err(ContractError::Std) +} + +pub fn query_network_address( + deps: &DepsMut, + x_call_addr: &Addr, +) -> Result { + let query_msg = GetNetworkAddress {}; + let query = QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: x_call_addr.to_string(), + msg: to_binary(&query_msg).map_err(ContractError::Std)?, + }); + + deps.querier.query(&query).map_err(ContractError::Std) +} diff --git a/contracts/cw-common/src/hub_token_msg.rs b/contracts/cw-common/src/hub_token_msg.rs index b31edf3..b16ea58 100644 --- a/contracts/cw-common/src/hub_token_msg.rs +++ b/contracts/cw-common/src/hub_token_msg.rs @@ -9,6 +9,7 @@ pub use cw20_base::msg::{ExecuteMsg as Cw20ExecuteMsg, QueryMsg}; pub struct InstantiateMsg { pub x_call: String, pub hub_address: String, + pub manager: Addr, } #[cw_serde] @@ -16,10 +17,12 @@ pub enum ExecuteMsg { Setup { x_call: Addr, hub_address: NetworkAddress, + manager: Addr, }, HandleCallMessage { from: NetworkAddress, data: Vec, + protocols: Vec, }, CrossTransfer { to: NetworkAddress, @@ -27,14 +30,9 @@ pub enum ExecuteMsg { data: Vec, }, /// Transfer is a base message to move tokens to another account without triggering actions - Transfer { - recipient: String, - amount: Uint128, - }, + Transfer { recipient: String, amount: Uint128 }, /// Burn is a base message to destroy tokens forever - Burn { - amount: Uint128, - }, + Burn { amount: Uint128 }, /// Only with "approval" extension. Allows spender to access an additional amount tokens /// from the owner's (env.sender) account. If expires is Some(), overwrites current allowance /// expiration with this one. @@ -59,22 +57,14 @@ pub enum ExecuteMsg { amount: Uint128, }, /// Only with "approval" extension. Destroys tokens forever - BurnFrom { - owner: String, - amount: Uint128, - }, + BurnFrom { owner: String, amount: Uint128 }, /// Only with the "mintable" extension. If authorized, creates amount new tokens /// and adds to the recipient balance. - Mint { - recipient: String, - amount: Uint128, - }, + Mint { recipient: String, amount: Uint128 }, /// Only with the "mintable" extension. The current minter may set /// a new minter. Setting the minter to None will remove the /// token's minter forever. - UpdateMinter { - new_minter: Option, - }, + UpdateMinter { new_minter: Option }, } #[cw_serde] diff --git a/contracts/cw-common/src/lib.rs b/contracts/cw-common/src/lib.rs index 674ba2f..ca20f1a 100644 --- a/contracts/cw-common/src/lib.rs +++ b/contracts/cw-common/src/lib.rs @@ -6,3 +6,6 @@ pub mod x_call_msg; pub mod asset_manager_msg; pub mod xcall_data_types; +pub mod xcall_manager_msg; + +pub mod helpers; diff --git a/contracts/cw-common/src/xcall_manager_msg.rs b/contracts/cw-common/src/xcall_manager_msg.rs new file mode 100644 index 0000000..3007371 --- /dev/null +++ b/contracts/cw-common/src/xcall_manager_msg.rs @@ -0,0 +1,159 @@ +use std::vec; + +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Addr; +use cw_ibc_rlp_lib::rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; + +#[cw_serde] +pub struct InstantiateMsg { + pub xcall: Addr, + pub icon_governance: String, + pub protocols: ProtocolConfig, +} + +#[cw_serde] +pub enum ExecuteMsg { + ProposeChange { + protocol: String, + }, + RemoveProposal {}, + ChangeProposer { + proposer: Addr, + }, + HandleCallMessage { + from: String, + data: Vec, + protocols: Vec, + }, +} + +#[cw_serde] +pub struct ProtocolConfig { + pub sources: Vec, + pub destinations: Vec, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(bool)] + VerifyProtocols { protocols: Vec }, + #[returns(ProtocolConfig)] + GetProtocols {}, +} + +#[cw_serde] +pub struct MigrateMsg {} + +pub const CONFIGURE_PROTOCOLS: &str = "ConfigureProtocols"; +#[cw_serde] +pub struct ConfigureProtocols { + pub sources: Vec, + pub destinations: Vec, +} + +impl Encodable for ConfigureProtocols { + fn rlp_append(&self, stream: &mut RlpStream) { + stream.begin_list(3); + stream.append(&CONFIGURE_PROTOCOLS.to_string()); + stream.begin_list(self.sources.len()); + for protocol in self.sources.iter() { + stream.append(protocol); + } + + stream.begin_list(self.destinations.len()); + for protocol in self.destinations.iter() { + stream.append(protocol); + } + } +} + +impl Decodable for ConfigureProtocols { + fn decode(rlp: &Rlp) -> Result { + let rlp_sources = rlp.at(1)?; + let rlp_destinations = rlp.at(2)?; + Ok(Self { + sources: rlp_sources.as_list()?, + destinations: rlp_destinations.as_list()?, + }) + } +} + +pub const EXECUTE: &str = "Execute"; +#[cw_serde] +pub struct Execute { + pub contract_addr: String, + pub message: String, // Base64 encoded binary +} + +impl Encodable for Execute { + fn rlp_append(&self, stream: &mut RlpStream) { + stream.begin_list(3); + stream.append(&EXECUTE.to_string()); + stream.append(&self.contract_addr.to_string()); + stream.append(&self.message.to_string()); + } +} + +impl Decodable for Execute { + fn decode(rlp: &Rlp) -> Result { + Ok(Self { + contract_addr: rlp.val_at(1)?, + message: rlp.val_at(2)?, + }) + } +} + +pub const MIGRATE: &str = "Migrate"; + +#[cw_serde] +pub struct Migrate { + pub contract_addr: String, + pub code_id: u64, + pub message: String, +} + +impl Encodable for Migrate { + fn rlp_append(&self, stream: &mut RlpStream) { + stream.begin_list(3); + stream.append(&MIGRATE.to_string()); + stream.append(&self.contract_addr.to_string()); + stream.append(&self.code_id); + stream.append(&self.message.to_string()); + } +} + +impl Decodable for Migrate { + fn decode(rlp: &Rlp) -> Result { + Ok(Self { + contract_addr: rlp.val_at(1)?, + code_id: rlp.val_at(2)?, + message: rlp.val_at(3)?, + }) + } +} + +pub const UPDATE_ADMIN: &str = "UpdateAdmin"; +#[cw_serde] +pub struct UpdateAdmin { + pub contract_addr: String, + pub admin: String, +} + +impl Encodable for UpdateAdmin { + fn rlp_append(&self, stream: &mut RlpStream) { + stream.begin_list(3); + stream.append(&UPDATE_ADMIN.to_string()); + stream.append(&self.contract_addr.to_string()); + stream.append(&self.admin); + } +} + +impl Decodable for UpdateAdmin { + fn decode(rlp: &Rlp) -> Result { + Ok(Self { + contract_addr: rlp.val_at(1)?, + admin: rlp.val_at(2)?, + }) + } +} diff --git a/contracts/token-contracts/cw-hub-bnusd/Cargo.toml b/contracts/token-contracts/cw-hub-bnusd/Cargo.toml index 3dcf970..4c15f4b 100644 --- a/contracts/token-contracts/cw-hub-bnusd/Cargo.toml +++ b/contracts/token-contracts/cw-hub-bnusd/Cargo.toml @@ -58,7 +58,7 @@ cw_xcall_ibc_connection = { git = "https://github.com/icon-project/IBC-Integrat cw_mock_ibc_core = { git = "https://github.com/icon-project/IBC-Integration.git", branch="main", package="cw-mock-ibc-core" } anyhow = { version = "1.0", default-features = false } getrandom = { version = "0.2", features = ["custom"] } - +cw-xcall-manager = { path = "../../core-contracts/cw-xcall-manager" } [profile.release] # Do not perform backtrace for panic on release builds. diff --git a/contracts/token-contracts/cw-hub-bnusd/src/contract.rs b/contracts/token-contracts/cw-hub-bnusd/src/contract.rs index c1699c4..3dc3642 100644 --- a/contracts/token-contracts/cw-hub-bnusd/src/contract.rs +++ b/contracts/token-contracts/cw-hub-bnusd/src/contract.rs @@ -6,8 +6,10 @@ use crate::constants::{ }; use crate::error::ContractError; use crate::state::{ - DESTINATION_TOKEN_ADDRESS, DESTINATION_TOKEN_NET, NID, OWNER, X_CALL, X_CALL_NETWORK_ADDRESS, + DESTINATION_TOKEN_ADDRESS, DESTINATION_TOKEN_NET, NID, OWNER, X_CALL, X_CALL_MANAGER, + X_CALL_NETWORK_ADDRESS, }; +use cw_common::helpers::verify_protocol; use cw_common::network_address::IconAddressValidation; #[cfg(not(feature = "library"))] @@ -72,7 +74,14 @@ pub fn instantiate( cap: None, }), }; - setup_function(deps, env, x_call_addr, hub_network_address, token_info) + setup_function( + deps, + env, + x_call_addr, + hub_network_address, + token_info, + msg.manager, + ) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -86,8 +95,20 @@ pub fn execute( ExecuteMsg::Setup { x_call, hub_address, - } => execute::setup(deps, env, info, x_call, hub_address), - ExecuteMsg::HandleCallMessage { from, data } => { + manager, + } => execute::setup(deps, env, info, x_call, hub_address, manager), + ExecuteMsg::HandleCallMessage { + from, + data, + protocols, + } => { + let xcall_manger = X_CALL_MANAGER.load(deps.storage)?; + let res = verify_protocol(&deps, xcall_manger, protocols); + + if res.is_err() { + return Err(ContractError::Unauthorized); + } + execute::handle_call_message(deps, env, info, from, data) } ExecuteMsg::CrossTransfer { to, amount, data } => { @@ -175,7 +196,7 @@ mod execute { use bytes::BytesMut; use cosmwasm_std::{ensure, to_binary, Addr, CosmosMsg, SubMsg}; - use cw_common::network_address::NetId; + use cw_common::{helpers::get_protocols, network_address::NetId}; use cw_ibc_rlp_lib::rlp::{decode, encode}; use debug_print::debug_println; @@ -189,6 +210,7 @@ mod execute { info: MessageInfo, x_call: Addr, hub_network_address: NetworkAddress, + xcall_manager: Addr, ) -> Result { let owner = OWNER.load(deps.storage)?; if owner != info.sender { @@ -202,7 +224,14 @@ mod execute { deps.api .addr_validate(x_call.as_str()) .map_err(ContractError::Std)?; - setup_function(deps, env, x_call, hub_network_address, token_info) + setup_function( + deps, + env, + x_call, + hub_network_address, + token_info, + xcall_manager, + ) } pub fn handle_call_message( @@ -280,13 +309,13 @@ mod execute { }; let hub_token_address = NetworkAddress::new(&hub_net.to_string(), hub_address.as_ref()); - + let cfg = get_protocols(&deps, X_CALL_MANAGER.load(deps.storage)?).unwrap(); let call_message = XCallMsg::SendCallMessage { to: hub_token_address, data: encode(&call_data).to_vec(), rollback: Some(encode(&rollback_data).to_vec()), - sources: None, - destinations: None, + sources: Some(cfg.sources), + destinations: Some(cfg.destinations), }; let wasm_execute_message: CosmosMsg = CosmosMsg::Wasm(cosmwasm_std::WasmMsg::Execute { @@ -406,6 +435,7 @@ fn setup_function( x_call: Addr, hub_network_address: NetworkAddress, token_info: TokenInfo, + xcall_manager: Addr, ) -> Result { TOKEN_INFO .save(deps.storage, &token_info) @@ -440,6 +470,7 @@ fn setup_function( NID.save(deps.storage, &nid)?; DESTINATION_TOKEN_ADDRESS.save(deps.storage, &hub_address)?; DESTINATION_TOKEN_NET.save(deps.storage, &hub_net)?; + X_CALL_MANAGER.save(deps.storage, &xcall_manager)?; Ok(Response::default()) } @@ -489,6 +520,7 @@ mod tests { to_binary, Addr, ContractResult, MemoryStorage, OwnedDeps, SystemResult, Uint128, WasmQuery, }; + use cw_common::xcall_manager_msg::{self, ProtocolConfig}; use cw_ibc_rlp_lib::rlp::encode; use debug_print::debug_println; @@ -507,15 +539,29 @@ mod tests { let msg = InstantiateMsg { x_call: "archway123fdth".to_owned(), hub_address: "0x01.icon/cx9876543210fedcba9876543210fedcba98765432".to_owned(), + manager: Addr::unchecked("manager".to_string()), }; deps.querier.update_wasm(|r| match r { - WasmQuery::Smart { - contract_addr: _, - msg: _, - } => SystemResult::Ok(ContractResult::Ok( - to_binary("0x01.icon/cx9876543210fedcba9876543210fedcba98765432").unwrap(), - )), + WasmQuery::Smart { contract_addr, msg } => { + if contract_addr == &"manager".to_owned() { + if msg == &to_binary(&xcall_manager_msg::QueryMsg::GetProtocols {}).unwrap() { + return SystemResult::Ok(ContractResult::Ok( + to_binary(&ProtocolConfig { + sources: vec![], + destinations: vec![], + }) + .unwrap(), + )); + } + + SystemResult::Ok(ContractResult::Ok(to_binary(&true).unwrap())) + } else { + SystemResult::Ok(ContractResult::Ok( + to_binary("0x01.icon/cx9876543210fedcba9876543210fedcba98765432").unwrap(), + )) + } + } _ => todo!(), }); @@ -558,6 +604,7 @@ mod tests { ) .unwrap(), data, + protocols: vec![], }, ); assert!(res.is_ok()); @@ -590,6 +637,7 @@ mod tests { ) .unwrap(), data, + protocols: vec![], }, ) .unwrap(); @@ -651,6 +699,7 @@ mod tests { "0x01.icon/cx9876543210fedcba9876543210fedcba98765432", ) .unwrap(), + manager: Addr::unchecked("manager".to_string()), }; deps.querier.update_wasm(|r| match r { @@ -716,6 +765,7 @@ mod tests { ) .unwrap(), data, + protocols: vec![], }, ); assert!(res.is_ok()); diff --git a/contracts/token-contracts/cw-hub-bnusd/src/state.rs b/contracts/token-contracts/cw-hub-bnusd/src/state.rs index 8058ee8..bbf08b5 100644 --- a/contracts/token-contracts/cw-hub-bnusd/src/state.rs +++ b/contracts/token-contracts/cw-hub-bnusd/src/state.rs @@ -4,6 +4,7 @@ use cw_storage_plus::Item; pub const OWNER: Item = Item::new("owner"); pub const X_CALL: Item = Item::new("xCall"); +pub const X_CALL_MANAGER: Item = Item::new("xcall_manager"); pub const X_CALL_NETWORK_ADDRESS: Item = Item::new("xCallBTPAddress"); pub const NID: Item = Item::new("nid"); pub const DESTINATION_TOKEN_ADDRESS: Item = Item::new("hubAddress"); diff --git a/contracts/token-contracts/cw-hub-bnusd/tests/cross_transfer_test.rs b/contracts/token-contracts/cw-hub-bnusd/tests/cross_transfer_test.rs index 8eb5e0f..c138f86 100644 --- a/contracts/token-contracts/cw-hub-bnusd/tests/cross_transfer_test.rs +++ b/contracts/token-contracts/cw-hub-bnusd/tests/cross_transfer_test.rs @@ -7,11 +7,9 @@ use cw_common::network_address::NetworkAddress; use cw_multi_test::Executor; use crate::setup::{call_set_xcall_host, execute_setup, instantiate_contracts}; -use setup::{mint_token, set_default_connection, setup_context, TestContext}; +use setup::{mint_token, setup_context, TestContext}; pub fn cross_transfer(mut ctx: TestContext) -> TestContext { - let x_call_connection = ctx.get_xcall_connection(); - ctx = set_default_connection(ctx, x_call_connection); call_set_xcall_host(&mut ctx); let _resp = ctx .app diff --git a/contracts/token-contracts/cw-hub-bnusd/tests/handle_call_message_test.rs b/contracts/token-contracts/cw-hub-bnusd/tests/handle_call_message_test.rs index 9d7873f..049cdd1 100644 --- a/contracts/token-contracts/cw-hub-bnusd/tests/handle_call_message_test.rs +++ b/contracts/token-contracts/cw-hub-bnusd/tests/handle_call_message_test.rs @@ -16,6 +16,8 @@ use setup::{get_event, set_default_connection, setup_context, TestContext}; fn execute_and_handle_message(mut context: TestContext) -> TestContext { let hub_token_addr = context.get_hubtoken_app().into_string(); + let relay = context.get_xcall_connection(); + let call_data = CrossTransfer { method: "xCrossTransfer".to_string(), from: NetworkAddress::from_str("icon/cx7866543210fedcba9876543210fedcba987654df").unwrap(), @@ -40,7 +42,8 @@ fn execute_and_handle_message(mut context: TestContext) -> TestContext { stream.append(&sequence_no); stream.append(&false); stream.append(&data); - stream.begin_list(0); + stream.begin_list(1); + stream.append(&relay.to_string()); let encoded_data: Vec = stream.out().to_vec(); println!("Encoded Data {:?}", encoded_data); @@ -52,9 +55,6 @@ fn execute_and_handle_message(mut context: TestContext) -> TestContext { let data = stream.out().to_vec(); - let relay = Addr::unchecked("relay"); - context = set_default_connection(context, relay.clone()); - let response = context .app .execute_contract( @@ -175,7 +175,7 @@ pub fn cross_transfer_revert_data_test() { let sequence_no = event.get("sn").unwrap(); let balance = balance_of(&context, user.clone()); - let expected_balance = (initial_balance - amount); + let expected_balance = initial_balance - amount; assert_eq!(balance.balance, expected_balance); let message_type: u64 = 2; diff --git a/contracts/token-contracts/cw-hub-bnusd/tests/setup.rs b/contracts/token-contracts/cw-hub-bnusd/tests/setup.rs index 8a00574..faabaab 100644 --- a/contracts/token-contracts/cw-hub-bnusd/tests/setup.rs +++ b/contracts/token-contracts/cw-hub-bnusd/tests/setup.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::str::FromStr; use cw_common::x_call_msg::XCallMsg as XCallExecuteMsg; +use cw_common::xcall_manager_msg::{self, ProtocolConfig}; use cw_hub_bnusd::contract::{execute, instantiate, query}; use cw_multi_test::App; use cw_multi_test::{Contract, ContractWrapper, Executor}; @@ -26,6 +27,7 @@ use cw_common::{ #[derive(Debug, PartialEq, Eq, Hash)] pub enum TestApps { XCall, + XCallManager, HubToken, XcallConnection, IbcCore, @@ -73,6 +75,14 @@ impl TestContext { pub fn get_hubtoken_app(&self) -> Addr { return self.contracts.get(&TestApps::HubToken).unwrap().clone(); } + + pub fn set_xcall_manager(&mut self, addr: Addr) -> Option { + self.contracts.insert(TestApps::XCallManager, addr) + } + + pub fn get_xcall_manager(&self) -> Addr { + return self.contracts.get(&TestApps::XCallManager).unwrap().clone(); + } } pub fn setup_context() -> TestContext { @@ -112,6 +122,14 @@ pub fn x_call_connection_setup() -> Box> { ) } +pub fn xcall_manager_setup() -> Box> { + Box::new(ContractWrapper::new( + cw_xcall_manager::contract::execute, + cw_xcall_manager::contract::instantiate, + cw_xcall_manager::contract::query, + )) +} + use cosmwasm_std::{Attribute, Event, Uint128}; use cw_common::network_address::NetId; @@ -192,6 +210,7 @@ pub fn init_token(mut ctx: TestContext, _x_call_address: String) -> TestContext &InstantiateMsg { x_call: Addr::unchecked(_x_call_address).into_string(), hub_address: "icon/cx9876543210fedcba9876543210fedcba98765432".to_owned(), + manager: Addr::unchecked("manager"), }, &[], "HubToken", @@ -202,6 +221,32 @@ pub fn init_token(mut ctx: TestContext, _x_call_address: String) -> TestContext ctx } +pub fn init_xcall_manager(mut ctx: TestContext) -> TestContext { + let code: Box> = xcall_manager_setup(); + let code_id = ctx.app.store_code(code); + + let addr = ctx + .app + .instantiate_contract( + code_id, + ctx.sender.clone(), + &xcall_manager_msg::InstantiateMsg { + xcall: ctx.get_xcall_app(), + icon_governance: "ICON_GOVERNANCE".to_string(), + protocols: ProtocolConfig { + sources: vec![ctx.get_xcall_connection().to_string()], + destinations: vec![], + }, + }, + &[], + "IbcCore", + None, + ) + .unwrap(); + ctx.set_xcall_manager(addr); + ctx +} + pub fn call_set_xcall_host(ctx: &mut TestContext) -> AppResponse { ctx.app .execute_contract( @@ -227,6 +272,7 @@ pub fn execute_setup(mut ctx: TestContext) -> TestContext { "icon/cx7866543210fedcba9876543210fedcba987654df", ) .unwrap(), + manager: ctx.get_xcall_manager(), }, &[], ) @@ -241,6 +287,7 @@ pub fn instantiate_contracts(mut ctx: TestContext) -> TestContext { ctx = init_token(ctx, x_call_address); ctx = init_mock_ibc_core(ctx); ctx = init_xcall_connection_contract(ctx); + ctx = init_xcall_manager(ctx); ctx }