From 6a879a07aba402b809cb42f1c27626e822880d19 Mon Sep 17 00:00:00 2001 From: Steve Degosserie <723552+stiiifff@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:18:11 +0200 Subject: [PATCH] Neutron IBC Transfer service --- Cargo.lock | 5 + contracts/services/forwarder/src/tests.rs | 4 +- .../services/generic-ibc-transfer/src/msg.rs | 37 +- .../generic-ibc-transfer/src/tests.rs | 126 +++-- .../services/neutron-ibc-transfer/Cargo.toml | 46 +- .../neutron-ibc-transfer/src/bin/schema.rs | 4 +- .../neutron-ibc-transfer/src/contract.rs | 37 +- .../services/neutron-ibc-transfer/src/lib.rs | 8 +- .../services/neutron-ibc-transfer/src/msg.rs | 106 ---- .../neutron-ibc-transfer/src/tests.rs | 503 +++++++++++++----- .../services/reverse-splitter/src/tests.rs | 4 +- contracts/services/splitter/src/tests.rs | 4 +- contracts/services/template/src/tests.rs | 4 +- .../examples/ibc_transfer_juno_ntrn.rs | 1 + .../examples/ibc_transfer_ntrn_juno.rs | 11 +- packages/service-utils/src/testing.rs | 98 +++- 16 files changed, 626 insertions(+), 372 deletions(-) delete mode 100644 contracts/services/neutron-ibc-transfer/src/msg.rs diff --git a/Cargo.lock b/Cargo.lock index bf5b84cd..a2f96ebe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4873,15 +4873,20 @@ version = "0.1.0" dependencies = [ "cosmwasm-schema 2.1.4", "cosmwasm-std 2.1.4", + "cw-multi-test", "cw-ownable", "cw-storage-plus 2.0.0", "cw-utils 2.0.0", + "cw20 2.0.0", + "cw20-base", "getset", "neutron-sdk", "schemars", "serde", + "sha2 0.10.8", "thiserror", "valence-account-utils", + "valence-generic-ibc-transfer-service", "valence-ibc-utils", "valence-macros", "valence-service-base", diff --git a/contracts/services/forwarder/src/tests.rs b/contracts/services/forwarder/src/tests.rs index 3796f312..813d18f5 100644 --- a/contracts/services/forwarder/src/tests.rs +++ b/contracts/services/forwarder/src/tests.rs @@ -1,5 +1,5 @@ use crate::msg::{ActionsMsgs, Config, ForwardingConstraints, QueryMsg, ServiceConfig}; -use cosmwasm_std::{coin, Addr, Coin, Uint128}; +use cosmwasm_std::{coin, Addr, Coin, Empty, Uint128}; use cw20::Cw20Coin; use cw_multi_test::{error::AnyResult, App, AppResponse, ContractWrapper, Executor}; use cw_ownable::Ownership; @@ -115,7 +115,7 @@ impl ForwarderTestSuite { } } -impl ServiceTestSuite for ForwarderTestSuite { +impl ServiceTestSuite for ForwarderTestSuite { fn app(&self) -> &App { self.inner.app() } diff --git a/contracts/services/generic-ibc-transfer/src/msg.rs b/contracts/services/generic-ibc-transfer/src/msg.rs index 5b3d7784..b8f073a7 100644 --- a/contracts/services/generic-ibc-transfer/src/msg.rs +++ b/contracts/services/generic-ibc-transfer/src/msg.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Deps, DepsMut, Uint128, Uint64}; +use cosmwasm_std::{Addr, CustomQuery, Deps, DepsMut, Uint128, Uint64}; use cw_ownable::cw_ownable_query; use getset::{Getters, Setters}; use valence_macros::OptionalStruct; @@ -48,9 +48,24 @@ pub enum IbcTransferAmount { #[cw_serde] pub struct RemoteChainInfo { pub channel_id: String, + pub port_id: Option, pub ibc_transfer_timeout: Option, } +impl RemoteChainInfo { + pub fn new( + channel_id: String, + port_id: Option, + ibc_transfer_timeout: Option, + ) -> Self { + Self { + channel_id, + port_id, + ibc_transfer_timeout, + } + } +} + impl ServiceConfig { pub fn new( input_addr: ServiceAccountType, @@ -91,6 +106,15 @@ impl ServiceConfig { )); } + if let Some(port_id) = &self.remote_chain_info.port_id { + if port_id.is_empty() { + return Err(ServiceError::ConfigurationError( + "Invalid IBC transfer config: remote_chain_info's port_id cannot be empty (if specified)." + .to_string(), + )); + } + } + if let Some(timeout) = self.remote_chain_info.ibc_transfer_timeout { if timeout.is_zero() { return Err(ServiceError::ConfigurationError( @@ -136,7 +160,14 @@ impl ServiceConfigInterface for ServiceConfig { } impl OptionalServiceConfig { - pub fn update_config(self, deps: &DepsMut, config: &mut Config) -> Result<(), ServiceError> { + pub fn update_config( + self, + deps: &DepsMut, + config: &mut Config, + ) -> Result<(), ServiceError> + where + T: CustomQuery, + { if let Some(input_addr) = self.input_addr { config.input_addr = input_addr.to_addr(deps.api)?; } @@ -148,7 +179,7 @@ impl OptionalServiceConfig { if let Some(denom) = self.denom { config.denom = denom .clone() - .into_checked(deps.as_ref()) + .into_checked(deps.as_ref().into_empty()) .map_err(|err| ServiceError::ConfigurationError(err.to_string()))?; } diff --git a/contracts/services/generic-ibc-transfer/src/tests.rs b/contracts/services/generic-ibc-transfer/src/tests.rs index 41873807..2f50a087 100644 --- a/contracts/services/generic-ibc-transfer/src/tests.rs +++ b/contracts/services/generic-ibc-transfer/src/tests.rs @@ -1,5 +1,5 @@ use crate::msg::{ActionMsgs, Config, IbcTransferAmount, QueryMsg, RemoteChainInfo, ServiceConfig}; -use cosmwasm_std::{coin, Addr, Uint128, Uint64}; +use cosmwasm_std::{coin, Addr, Empty, Uint128, Uint64}; use cw_multi_test::{error::AnyResult, App, AppResponse, ContractWrapper, Executor}; use cw_ownable::Ownership; use getset::{Getters, Setters}; @@ -18,7 +18,7 @@ struct IbcTransferTestSuite { #[getset(get)] inner: ServiceTestSuiteBase, #[getset(get)] - gen_ibc_transfer_code_id: u64, + ibc_transfer_code_id: u64, #[getset(get)] input_addr: Addr, #[getset(get)] @@ -42,32 +42,32 @@ impl IbcTransferTestSuite { let output_addr = inner.api().addr_make("output_account"); // Template contract - let gen_ibc_transfer_code = ContractWrapper::new( + let ibc_transfer_code = ContractWrapper::new( crate::contract::execute, crate::contract::instantiate, crate::contract::query, ); - let gen_ibc_transfer_code_id = inner.app_mut().store_code(Box::new(gen_ibc_transfer_code)); + let ibc_transfer_code_id = inner.app_mut().store_code(Box::new(ibc_transfer_code)); Self { inner, - gen_ibc_transfer_code_id, + ibc_transfer_code_id, input_addr, output_addr, input_balance, } } - pub fn gen_ibc_transfer_init(&mut self, cfg: &ServiceConfig) -> Addr { + pub fn ibc_transfer_init(&mut self, cfg: &ServiceConfig) -> Addr { let init_msg = InstantiateMsg { owner: self.owner().to_string(), processor: self.processor().to_string(), config: cfg.clone(), }; let addr = self.contract_init( - self.gen_ibc_transfer_code_id, - "generic_ibc_transfer_svc", + self.ibc_transfer_code_id, + "ibc_transfer_service", &init_msg, &[], ); @@ -85,7 +85,7 @@ impl IbcTransferTestSuite { addr } - fn gen_ibc_transfer_config( + fn ibc_transfer_config( &self, denom: String, amount: IbcTransferAmount, @@ -122,7 +122,7 @@ impl IbcTransferTestSuite { } } -impl ServiceTestSuite for IbcTransferTestSuite { +impl ServiceTestSuite for IbcTransferTestSuite { fn app(&self) -> &App { self.inner.app() } @@ -148,22 +148,22 @@ impl ServiceTestSuite for IbcTransferTestSuite { } } +// Note: all tests below are replicated to the Neutron IBC transfer service +// Any change in the tests below should be reflected in the Neutron IBC transfer service. + #[test] fn instantiate_with_valid_config() { let mut suite = IbcTransferTestSuite::default(); - let cfg = suite.gen_ibc_transfer_config( + let cfg = suite.ibc_transfer_config( NTRN.to_string(), IbcTransferAmount::FullAmount, "".to_string(), - RemoteChainInfo { - channel_id: "channel-1".to_string(), - ibc_transfer_timeout: Some(600u64.into()), - }, + RemoteChainInfo::new("channel-1".to_string(), None, Some(600u64.into())), ); // Instantiate IBC transfer contract - let svc = suite.gen_ibc_transfer_init(&cfg); + let svc = suite.ibc_transfer_init(&cfg); // Verify owner let owner_res: Ownership = suite.query_wasm(&svc, &QueryMsg::Ownership {}); @@ -192,14 +192,11 @@ fn instantiate_with_valid_config() { fn pre_validate_config_works() { let suite = IbcTransferTestSuite::default(); - let cfg = suite.gen_ibc_transfer_config( + let cfg = suite.ibc_transfer_config( NTRN.to_string(), IbcTransferAmount::FullAmount, "".to_string(), - RemoteChainInfo { - channel_id: "channel-1".to_string(), - ibc_transfer_timeout: Some(600u64.into()), - }, + RemoteChainInfo::new("channel-1".to_string(), None, Some(600u64.into())), ); // Pre-validate config @@ -211,18 +208,15 @@ fn pre_validate_config_works() { fn instantiate_fails_for_zero_amount() { let mut suite = IbcTransferTestSuite::default(); - let cfg = suite.gen_ibc_transfer_config( + let cfg = suite.ibc_transfer_config( NTRN.to_string(), IbcTransferAmount::FixedAmount(Uint128::zero()), "".to_string(), - RemoteChainInfo { - channel_id: "channel-1".to_string(), - ibc_transfer_timeout: Some(600u64.into()), - }, + RemoteChainInfo::new("channel-1".to_string(), None, Some(600u64.into())), ); - // Instantiate Generic IBC transfer contract - suite.gen_ibc_transfer_init(&cfg); + // Instantiate IBC transfer contract + suite.ibc_transfer_init(&cfg); } #[test] @@ -232,18 +226,37 @@ fn instantiate_fails_for_zero_amount() { fn instantiate_fails_for_invalid_channel_id() { let mut suite = IbcTransferTestSuite::default(); - let cfg = suite.gen_ibc_transfer_config( + let cfg = suite.ibc_transfer_config( + NTRN.to_string(), + IbcTransferAmount::FixedAmount(Uint128::one()), + "".to_string(), + RemoteChainInfo::new("".to_string(), None, Some(600u64.into())), + ); + + // Instantiate IBC transfer contract + suite.ibc_transfer_init(&cfg); +} + +#[test] +#[should_panic( + expected = "Invalid IBC transfer config: remote_chain_info's port_id cannot be empty (if specified)." +)] +fn instantiate_fails_for_invalid_port_id() { + let mut suite = IbcTransferTestSuite::default(); + + let cfg = suite.ibc_transfer_config( NTRN.to_string(), IbcTransferAmount::FixedAmount(Uint128::one()), "".to_string(), - RemoteChainInfo { - channel_id: "".to_string(), - ibc_transfer_timeout: Some(600u64.into()), - }, + RemoteChainInfo::new( + "channel-1".to_string(), + Some("".to_string()), + Some(600u64.into()), + ), ); - // Instantiate Generic IBC transfer contract - suite.gen_ibc_transfer_init(&cfg); + // Instantiate IBC transfer contract + suite.ibc_transfer_init(&cfg); } #[test] @@ -253,18 +266,15 @@ fn instantiate_fails_for_invalid_channel_id() { fn instantiate_fails_for_invalid_ibc_transfer_timeout() { let mut suite = IbcTransferTestSuite::default(); - let cfg = suite.gen_ibc_transfer_config( + let cfg = suite.ibc_transfer_config( NTRN.to_string(), IbcTransferAmount::FixedAmount(Uint128::one()), "".to_string(), - RemoteChainInfo { - channel_id: "channel-1".to_string(), - ibc_transfer_timeout: Some(Uint64::zero()), - }, + RemoteChainInfo::new("channel-1".to_string(), None, Some(Uint64::zero())), ); - // Instantiate Generic IBC transfer contract - suite.gen_ibc_transfer_init(&cfg); + // Instantiate IBC transfer contract + suite.ibc_transfer_init(&cfg); } // Config update tests @@ -274,18 +284,15 @@ fn instantiate_fails_for_invalid_ibc_transfer_timeout() { fn update_config_validates_config() { let mut suite = IbcTransferTestSuite::default(); - let mut cfg = suite.gen_ibc_transfer_config( + let mut cfg = suite.ibc_transfer_config( NTRN.to_string(), IbcTransferAmount::FullAmount, "".to_string(), - RemoteChainInfo { - channel_id: "channel-1".to_string(), - ibc_transfer_timeout: Some(600u64.into()), - }, + RemoteChainInfo::new("channel-1".to_string(), None, Some(600u64.into())), ); // Instantiate IBC transfer contract - let svc = suite.gen_ibc_transfer_init(&cfg); + let svc = suite.ibc_transfer_init(&cfg); // Update config and set amount to zero cfg.amount = IbcTransferAmount::FixedAmount(Uint128::zero()); @@ -298,18 +305,15 @@ fn update_config_validates_config() { fn update_config_with_valid_config() { let mut suite = IbcTransferTestSuite::default(); - let mut cfg = suite.gen_ibc_transfer_config( + let mut cfg = suite.ibc_transfer_config( NTRN.to_string(), IbcTransferAmount::FullAmount, "".to_string(), - RemoteChainInfo { - channel_id: "channel-1".to_string(), - ibc_transfer_timeout: Some(600u64.into()), - }, + RemoteChainInfo::new("channel-1".to_string(), None, Some(600u64.into())), ); // Instantiate IBC transfer contract - let svc = suite.gen_ibc_transfer_init(&cfg); + let svc = suite.ibc_transfer_init(&cfg); // Update config: swap input and output addresses cfg.input_addr = ServiceAccountType::Addr(suite.output_addr().to_string()); @@ -330,10 +334,7 @@ fn update_config_with_valid_config() { CheckedDenom::Native(NTRN.into()), IbcTransferAmount::FixedAmount(ONE_MILLION.into()), "Chancellor on brink of second bailout for banks.".to_string(), - RemoteChainInfo { - channel_id: "channel-1".to_string(), - ibc_transfer_timeout: Some(600u64.into()) - } + RemoteChainInfo::new("channel-1".to_string(), None, Some(600u64.into())) ) ); } @@ -347,18 +348,15 @@ fn update_config_with_valid_config() { fn ibc_transfer_fails_for_insufficient_balance() { let mut suite = IbcTransferTestSuite::default(); - let cfg = suite.gen_ibc_transfer_config( + let cfg = suite.ibc_transfer_config( NTRN.to_string(), IbcTransferAmount::FixedAmount(ONE_MILLION.into()), "".to_string(), - RemoteChainInfo { - channel_id: "channel-1".to_string(), - ibc_transfer_timeout: Some(600u64.into()), - }, + RemoteChainInfo::new("channel-1".to_string(), None, Some(600u64.into())), ); // Instantiate contract - let svc = suite.gen_ibc_transfer_init(&cfg); + let svc = suite.ibc_transfer_init(&cfg); // Execute IBC transfer suite.execute_ibc_transfer(svc).unwrap(); diff --git a/contracts/services/neutron-ibc-transfer/Cargo.toml b/contracts/services/neutron-ibc-transfer/Cargo.toml index d9889111..f453415b 100644 --- a/contracts/services/neutron-ibc-transfer/Cargo.toml +++ b/contracts/services/neutron-ibc-transfer/Cargo.toml @@ -14,28 +14,26 @@ crate-type = ["cdylib", "rlib"] library = [] [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-ownable = { workspace = true } -cw-storage-plus = { workspace = true } -cw-utils = { workspace = true } -getset = { workspace = true } -neutron-sdk = { workspace = true } -schemars = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } -valence-account-utils = { workspace = true } -valence-macros = { workspace = true } -valence-ibc-utils = { workspace = true, features = ["neutron"] } -valence-service-utils = { workspace = true } -valence-service-base = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-ownable = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +getset = { workspace = true } +neutron-sdk = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +valence-macros = { workspace = true } +valence-ibc-utils = { workspace = true, features = ["neutron"] } +valence-generic-ibc-transfer-service = { workspace = true } +valence-service-utils = { workspace = true } +valence-service-base = { workspace = true } -# [dev-dependencies] -# cw-multi-test = { workspace = true } -# cw-ownable = { workspace = true } -# cw20 = { workspace = true } -# cw20-base = { workspace = true } -# sha2 = { workspace = true } -# valence-account-utils = { workspace = true } -# valence-service-utils = { workspace = true, features = ["testing"] } -# valence-test-dynamic-ratio = { workspace = true } +[dev-dependencies] +cw-multi-test = { workspace = true } +cw20 = { workspace = true } +cw20-base = { workspace = true } +sha2 = { workspace = true } +valence-account-utils = { workspace = true } +valence-service-utils = { workspace = true, features = ["testing"] } diff --git a/contracts/services/neutron-ibc-transfer/src/bin/schema.rs b/contracts/services/neutron-ibc-transfer/src/bin/schema.rs index 20e2a381..4c372606 100644 --- a/contracts/services/neutron-ibc-transfer/src/bin/schema.rs +++ b/contracts/services/neutron-ibc-transfer/src/bin/schema.rs @@ -1,14 +1,14 @@ use cosmwasm_schema::write_api; use valence_neutron_ibc_transfer_service::msg::{ - ActionsMsgs, OptionalServiceConfig, QueryMsg, ServiceConfig, + ActionMsgs, OptionalServiceConfig, QueryMsg, ServiceConfig, }; use valence_service_utils::msg::{ExecuteMsg, InstantiateMsg}; fn main() { write_api! { instantiate: InstantiateMsg, - execute: ExecuteMsg, + execute: ExecuteMsg, query: QueryMsg, } } diff --git a/contracts/services/neutron-ibc-transfer/src/contract.rs b/contracts/services/neutron-ibc-transfer/src/contract.rs index 54174916..16647647 100644 --- a/contracts/services/neutron-ibc-transfer/src/contract.rs +++ b/contracts/services/neutron-ibc-transfer/src/contract.rs @@ -7,7 +7,7 @@ use valence_service_utils::{ msg::{ExecuteMsg, InstantiateMsg}, }; -use crate::msg::{ActionsMsgs, Config, OptionalServiceConfig, QueryMsg, ServiceConfig}; +use crate::msg::{ActionMsgs, Config, OptionalServiceConfig, QueryMsg, ServiceConfig}; // version info for migration info const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); @@ -15,12 +15,12 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( - deps: DepsMut, + deps: DepsMut, _env: Env, _info: MessageInfo, msg: InstantiateMsg, ) -> Result { - valence_service_base::instantiate(deps, CONTRACT_NAME, CONTRACT_VERSION, msg) + valence_service_base::instantiate(deps.into_empty(), CONTRACT_NAME, CONTRACT_VERSION, msg) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -28,7 +28,7 @@ pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, - msg: ExecuteMsg, + msg: ExecuteMsg, ) -> Result { valence_service_base::execute( deps, @@ -45,24 +45,31 @@ mod actions { use neutron_sdk::bindings::query::NeutronQuery; use valence_service_utils::{error::ServiceError, execute_on_behalf_of}; - use crate::msg::{ActionsMsgs, Config}; + use crate::msg::{ActionMsgs, Config, IbcTransferAmount}; pub fn process_action( deps: DepsMut, env: Env, _info: MessageInfo, - msg: ActionsMsgs, + msg: ActionMsgs, cfg: Config, ) -> Result { match msg { - ActionsMsgs::IbcTransfer {} => { + ActionMsgs::IbcTransfer {} => { let balance = cfg.denom().query_balance(&deps.querier, cfg.input_addr())?; - if balance < *cfg.amount() { - return Err(ServiceError::ExecutionError(format!( - "Insufficient balance for denom '{}' in config (required: {}, available: {}).", - cfg.denom(), cfg.amount(), balance, - ))); - } + + let amount = match cfg.amount() { + IbcTransferAmount::FullAmount => balance, + IbcTransferAmount::FixedAmount(amount) => { + if balance < *amount { + return Err(ServiceError::ExecutionError(format!( + "Insufficient balance for denom '{}' in config (required: {}, available: {}).", + cfg.denom(), amount, balance, + ))); + } + *amount + } + }; // IBC Transfer funds from input account to output account on the remote chain let block_time = env.block.time; @@ -74,7 +81,7 @@ mod actions { cfg.input_addr().to_string(), cfg.output_addr().to_string(), cfg.denom().to_string(), - cfg.amount().u128(), + amount.u128(), cfg.memo().clone(), None, cfg.remote_chain_info() @@ -113,7 +120,7 @@ mod execute { } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Ownership {} => { to_json_binary(&valence_service_base::get_ownership(deps.storage)?) diff --git a/contracts/services/neutron-ibc-transfer/src/lib.rs b/contracts/services/neutron-ibc-transfer/src/lib.rs index 08d6d688..3679afc2 100644 --- a/contracts/services/neutron-ibc-transfer/src/lib.rs +++ b/contracts/services/neutron-ibc-transfer/src/lib.rs @@ -1,5 +1,11 @@ pub mod contract; -pub mod msg; + +pub mod msg { + pub use valence_generic_ibc_transfer_service::msg::{ + ActionMsgs, Config, IbcTransferAmount, OptionalServiceConfig, QueryMsg, RemoteChainInfo, + ServiceConfig, + }; +} #[cfg(test)] mod tests; diff --git a/contracts/services/neutron-ibc-transfer/src/msg.rs b/contracts/services/neutron-ibc-transfer/src/msg.rs deleted file mode 100644 index 7225c622..00000000 --- a/contracts/services/neutron-ibc-transfer/src/msg.rs +++ /dev/null @@ -1,106 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Deps, DepsMut, Uint128, Uint64}; -use cw_ownable::cw_ownable_query; -use getset::{Getters, Setters}; -use neutron_sdk::bindings::query::NeutronQuery; -use valence_macros::OptionalStruct; -use valence_service_utils::{ - denoms::{CheckedDenom, UncheckedDenom}, - error::ServiceError, - msg::ServiceConfigValidation, - ServiceAccountType, ServiceConfigInterface, -}; - -#[cw_serde] -pub enum ActionsMsgs { - IbcTransfer {}, -} - -#[cw_ownable_query] -#[cw_serde] -#[derive(QueryResponses)] -/// Enum representing the different query messages that can be sent. -pub enum QueryMsg { - /// Query to get the processor address. - #[returns(Addr)] - GetProcessor {}, - /// Query to get the service configuration. - #[returns(Config)] - GetServiceConfig {}, -} - -#[cw_serde] -#[derive(OptionalStruct)] -pub struct ServiceConfig { - pub input_addr: ServiceAccountType, - pub output_addr: String, - pub denom: UncheckedDenom, - pub amount: Uint128, - pub memo: String, - pub remote_chain_info: RemoteChainInfo, -} - -#[cw_serde] -pub struct RemoteChainInfo { - pub channel_id: String, - pub port_id: Option, - pub denom: String, - pub ibc_transfer_timeout: Option, -} - -impl ServiceConfigValidation for ServiceConfig { - #[cfg(not(target_arch = "wasm32"))] - fn pre_validate(&self, _api: &dyn cosmwasm_std::Api) -> Result<(), ServiceError> { - Ok(()) - } - - fn validate(&self, deps: Deps) -> Result { - Ok(Config { - input_addr: self.input_addr.to_addr(deps.api)?, - // Can't validate output address as it's on another chain - output_addr: Addr::unchecked(self.output_addr.clone()), - denom: self - .denom - .clone() - .into_checked(deps) - .map_err(|err| ServiceError::ConfigurationError(err.to_string()))?, - amount: self.amount, - memo: self.memo.clone(), - remote_chain_info: self.remote_chain_info.clone(), - }) - } -} - -impl ServiceConfigInterface for ServiceConfig { - /// This function is used to see if 2 configs are different - fn is_diff(&self, other: &ServiceConfig) -> bool { - !self.eq(other) - } -} - -impl OptionalServiceConfig { - pub fn update_config( - self, - _deps: &DepsMut, - _config: &mut Config, - ) -> Result<(), ServiceError> { - Ok(()) - } -} - -#[cw_serde] -#[derive(Getters, Setters)] -pub struct Config { - #[getset(get = "pub", set)] - input_addr: Addr, - #[getset(get = "pub", set)] - output_addr: Addr, - #[getset(get = "pub", set)] - denom: CheckedDenom, - #[getset(get = "pub", set)] - amount: Uint128, - #[getset(get = "pub", set)] - memo: String, - #[getset(get = "pub", set)] - remote_chain_info: RemoteChainInfo, -} diff --git a/contracts/services/neutron-ibc-transfer/src/tests.rs b/contracts/services/neutron-ibc-transfer/src/tests.rs index ebd8e327..789e1d10 100644 --- a/contracts/services/neutron-ibc-transfer/src/tests.rs +++ b/contracts/services/neutron-ibc-transfer/src/tests.rs @@ -1,136 +1,367 @@ -// use crate::msg::{ActionsMsgs, Config, QueryMsg, ServiceConfig}; -// use cosmwasm_std::Addr; -// use cw_multi_test::{error::AnyResult, App, AppResponse, ContractWrapper, Executor}; -// use cw_ownable::Ownership; -// use getset::{Getters, Setters}; -// use valence_service_utils::{ -// msg::ExecuteMsg, -// msg::InstantiateMsg, -// testing::{ServiceTestSuite, ServiceTestSuiteBase}, -// }; - -// #[derive(Getters, Setters)] -// struct TemplateTestSuite { -// #[getset(get)] -// inner: ServiceTestSuiteBase, -// #[getset(get)] -// template_code_id: u64, -// } - -// impl Default for TemplateTestSuite { -// fn default() -> Self { -// Self::new() -// } -// } - -// #[allow(dead_code)] -// impl TemplateTestSuite { -// pub fn new() -> Self { -// let mut inner = ServiceTestSuiteBase::new(); - -// // Template contract -// let template_code = ContractWrapper::new( -// crate::contract::execute, -// crate::contract::instantiate, -// crate::contract::query, -// ); - -// let template_code_id = inner.app_mut().store_code(Box::new(template_code)); - -// Self { -// inner, -// template_code_id, -// } -// } - -// pub fn template_init(&mut self, cfg: &ServiceConfig) -> Addr { -// let init_msg = InstantiateMsg { -// owner: self.owner().to_string(), -// processor: self.processor().to_string(), -// config: cfg.clone(), -// }; -// self.contract_init(self.template_code_id, "template", &init_msg, &[]) -// } - -// fn template_config(&self) -> ServiceConfig { -// ServiceConfig {} -// } - -// fn execute_noop(&mut self, addr: Addr) -> AnyResult { -// self.contract_execute( -// addr, -// &ExecuteMsg::<_, ServiceConfig>::ProcessAction(ActionsMsgs::IbcTransfer {}), -// ) -// } - -// fn update_config(&mut self, addr: Addr, new_config: ServiceConfig) -> AnyResult { -// let owner = self.owner().clone(); -// self.app_mut().execute_contract( -// owner, -// addr, -// &ExecuteMsg::::UpdateConfig { new_config }, -// &[], -// ) -// } -// } - -// impl ServiceTestSuite for TemplateTestSuite { -// fn app(&self) -> &App { -// self.inner.app() -// } - -// fn app_mut(&mut self) -> &mut App { -// self.inner.app_mut() -// } - -// fn owner(&self) -> &Addr { -// self.inner.owner() -// } - -// fn processor(&self) -> &Addr { -// self.inner.processor() -// } - -// fn account_code_id(&self) -> u64 { -// self.inner.account_code_id() -// } - -// fn cw20_code_id(&self) -> u64 { -// self.inner.cw20_code_id() -// } -// } - -// #[test] -// fn instantiate_with_valid_config() { -// let mut suite = TemplateTestSuite::default(); - -// let cfg = suite.template_config(); - -// // Instantiate Template contract -// let svc = suite.template_init(&cfg); - -// // Verify owner -// let owner_res: Ownership = suite.query_wasm(&svc, &QueryMsg::Ownership {}); -// assert_eq!(owner_res.owner, Some(suite.owner().clone())); - -// // Verify processor -// let processor_addr: Addr = suite.query_wasm(&svc, &QueryMsg::GetProcessor {}); -// assert_eq!(processor_addr, suite.processor().clone()); - -// // Verify service config -// let svc_cfg: Config = suite.query_wasm(&svc, &QueryMsg::GetServiceConfig {}); -// assert_eq!(svc_cfg, Config {}); -// } - -// #[test] -// fn execute_action() { -// let mut suite = TemplateTestSuite::default(); - -// let cfg = suite.template_config(); - -// // Instantiate Template contract -// let svc = suite.template_init(&cfg); - -// // Execute action -// suite.execute_noop(svc).unwrap(); -// } +use crate::msg::{ActionMsgs, Config, IbcTransferAmount, QueryMsg, RemoteChainInfo, ServiceConfig}; +use cosmwasm_std::{coin, Addr, Empty, Uint128, Uint64}; +use cw_multi_test::{ + custom_app, error::AnyResult, no_init, AppResponse, BasicApp, ContractWrapper, Executor, +}; +use cw_ownable::Ownership; +use getset::{Getters, Setters}; +use neutron_sdk::bindings::query::NeutronQuery; +use valence_service_utils::{ + denoms::CheckedDenom, + msg::{ExecuteMsg, InstantiateMsg, ServiceConfigValidation}, + testing::{CustomServiceTestSuiteBase, ServiceTestSuite}, + ServiceAccountType, +}; + +const NTRN: &str = "untrn"; +const ONE_MILLION: u128 = 1_000_000_000_000_u128; + +#[derive(Getters, Setters)] +struct IbcTransferTestSuite { + #[getset(get)] + inner: CustomServiceTestSuiteBase, + #[getset(get)] + ibc_transfer_code_id: u64, + #[getset(get)] + input_addr: Addr, + #[getset(get)] + output_addr: Addr, + #[getset(get)] + input_balance: Option<(u128, String)>, +} + +impl Default for IbcTransferTestSuite { + fn default() -> Self { + Self::new(None) + } +} + +#[allow(dead_code)] +impl IbcTransferTestSuite { + pub fn new(input_balance: Option<(u128, String)>) -> Self { + let app = custom_app::(no_init); + let mut inner = CustomServiceTestSuiteBase::new(app); + + let input_addr = inner.get_contract_addr(inner.account_code_id(), "input_account"); + let output_addr = inner.api().addr_make("output_account"); + + // Template contract + let ibc_transfer_code = ContractWrapper::new( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ); + + let ibc_transfer_code_id = inner.app_mut().store_code(Box::new(ibc_transfer_code)); + + Self { + inner, + ibc_transfer_code_id, + input_addr, + output_addr, + input_balance, + } + } + + pub fn ibc_transfer_init(&mut self, cfg: &ServiceConfig) -> Addr { + let init_msg = InstantiateMsg { + owner: self.owner().to_string(), + processor: self.processor().to_string(), + config: cfg.clone(), + }; + let addr = self.contract_init( + self.ibc_transfer_code_id, + "ibc_transfer_service", + &init_msg, + &[], + ); + + let input_addr = self.input_addr().clone(); + if self.app_mut().contract_data(&input_addr).is_err() { + let account_addr = self.account_init("input_account", vec![addr.to_string()]); + assert_eq!(account_addr, input_addr); + + if let Some((amount, denom)) = self.input_balance.as_ref().cloned() { + self.init_balance(&input_addr, vec![coin(amount, denom.to_string())]); + } + } + + addr + } + + fn ibc_transfer_config( + &self, + denom: String, + amount: IbcTransferAmount, + memo: String, + remote_chain_info: RemoteChainInfo, + ) -> ServiceConfig { + ServiceConfig { + input_addr: valence_service_utils::ServiceAccountType::Addr( + self.input_addr().to_string(), + ), + output_addr: self.output_addr().to_string(), + denom: valence_service_utils::denoms::UncheckedDenom::Native(denom), + amount, + memo, + remote_chain_info, + } + } + + fn execute_ibc_transfer(&mut self, addr: Addr) -> AnyResult { + self.contract_execute( + addr, + &ExecuteMsg::<_, ServiceConfig>::ProcessAction(ActionMsgs::IbcTransfer {}), + ) + } + + fn update_config(&mut self, addr: Addr, new_config: ServiceConfig) -> AnyResult { + let owner = self.owner().clone(); + self.app_mut().execute_contract( + owner, + addr, + &ExecuteMsg::::UpdateConfig { new_config }, + &[], + ) + } +} + +impl ServiceTestSuite for IbcTransferTestSuite { + fn app(&self) -> &BasicApp { + self.inner.app() + } + + fn app_mut(&mut self) -> &mut BasicApp { + self.inner.app_mut() + } + + fn owner(&self) -> &Addr { + self.inner.owner() + } + + fn processor(&self) -> &Addr { + self.inner.processor() + } + + fn account_code_id(&self) -> u64 { + self.inner.account_code_id() + } + + fn cw20_code_id(&self) -> u64 { + self.inner.cw20_code_id() + } +} + +// Note: all tests below are replicated from the Generic IBC transfer service +// Any change in the tests below should be reflected in the Generic IBC transfer service. + +#[test] +fn instantiate_with_valid_config() { + let mut suite = IbcTransferTestSuite::default(); + + let cfg = suite.ibc_transfer_config( + NTRN.to_string(), + IbcTransferAmount::FullAmount, + "".to_string(), + RemoteChainInfo::new("channel-1".to_string(), None, Some(600u64.into())), + ); + + // Instantiate IBC transfer contract + let svc = suite.ibc_transfer_init(&cfg); + + // Verify owner + let owner_res: Ownership = suite.query_wasm(&svc, &QueryMsg::Ownership {}); + assert_eq!(owner_res.owner, Some(suite.owner().clone())); + + // Verify processor + let processor_addr: Addr = suite.query_wasm(&svc, &QueryMsg::GetProcessor {}); + assert_eq!(processor_addr, suite.processor().clone()); + + // Verify service config + let svc_cfg: Config = suite.query_wasm(&svc, &QueryMsg::GetServiceConfig {}); + assert_eq!( + svc_cfg, + Config::new( + suite.input_addr().clone(), + suite.output_addr().clone(), + CheckedDenom::Native(NTRN.into()), + IbcTransferAmount::FullAmount, + "".to_string(), + cfg.remote_chain_info.clone() + ) + ); +} + +#[test] +fn pre_validate_config_works() { + let suite = IbcTransferTestSuite::default(); + + let cfg = suite.ibc_transfer_config( + NTRN.to_string(), + IbcTransferAmount::FullAmount, + "".to_string(), + RemoteChainInfo::new("channel-1".to_string(), None, Some(600u64.into())), + ); + + // Pre-validate config + cfg.pre_validate(suite.api()).unwrap(); +} + +#[test] +#[should_panic(expected = "Invalid IBC transfer config: amount cannot be zero.")] +fn instantiate_fails_for_zero_amount() { + let mut suite = IbcTransferTestSuite::default(); + + let cfg = suite.ibc_transfer_config( + NTRN.to_string(), + IbcTransferAmount::FixedAmount(Uint128::zero()), + "".to_string(), + RemoteChainInfo::new("channel-1".to_string(), None, Some(600u64.into())), + ); + + // Instantiate IBC transfer contract + suite.ibc_transfer_init(&cfg); +} + +#[test] +#[should_panic( + expected = "Invalid IBC transfer config: remote_chain_info's channel_id cannot be empty." +)] +fn instantiate_fails_for_invalid_channel_id() { + let mut suite = IbcTransferTestSuite::default(); + + let cfg = suite.ibc_transfer_config( + NTRN.to_string(), + IbcTransferAmount::FixedAmount(Uint128::one()), + "".to_string(), + RemoteChainInfo::new("".to_string(), None, Some(600u64.into())), + ); + + // Instantiate IBC transfer contract + suite.ibc_transfer_init(&cfg); +} + +#[test] +#[should_panic( + expected = "Invalid IBC transfer config: remote_chain_info's port_id cannot be empty (if specified)." +)] +fn instantiate_fails_for_invalid_port_id() { + let mut suite = IbcTransferTestSuite::default(); + + let cfg = suite.ibc_transfer_config( + NTRN.to_string(), + IbcTransferAmount::FixedAmount(Uint128::one()), + "".to_string(), + RemoteChainInfo::new( + "channel-1".to_string(), + Some("".to_string()), + Some(600u64.into()), + ), + ); + + // Instantiate IBC transfer contract + suite.ibc_transfer_init(&cfg); +} + +#[test] +#[should_panic( + expected = "Invalid IBC transfer config: remote_chain_info's ibc_transfer_timeout cannot be zero." +)] +fn instantiate_fails_for_invalid_ibc_transfer_timeout() { + let mut suite = IbcTransferTestSuite::default(); + + let cfg = suite.ibc_transfer_config( + NTRN.to_string(), + IbcTransferAmount::FixedAmount(Uint128::one()), + "".to_string(), + RemoteChainInfo::new("channel-1".to_string(), None, Some(Uint64::zero())), + ); + + // Instantiate IBC transfer contract + suite.ibc_transfer_init(&cfg); +} + +// Config update tests + +#[test] +#[should_panic(expected = "Invalid IBC transfer config: amount cannot be zero.")] +fn update_config_validates_config() { + let mut suite = IbcTransferTestSuite::default(); + + let mut cfg = suite.ibc_transfer_config( + NTRN.to_string(), + IbcTransferAmount::FullAmount, + "".to_string(), + RemoteChainInfo::new("channel-1".to_string(), None, Some(600u64.into())), + ); + + // Instantiate IBC transfer contract + let svc = suite.ibc_transfer_init(&cfg); + + // Update config and set amount to zero + cfg.amount = IbcTransferAmount::FixedAmount(Uint128::zero()); + + // Execute update config action + suite.update_config(svc.clone(), cfg).unwrap(); +} + +#[test] +fn update_config_with_valid_config() { + let mut suite = IbcTransferTestSuite::default(); + + let mut cfg = suite.ibc_transfer_config( + NTRN.to_string(), + IbcTransferAmount::FullAmount, + "".to_string(), + RemoteChainInfo::new("channel-1".to_string(), None, Some(600u64.into())), + ); + + // Instantiate IBC transfer contract + let svc = suite.ibc_transfer_init(&cfg); + + // Update config: swap input and output addresses + cfg.input_addr = ServiceAccountType::Addr(suite.output_addr().to_string()); + cfg.output_addr = suite.input_addr().to_string(); + cfg.amount = IbcTransferAmount::FixedAmount(ONE_MILLION.into()); + cfg.memo = "Chancellor on brink of second bailout for banks.".to_string(); + + // Execute update config action + suite.update_config(svc.clone(), cfg).unwrap(); + + // Verify service config + let svc_cfg: Config = suite.query_wasm(&svc, &QueryMsg::GetServiceConfig {}); + assert_eq!( + svc_cfg, + Config::new( + suite.output_addr().clone(), + suite.input_addr().clone(), + CheckedDenom::Native(NTRN.into()), + IbcTransferAmount::FixedAmount(ONE_MILLION.into()), + "Chancellor on brink of second bailout for banks.".to_string(), + RemoteChainInfo::new("channel-1".to_string(), None, Some(600u64.into())) + ) + ); +} + +// Insufficient balance tests + +#[test] +#[should_panic( + expected = "Execution error: Insufficient balance for denom 'untrn' in config (required: 1000000000000, available: 0)." +)] +fn ibc_transfer_fails_for_insufficient_balance() { + let mut suite = IbcTransferTestSuite::default(); + + let cfg = suite.ibc_transfer_config( + NTRN.to_string(), + IbcTransferAmount::FixedAmount(ONE_MILLION.into()), + "".to_string(), + RemoteChainInfo::new("channel-1".to_string(), None, Some(600u64.into())), + ); + + // Instantiate contract + let svc = suite.ibc_transfer_init(&cfg); + + // Execute IBC transfer + suite.execute_ibc_transfer(svc).unwrap(); +} diff --git a/contracts/services/reverse-splitter/src/tests.rs b/contracts/services/reverse-splitter/src/tests.rs index 863ee3dc..9c4ea2aa 100644 --- a/contracts/services/reverse-splitter/src/tests.rs +++ b/contracts/services/reverse-splitter/src/tests.rs @@ -1,7 +1,7 @@ use crate::msg::{ ActionMsgs, Config, QueryMsg, ServiceConfig, SplitAmount, SplitConfig, UncheckedSplitConfig, }; -use cosmwasm_std::{Addr, Decimal}; +use cosmwasm_std::{Addr, Decimal, Empty}; use cw20::Cw20Coin; use cw_multi_test::{error::AnyResult, App, AppResponse, ContractWrapper, Executor}; use cw_ownable::Ownership; @@ -146,7 +146,7 @@ impl ReverseSplitterTestSuite { } } -impl ServiceTestSuite for ReverseSplitterTestSuite { +impl ServiceTestSuite for ReverseSplitterTestSuite { fn app(&self) -> &App { self.inner.app() } diff --git a/contracts/services/splitter/src/tests.rs b/contracts/services/splitter/src/tests.rs index b1cd57b1..77e0eaee 100644 --- a/contracts/services/splitter/src/tests.rs +++ b/contracts/services/splitter/src/tests.rs @@ -1,7 +1,7 @@ use crate::msg::{ ActionMsgs, Config, QueryMsg, ServiceConfig, SplitAmount, SplitConfig, UncheckedSplitConfig, }; -use cosmwasm_std::{coin, Addr, Coin, Decimal}; +use cosmwasm_std::{coin, Addr, Coin, Decimal, Empty}; use cw20::Cw20Coin; use cw_multi_test::{error::AnyResult, App, AppResponse, ContractWrapper, Executor}; use cw_ownable::Ownership; @@ -143,7 +143,7 @@ impl SplitterTestSuite { } } -impl ServiceTestSuite for SplitterTestSuite { +impl ServiceTestSuite for SplitterTestSuite { fn app(&self) -> &App { self.inner.app() } diff --git a/contracts/services/template/src/tests.rs b/contracts/services/template/src/tests.rs index add1648a..e9d36d6c 100644 --- a/contracts/services/template/src/tests.rs +++ b/contracts/services/template/src/tests.rs @@ -1,5 +1,5 @@ use crate::msg::{ActionsMsgs, Config, QueryMsg, ServiceConfig}; -use cosmwasm_std::Addr; +use cosmwasm_std::{Addr, Empty}; use cw_multi_test::{error::AnyResult, App, AppResponse, ContractWrapper, Executor}; use cw_ownable::Ownership; use getset::{Getters, Setters}; @@ -74,7 +74,7 @@ impl TemplateTestSuite { } } -impl ServiceTestSuite for TemplateTestSuite { +impl ServiceTestSuite for TemplateTestSuite { fn app(&self) -> &App { self.inner.app() } diff --git a/local-interchaintest/examples/ibc_transfer_juno_ntrn.rs b/local-interchaintest/examples/ibc_transfer_juno_ntrn.rs index 9b516094..654efdcd 100644 --- a/local-interchaintest/examples/ibc_transfer_juno_ntrn.rs +++ b/local-interchaintest/examples/ibc_transfer_juno_ntrn.rs @@ -145,6 +145,7 @@ fn main() -> Result<(), Box> { .src(JUNO_CHAIN_NAME) .dest(NEUTRON_CHAIN_NAME) .get(), + port_id: None, ibc_transfer_timeout: Some(600u64.into()), }, }, diff --git a/local-interchaintest/examples/ibc_transfer_ntrn_juno.rs b/local-interchaintest/examples/ibc_transfer_ntrn_juno.rs index 822c5419..9e08005f 100644 --- a/local-interchaintest/examples/ibc_transfer_ntrn_juno.rs +++ b/local-interchaintest/examples/ibc_transfer_ntrn_juno.rs @@ -19,7 +19,8 @@ use localic_utils::{ }; use log::info; -use valence_neutron_ibc_transfer_service::msg::{ActionsMsgs, ServiceConfig}; +use valence_generic_ibc_transfer_service::msg::IbcTransferAmount; +use valence_neutron_ibc_transfer_service::msg::{ActionMsgs, ServiceConfig}; use valence_service_utils::{denoms::UncheckedDenom, ServiceAccountType}; fn main() -> Result<(), Box> { @@ -145,7 +146,7 @@ fn main() -> Result<(), Box> { input_addr: ServiceAccountType::Addr(input_account.clone()), output_addr: output_account.clone(), denom: UncheckedDenom::Native(NTRN_DENOM.to_string()), - amount: transfer_amount.into(), + amount: IbcTransferAmount::FixedAmount(transfer_amount.into()), memo: "".to_owned(), remote_chain_info: valence_neutron_ibc_transfer_service::msg::RemoteChainInfo { channel_id: test_ctx @@ -154,7 +155,6 @@ fn main() -> Result<(), Box> { .unwrap() .clone(), port_id: None, - denom: neutron_on_juno_denom.to_string(), ibc_transfer_timeout: Some(600u64.into()), }, }, @@ -189,9 +189,8 @@ fn main() -> Result<(), Box> { ); info!("Initiate IBC transfer"); - let ibc_transfer_msg = &valence_service_utils::msg::ExecuteMsg::<_, ()>::ProcessAction( - ActionsMsgs::IbcTransfer {}, - ); + let ibc_transfer_msg = + &valence_service_utils::msg::ExecuteMsg::<_, ()>::ProcessAction(ActionMsgs::IbcTransfer {}); contract_execute( test_ctx diff --git a/packages/service-utils/src/testing.rs b/packages/service-utils/src/testing.rs index fb44a083..b4c0e2d8 100644 --- a/packages/service-utils/src/testing.rs +++ b/packages/service-utils/src/testing.rs @@ -1,9 +1,12 @@ use cosmwasm_std::{ - coin, instantiate2_address, testing::MockApi, Addr, Api, CodeInfoResponse, Coin, Uint128, + coin, instantiate2_address, testing::MockApi, Addr, Api, CodeInfoResponse, Coin, CustomMsg, + CustomQuery, Empty, Uint128, }; use cw20::Cw20Coin; -use cw_multi_test::{error::AnyResult, next_block, App, AppResponse, ContractWrapper, Executor}; -use serde::Serialize; +use cw_multi_test::{ + error::AnyResult, next_block, App, AppResponse, BasicApp, ContractWrapper, Executor, +}; +use serde::{de::DeserializeOwned, Serialize}; use sha2::{Digest, Sha256}; use std::fmt::Debug; @@ -55,9 +58,13 @@ impl ServiceTestSuiteBase { } } -pub trait ServiceTestSuite { - fn app(&self) -> &App; - fn app_mut(&mut self) -> &mut App; +pub trait ServiceTestSuite +where + ExecC: CustomMsg + Debug + DeserializeOwned + 'static, + QueryC: CustomQuery + Debug + DeserializeOwned + 'static, +{ + fn app(&self) -> &BasicApp; + fn app_mut(&mut self) -> &mut BasicApp; fn owner(&self) -> &Addr; fn processor(&self) -> &Addr; fn account_code_id(&self) -> u64; @@ -275,7 +282,7 @@ pub trait ServiceTestSuite { } } -impl ServiceTestSuite for ServiceTestSuiteBase { +impl ServiceTestSuite for ServiceTestSuiteBase { fn app(&self) -> &App { &self.app } @@ -300,3 +307,80 @@ impl ServiceTestSuite for ServiceTestSuiteBase { self.cw20_code_id } } + +pub struct CustomServiceTestSuiteBase +where + ExecC: CustomMsg + Debug + DeserializeOwned, + QueryC: CustomQuery + Debug + DeserializeOwned, +{ + app: BasicApp, + owner: Addr, + processor: Addr, + account_code_id: u64, + cw20_code_id: u64, +} + +impl CustomServiceTestSuiteBase +where + ExecC: CustomMsg + Debug + DeserializeOwned + 'static, + QueryC: CustomQuery + Debug + DeserializeOwned + 'static, +{ + pub fn new(mut app: BasicApp) -> Self { + let owner = app.api().addr_make("owner"); + let processor = app.api().addr_make("processor"); + + let account_code = ContractWrapper::new_with_empty( + valence_base_account::contract::execute, + valence_base_account::contract::instantiate, + valence_base_account::contract::query, + ); + + let account_code_id = app.store_code(Box::new(account_code)); + + let cw20_code = ContractWrapper::new_with_empty( + cw20_base::contract::execute, + cw20_base::contract::instantiate, + cw20_base::contract::query, + ); + + let cw20_code_id = app.store_code(Box::new(cw20_code)); + + Self { + app, + owner, + processor, + account_code_id, + cw20_code_id, + } + } +} + +impl ServiceTestSuite for CustomServiceTestSuiteBase +where + ExecC: CustomMsg + Debug + DeserializeOwned + 'static, + QueryC: CustomQuery + Debug + DeserializeOwned + 'static, +{ + fn app(&self) -> &BasicApp { + &self.app + } + + fn app_mut(&mut self) -> &mut BasicApp { + &mut self.app + } + + fn owner(&self) -> &Addr { + &self.owner + } + + fn processor(&self) -> &Addr { + &self.processor + } + + fn account_code_id(&self) -> u64 { + self.account_code_id + } + + fn cw20_code_id(&self) -> u64 { + self.cw20_code_id + } +}