diff --git a/Cargo.lock b/Cargo.lock index 2c1cb8e..967d72c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,6 +19,34 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "apollo-cw-asset" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "423502406a307052f6877030f48b5fb4e9fb338fc5e7c8ca1064210def52876b" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw20 1.1.1", + "schemars", + "serde", +] + +[[package]] +name = "astroport" +version = "2.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b991ce88f077c1d12f850c1f8fba3671bd72784f43f1638731d6d28f9b79839" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "cw20 0.15.1", + "itertools 0.10.5", + "uint", +] + [[package]] name = "astroport" version = "3.6.1" @@ -48,6 +76,106 @@ dependencies = [ "thiserror", ] +[[package]] +name = "astroport-factory" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ecf768e2d3153bebfbe0c502ffa4199a52598e9b6e89fca54339615b2de77eb" +dependencies = [ + "astroport 2.9.3", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw2 0.15.1", + "itertools 0.10.5", + "protobuf", + "thiserror", +] + +[[package]] +name = "astroport-native-coin-registry" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648ed6827a8f11012c0377fb60329204e8511fe46c86db3220113e70bdc57826" +dependencies = [ + "astroport 2.9.3", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus 0.15.1", + "cw2 0.15.1", + "thiserror", +] + +[[package]] +name = "astroport-pair" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd96bc64722440636ed6267a6945ccce076231a08467d6d46a4af4c4ff062c69" +dependencies = [ + "astroport 3.6.1", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw-utils 1.0.2", + "cw2 0.15.1", + "cw20 0.15.1", + "integer-sqrt", + "protobuf", + "thiserror", +] + +[[package]] +name = "astroport-token" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3360383a2e585211da9a455ad57eb100578253b5d18a387f025cadd666604d99" +dependencies = [ + "astroport 2.9.3", + "cosmwasm-schema", + "cosmwasm-std", + "cw2 0.15.1", + "cw20 0.15.1", + "cw20-base", + "snafu", +] + +[[package]] +name = "astroport-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff238ecf7aee3ec7205367032fcfc78214b8921740d5f880e84644a9cb973d3" +dependencies = [ + "apollo-cw-asset", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw20 1.1.1", + "schemars", + "serde", + "uint", +] + +[[package]] +name = "astroport-whitelist" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44156757bfab3d4bd208d9b86b890d1478f45d07c8f8d3d1c3e3da91081cb54d" +dependencies = [ + "astroport 2.9.3", + "cosmwasm-schema", + "cosmwasm-std", + "cw1-whitelist 0.15.1", + "cw2 0.15.1", + "thiserror", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "base16ct" version = "0.2.0" @@ -189,6 +317,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cosmwasm-storage" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd2b4ae72a03e8f56c85df59d172d51d2d7dc9cec6e2bc811e3fb60c588032a4" +dependencies = [ + "cosmwasm-std", + "serde", +] + [[package]] name = "cpufeatures" version = "0.2.11" @@ -310,6 +448,64 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw1" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe0783ec4210ba4e0cdfed9874802f469c6db0880f742ad427cb950e940b21c" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw1" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd87b521055960569f562a143078c93031d5e8780d9520936cc97f8aa2f1101" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw1-whitelist" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233dd13f61495f1336da57c8bdca0536fa9f8dd59c12d2bbfc59928ea580e478" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "cw1 0.15.1", + "cw2 0.15.1", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw1-whitelist" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c12f6e7859ad758c95e7bcd7ac59419d550b41e4e35bf8aac021070a686abc" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.2", + "cw1 1.1.1", + "cw2 1.1.1", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw2" version = "0.15.1" @@ -363,6 +559,24 @@ dependencies = [ "serde", ] +[[package]] +name = "cw20-base" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0909c56d0c14601fbdc69382189799482799dcad87587926aec1f3aa321abc41" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "cw2 0.15.1", + "cw20 0.15.1", + "schemars", + "semver", + "serde", + "thiserror", +] + [[package]] name = "cw3" version = "1.1.1" @@ -420,6 +634,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "dyn-clone" version = "1.0.16" @@ -553,6 +773,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + [[package]] name = "itertools" version = "0.10.5" @@ -601,18 +830,35 @@ checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" name = "lq-express-sm" version = "0.1.0" dependencies = [ - "astroport", + "astroport 3.6.1", + "astroport-factory", + "astroport-native-coin-registry", + "astroport-pair", + "astroport-token", + "astroport-types", + "astroport-whitelist", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", "cw-storage-plus 1.2.0", "cw-utils 1.0.2", + "cw1-whitelist 1.1.1", "cw2 1.1.1", + "cw20 1.1.1", "schemars", "serde", "thiserror", ] +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -661,12 +907,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "065717a5dfaca4a83d2fe57db3487b311365200000551d7a364e715dbf4346bc" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.11.0", "proc-macro2", "quote", "syn 2.0.39", ] +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" +dependencies = [ + "bytes", +] + [[package]] name = "quote" version = "1.0.33" @@ -836,6 +1091,27 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "snafu" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "spki" version = "0.7.2" diff --git a/Cargo.toml b/Cargo.toml index 85df6cc..f1bba3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,12 @@ optimize = """docker run --rm -v "$(pwd)":/code \ [dependencies] astroport = "3.6.1" +astroport-factory = "1.5.1" +astroport-native-coin-registry = "1.0.1" +astroport-pair = "1.5.0" +astroport-token = "1.1.1" +astroport-types = "0.1.3" +astroport-whitelist = "1.0.1" cosmwasm-schema = "1.5.0" cosmwasm-std = { version = "1.2.7", features = [ "abort", @@ -50,7 +56,9 @@ cosmwasm-std = { version = "1.2.7", features = [ cw-storage-plus = "1.1.0" cw-utils = "1.0.2" +cw1-whitelist = "1.1.1" cw2 = "1.1.1" +cw20 = "1.1.1" schemars = "0.8.15" serde = { version = "1.0.189", default-features = false, features = ["derive"] } thiserror = { version = "1.0.49" } diff --git a/src/contract.rs b/src/contract.rs index 45da70c..8cf53bb 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -1,29 +1,28 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - coins, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, + coins, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, }; use cw2::set_contract_version; use crate::error::ContractError; -use crate::msg::{ExecuteMsg, GetCountResponse, InstantiateMsg, QueryMsg}; +use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{State, STATE}; - use astroport::pair; +use cosmwasm_std::StdError; use cosmwasm_std::WasmMsg; // version info for migration info const CONTRACT_NAME: &str = "crates.io:lq-express-sm"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - +const SWAP_REPLY_ID: u64 = 2u64; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, _env: Env, info: MessageInfo, - msg: InstantiateMsg, + _msg: InstantiateMsg, ) -> Result { let state = State { - count: msg.count, owner: info.sender.clone(), }; set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; @@ -31,8 +30,7 @@ pub fn instantiate( Ok(Response::new() .add_attribute("method", "instantiate") - .add_attribute("owner", info.sender) - .add_attribute("count", msg.count.to_string())) + .add_attribute("owner", info.sender)) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -43,59 +41,86 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::Increment {} => execute::increment(deps), - ExecuteMsg::Reset { count } => execute::reset(deps, info, count), - ExecuteMsg::Astro {} => execute::astro_exec(deps, info), + ExecuteMsg::Astro { pair_address } => execute::astro_exec(deps, info, pair_address), } } +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> StdResult { + match msg.id { + SWAP_REPLY_ID => handle_swap_reply(deps, _env, msg), + id => Err(StdError::generic_err(format!("Unknown reply id: {}", id))), + } +} +fn handle_swap_reply(_deps: DepsMut, _env: Env, msg: Reply) -> StdResult { + let data = msg.result.into_result().map_err(StdError::generic_err)?; + + // Search for the transfer event + // If there are multiple transfers, you will need to find the right event to handle + let swap_event = data + .events + .iter() + .find(|e| { + e.attributes + .iter() + .any(|attr| attr.key == "action" && attr.value == "swap") + && e.attributes.iter().any(|attr| attr.key == "return_amount") + }) + .ok_or_else(|| StdError::generic_err("unable to find swap action".to_string()))?; + + let _ = swap_event + .attributes + .iter() + .find(|e| e.key == "return_amount") + .ok_or_else(|| StdError::generic_err("unable to find coin spent event".to_string()))?; + + // let spender_address = coin_spent_event + // .attributes + // .iter() + // .find(|a| a.key == "spender") + // .unwrap() + // .value + // .clone(); + // let coin = Coin::from_str(&spend_amount).unwrap(); + // // transfer back to user + // let msg = BankMsg::Send { + // to_address: spender_address, + // amount: vec![coin], + // }; + Ok(Response::new()) +} pub mod execute { use astroport::asset::Asset; - use cosmwasm_std::{Coin, Decimal}; + use cosmwasm_std::{Decimal, SubMsg}; use super::*; - pub fn increment(deps: DepsMut) -> Result { - STATE.update(deps.storage, |mut state| -> Result<_, ContractError> { - state.count += 1; - Ok(state) - })?; - - Ok(Response::new().add_attribute("action", "increment")) - } - - pub fn reset(deps: DepsMut, info: MessageInfo, count: i32) -> Result { - STATE.update(deps.storage, |mut state| -> Result<_, ContractError> { - if info.sender != state.owner { - return Err(ContractError::Unauthorized {}); - } - state.count = count; - Ok(state) - })?; - Ok(Response::new().add_attribute("action", "reset")) - } - pub fn astro_exec(deps: DepsMut, info: MessageInfo) -> Result { - let inj_amount = cw_utils::must_pay(&info, "inj")?.u128(); + pub fn astro_exec( + _deps: DepsMut, + info: MessageInfo, + pair_address: String, + ) -> Result { + let inj_amount = cw_utils::must_pay(&info, "inj").unwrap().u128(); // Pair of hINJ-INJ on testnet - let pair = "inj1relf3d6yh2e6arv84gaajawskttdq3vptn8qrs"; let swap_astro_msg = pair::ExecuteMsg::Swap { offer_asset: Asset::native("inj", inj_amount), ask_asset_info: None, belief_price: None, max_spread: Some(Decimal::percent(50)), - to: None, + to: Some(info.sender.to_string()), }; let exec_cw20_mint_msg = WasmMsg::Execute { - contract_addr: pair.into(), + contract_addr: pair_address.clone(), msg: to_json_binary(&swap_astro_msg)?, funds: coins(inj_amount, "inj"), }; + let submessage = SubMsg::reply_on_success(exec_cw20_mint_msg, SWAP_REPLY_ID); let res = Response::new() - .add_message(exec_cw20_mint_msg) + .add_submessage(submessage) .add_attribute("action", "swap") - .add_attribute("pair", pair) + .add_attribute("pair", pair_address) .add_attribute("offer_asset", "hinj") .add_attribute("ask_asset_info", "inj"); Ok(res) @@ -103,92 +128,32 @@ pub mod execute { } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::GetCount {} => to_json_binary(&query::count(deps)?), - } +pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg {} } -pub mod query { - use super::*; - - pub fn count(deps: Deps) -> StdResult { - let state = STATE.load(deps.storage)?; - Ok(GetCountResponse { count: state.count }) - } -} +pub mod query {} #[cfg(test)] mod tests { use super::*; + use cosmwasm_std::coins; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cosmwasm_std::Addr; - use cosmwasm_std::{coins, from_json}; use cw_multi_test::{App, ContractWrapper, Executor}; #[test] fn proper_initialization() { let mut deps = mock_dependencies(); - let msg = InstantiateMsg { count: 17 }; + let msg = InstantiateMsg {}; let info = mock_info("creator", &coins(1000, "earth")); // we can just call .unwrap() to assert this was a success let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!(0, res.messages.len()); - - // it worked, let's query the state - let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap(); - let value: GetCountResponse = from_json(&res).unwrap(); - assert_eq!(17, value.count); - } - - #[test] - fn increment() { - let mut deps = mock_dependencies(); - - let msg = InstantiateMsg { count: 17 }; - let info = mock_info("creator", &coins(2, "token")); - let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); - - // beneficiary can release it - let info = mock_info("anyone", &coins(2, "token")); - let msg = ExecuteMsg::Increment {}; - let _res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); - - // should increase counter by 1 - let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap(); - let value: GetCountResponse = from_json(&res).unwrap(); - assert_eq!(18, value.count); } - #[test] - fn reset() { - let mut deps = mock_dependencies(); - - let msg = InstantiateMsg { count: 17 }; - let info = mock_info("creator", &coins(2, "token")); - let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); - - // beneficiary can release it - let unauth_info = mock_info("anyone", &coins(2, "token")); - let msg = ExecuteMsg::Reset { count: 5 }; - let res = execute(deps.as_mut(), mock_env(), unauth_info, msg); - match res { - Err(ContractError::Unauthorized {}) => {} - _ => panic!("Must return unauthorized error"), - } - - // only the original creator can reset the counter - let auth_info = mock_info("creator", &coins(2, "token")); - let msg = ExecuteMsg::Reset { count: 5 }; - let _res = execute(deps.as_mut(), mock_env(), auth_info, msg).unwrap(); - - // should now be 5 - let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap(); - let value: GetCountResponse = from_json(&res).unwrap(); - assert_eq!(5, value.count); - } #[test] fn astro_test() { let mut app = App::new(|router, _, storage| { @@ -203,17 +168,19 @@ mod tests { .instantiate_contract( code_id, Addr::unchecked("user"), - &InstantiateMsg { count: 17 }, + &InstantiateMsg {}, &[], "Contract", None, ) .unwrap(); - let resp = app + let _ = app .execute_contract( Addr::unchecked("user"), addr, - &ExecuteMsg::Astro {}, + &ExecuteMsg::Astro { + pair_address: "pair".to_string(), + }, &coins(10, "uinj"), ) .unwrap(); diff --git a/src/helpers.rs b/src/helpers.rs index 6d988cc..a31775c 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,23 +1,23 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{ - to_json_binary, Addr, CosmosMsg, CustomQuery, Querier, QuerierWrapper, StdResult, WasmMsg, - WasmQuery, -}; +use cosmwasm_std::{to_json_binary, Addr, CosmosMsg, StdResult, WasmMsg}; -use crate::msg::{ExecuteMsg, GetCountResponse, QueryMsg}; +use crate::msg::ExecuteMsg; /// CwTemplateContract is a wrapper around Addr that provides a lot of helpers /// for working with this. +type CodeId = u64; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct CwTemplateContract(pub Addr); +pub struct CwTemplateContract(pub Addr, pub CodeId); impl CwTemplateContract { pub fn addr(&self) -> Addr { self.0.clone() } - + pub fn code_id(&self) -> u64 { + self.1 + } pub fn call>(&self, msg: T) -> StdResult { let msg = to_json_binary(&msg.into())?; Ok(WasmMsg::Execute { @@ -27,21 +27,4 @@ impl CwTemplateContract { } .into()) } - - /// Get Count - pub fn count(&self, querier: &Q) -> StdResult - where - Q: Querier, - T: Into, - CQ: CustomQuery, - { - let msg = QueryMsg::GetCount {}; - let query = WasmQuery::Smart { - contract_addr: self.addr().into(), - msg: to_json_binary(&msg)?, - } - .into(); - let res: GetCountResponse = QuerierWrapper::::new(querier).query(&query)?; - Ok(res) - } } diff --git a/src/integration_tests.rs b/src/integration_tests.rs index 0c2f454..e51d02c 100644 --- a/src/integration_tests.rs +++ b/src/integration_tests.rs @@ -1,13 +1,16 @@ +#[allow(clippy::inconsistent_digit_grouping, unused_variables)] #[cfg(test)] mod tests { - use std::marker::PhantomData; - use crate::helpers::CwTemplateContract; use crate::msg::InstantiateMsg; - use cosmwasm_std::{ - testing::{MockApi, MockQuerier, MockStorage}, - Addr, Coin, Empty, OwnedDeps, Querier, Uint128, + use astroport::asset::{Asset, AssetInfo, PairInfo}; + use astroport::factory::{InstantiateMsg as FactoryInitMsg, PairConfig, PairType}; + use astroport::pair::{ + ExecuteMsg as PairExecuteMsg, InstantiateMsg as PairInitMsg, QueryMsg as PairQueryMsg, + XYKPoolUpdateParams, }; + use astroport::token::{Cw20Coin, InstantiateMsg as TokenInitMsg, MinterResponse}; + use cosmwasm_std::{to_json_binary, Addr, Coin, Decimal, Empty, Uint128}; use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper, Executor}; pub fn contract_template() -> Box> { @@ -19,9 +22,66 @@ mod tests { Box::new(contract) } - const USER: &str = "USER"; - const ADMIN: &str = "ADMIN"; - const NATIVE_DENOM: &str = "denom"; + pub fn pair_contract() -> Box> { + let contract = ContractWrapper::new( + astroport_pair::contract::execute, + astroport_pair::contract::instantiate, + astroport_pair::contract::query, + ) + .with_reply_empty(astroport_pair::contract::reply); + Box::new(contract) + } + + pub fn token_contract() -> Box> { + let contract = ContractWrapper::new_with_empty( + astroport_token::contract::execute, + astroport_token::contract::instantiate, + astroport_token::contract::query, + ); + Box::new(contract) + } + + pub fn factory_contract() -> Box> { + let contract = ContractWrapper::new( + astroport_factory::contract::execute, + astroport_factory::contract::instantiate, + astroport_factory::contract::query, + ) + .with_reply_empty(astroport_factory::contract::reply); + Box::new(contract) + } + + pub fn whitelist_contract() -> Box> { + let contract = ContractWrapper::new( + astroport_whitelist::contract::execute, + astroport_whitelist::contract::instantiate, + astroport_whitelist::contract::query, + ); + Box::new(contract) + } + + pub fn registry_contract() -> Box> { + let contract = ContractWrapper::new( + astroport_native_coin_registry::contract::execute, + astroport_native_coin_registry::contract::instantiate, + astroport_native_coin_registry::contract::query, + ); + Box::new(contract) + } + + pub fn my_contract() -> Box> { + let contract = ContractWrapper::new( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ) + .with_reply_empty(crate::contract::reply); + Box::new(contract) + } + const USER: &str = "user"; + const ADMIN: &str = "admin"; + const NATIVE_DENOM: &str = "inj"; + const CW20_TEST_TOKEN: &str = "ttt"; fn mock_app() -> App { AppBuilder::new().build(|router, _, storage| { @@ -29,20 +89,205 @@ mod tests { .bank .init_balance( storage, - &Addr::unchecked(USER), - vec![Coin { - denom: NATIVE_DENOM.to_string(), - amount: Uint128::new(1), - }], + &Addr::unchecked(ADMIN), + vec![ + Coin { + denom: NATIVE_DENOM.to_string(), + amount: Uint128::new(10_000_000_000000 + 1_000000), + }, + Coin { + denom: "ttt".to_string(), + amount: Uint128::new(10_000_000_000_000), + }, + ], ) .unwrap(); }) } + fn instantiate_token(app: &mut App) -> CwTemplateContract { + let token_id = app.store_code(token_contract()); + let msg = TokenInitMsg { + name: "ttt".to_string(), + symbol: CW20_TEST_TOKEN.to_string(), + decimals: 18, + initial_balances: vec![Cw20Coin { + address: Addr::unchecked(ADMIN).to_string(), + amount: Uint128::from(1_000_000_000_000u128), + }], + mint: Some(MinterResponse { + minter: String::from(ADMIN), + cap: None, + }), + marketing: None, + }; + let token_contract_addr = app + .instantiate_contract( + token_id, + Addr::unchecked(ADMIN), + &msg, + &[], + "Test Token Contract", + None, + ) + .unwrap(); + + CwTemplateContract(token_contract_addr, token_id) + } + fn instantiate_registry(app: &mut App) -> CwTemplateContract { + let registry_id = app.store_code(registry_contract()); + let msg = astroport::native_coin_registry::InstantiateMsg { + owner: ADMIN.to_string(), + }; + let registry_contract_addr = app + .instantiate_contract( + registry_id, + Addr::unchecked(ADMIN), + &msg, + &[], + "Registry", + Some(ADMIN.to_string()), + ) + .unwrap(); + + CwTemplateContract(registry_contract_addr, registry_id) + } + fn instantiate_factory( + app: &mut App, + token_contract: &CwTemplateContract, + registry_contract: &CwTemplateContract, + ) -> CwTemplateContract { + let factory_id = app.store_code(factory_contract()); + let pair_id = app.store_code(pair_contract()); + let whitelist_id = app.store_code(whitelist_contract()); + let msg = FactoryInitMsg { + pair_configs: vec![PairConfig { + code_id: pair_id, + pair_type: PairType::Xyk {}, + total_fee_bps: 30, + maker_fee_bps: 3333, + is_disabled: false, + is_generator_disabled: false, + }], + token_code_id: token_contract.code_id(), + fee_address: None, + generator_address: None, + owner: Addr::unchecked(ADMIN).to_string(), + whitelist_code_id: whitelist_id, + coin_registry_address: registry_contract.addr().to_string(), + }; + let factory_contract_addr = app + .instantiate_contract( + factory_id, + Addr::unchecked(ADMIN), + &msg, + &[], + "Factory contract", + None, + ) + .unwrap(); + CwTemplateContract(factory_contract_addr, factory_id) + } + fn instantiate_pair( + app: &mut App, + token_contract: &CwTemplateContract, + factory_contract: &CwTemplateContract, + ) -> CwTemplateContract { + let pair_id = app.store_code(pair_contract()); + let msg = PairInitMsg { + asset_infos: [ + AssetInfo::NativeToken { + denom: "ttt".to_string(), + }, + AssetInfo::NativeToken { + denom: NATIVE_DENOM.to_string(), + }, + ] + .to_vec(), + token_code_id: token_contract.code_id(), + factory_addr: factory_contract.addr().to_string(), + init_params: None, + }; + let pair_contract_addr = app + .instantiate_contract( + pair_id, + Addr::unchecked(ADMIN), + &msg, + &[], + "Pair contract", + None, + ) + .unwrap(); + CwTemplateContract(pair_contract_addr, pair_id) + } + fn instantiate_my_contract(app: &mut App) -> CwTemplateContract { + let my_contract_id = app.store_code(my_contract()); + let msg = InstantiateMsg {}; + let my_contract_addr = app + .instantiate_contract( + my_contract_id, + Addr::unchecked(ADMIN), + &msg, + &[], + "My Contract", + Some(ADMIN.to_string()), + ) + .unwrap(); + CwTemplateContract(my_contract_addr, my_contract_id) + } + fn instantiate_contracts( + app: &mut App, + ) -> (CwTemplateContract, CwTemplateContract, CwTemplateContract) { + let token_contract = instantiate_token(app); + let registry_contract = instantiate_registry(app); + let factory_contract = instantiate_factory(app, &token_contract, ®istry_contract); + let pair_contract = instantiate_pair(app, &token_contract, &factory_contract); + (token_contract, factory_contract, pair_contract) + } + fn provide_liquidity_msg( + ttt_amount: Uint128, + inj_amount: Uint128, + receiver: Option, + slippage_tolerance: Option, + token_contract: &CwTemplateContract, + ) -> (PairExecuteMsg, [Coin; 2]) { + let msg = PairExecuteMsg::ProvideLiquidity { + assets: vec![ + Asset { + info: AssetInfo::NativeToken { + denom: "ttt".to_string(), + }, + amount: ttt_amount, + }, + Asset { + info: AssetInfo::NativeToken { + denom: "inj".to_string(), + }, + amount: inj_amount, + }, + ], + slippage_tolerance, + auto_stake: None, + receiver, + }; + + let coins = [ + Coin { + denom: "inj".to_string(), + amount: inj_amount, + }, + Coin { + denom: "ttt".to_string(), + amount: ttt_amount, + }, + ]; + + (msg, coins) + } fn proper_instantiate() -> (App, CwTemplateContract) { let mut app = mock_app(); let cw_template_id = app.store_code(contract_template()); - - let msg = InstantiateMsg { count: 1i32 }; + let pair_id = app.store_code(pair_contract()); + let msg = InstantiateMsg {}; let cw_template_contract_addr = app .instantiate_contract( cw_template_id, @@ -54,8 +299,218 @@ mod tests { ) .unwrap(); - let cw_template_contract = CwTemplateContract(cw_template_contract_addr); - + let cw_template_contract = CwTemplateContract(cw_template_contract_addr, cw_template_id); (app, cw_template_contract) } + #[allow(unused_variables, clippy::inconsistent_digit_grouping)] + #[test] + fn instantiate_and_query() { + let owner = Addr::unchecked(ADMIN); + let user = Addr::unchecked(USER); + let (mut app, cw_contract) = proper_instantiate(); + let (token_contract, factory_contract, pair_contract) = instantiate_contracts(&mut app); + let my_contract = instantiate_my_contract(&mut app); + let inj_amount = Uint128::new(1_000_000_000000); + let ttt_amount = Uint128::new(1_000_000_000000); + let inj_offer = Uint128::new(1_000000); + + let (msg, coins) = + provide_liquidity_msg(ttt_amount, inj_amount, None, None, &token_contract); + + app.execute_contract(owner.clone(), pair_contract.addr(), &msg, &coins) + .unwrap(); + let msg = PairExecuteMsg::UpdateConfig { + params: to_json_binary(&XYKPoolUpdateParams::EnableAssetBalancesTracking).unwrap(), + }; + app.execute_contract(owner.clone(), pair_contract.addr(), &msg, &[]) + .unwrap(); + + app.update_block(|b| b.height += 1); + + assert_eq!( + app.wrap().query_balance(owner.clone(), "inj").unwrap(), + Coin { + amount: Uint128::from(9000001000000u128), + denom: "inj".to_string() + } + ); + assert_eq!( + app.wrap() + .query_wasm_smart::>(pair_contract.addr(), &PairQueryMsg::Pair {}) + .unwrap(), + Some(PairInfo { + asset_infos: vec![ + AssetInfo::NativeToken { + denom: "ttt".to_string() + }, + AssetInfo::NativeToken { + denom: "inj".to_string() + } + ], + contract_addr: Addr::unchecked("contract4"), + liquidity_token: Addr::unchecked("contract5"), + pair_type: PairType::Xyk {} + }) + ); + let res: Option = app + .wrap() + .query_wasm_smart( + pair_contract.addr(), + &PairQueryMsg::AssetBalanceAt { + asset_info: AssetInfo::NativeToken { + denom: "inj".to_owned(), + }, + block_height: app.block_info().height.into(), + }, + ) + .unwrap(); + assert_eq!(res.unwrap(), Uint128::new(1_000_000_000000)); + + let res: Option = app + .wrap() + .query_wasm_smart( + pair_contract.addr(), + &PairQueryMsg::AssetBalanceAt { + asset_info: AssetInfo::NativeToken { + denom: "ttt".to_owned(), + }, + block_height: app.block_info().height.into(), + }, + ) + .unwrap(); + assert_eq!(res.unwrap(), Uint128::new(1_000_000_000000)); + let swap_msg = PairExecuteMsg::Swap { + offer_asset: Asset { + info: AssetInfo::NativeToken { + denom: "inj".to_owned(), + }, + amount: Uint128::new(1_000000), + }, + ask_asset_info: None, + belief_price: None, + max_spread: None, + to: None, + }; + let send_funds = vec![Coin { + denom: "inj".to_owned(), + amount: Uint128::new(1_000000), + }]; + app.execute_contract(owner.clone(), pair_contract.addr(), &swap_msg, &send_funds) + .unwrap(); + app.update_block(|b| b.height += 1); + // Check pool balances + let res: Option = app + .wrap() + .query_wasm_smart( + pair_contract.addr(), + &PairQueryMsg::AssetBalanceAt { + asset_info: AssetInfo::NativeToken { + denom: "ttt".to_owned(), + }, + block_height: app.block_info().height.into(), + }, + ) + .unwrap(); + assert_eq!(res.unwrap(), Uint128::new(999999003000)); + // Check pool balances + let res: Option = app + .wrap() + .query_wasm_smart( + pair_contract.addr(), + &PairQueryMsg::AssetBalanceAt { + asset_info: AssetInfo::NativeToken { + denom: "inj".to_owned(), + }, + block_height: app.block_info().height.into(), + }, + ) + .unwrap(); + assert_eq!(res.unwrap(), Uint128::new(1000001000000)); + + let my_swap_msg = crate::msg::ExecuteMsg::Astro { + pair_address: pair_contract.addr().into_string(), + }; + let send_funds = vec![Coin { + denom: "inj".to_owned(), + amount: Uint128::new(1_000000), + }]; + app.execute_contract(owner.clone(), my_contract.addr(), &my_swap_msg, &send_funds) + .unwrap(); + app.update_block(|b| b.height += 1); + // Check pool balances + let res: Option = app + .wrap() + .query_wasm_smart( + pair_contract.addr(), + &PairQueryMsg::AssetBalanceAt { + asset_info: AssetInfo::NativeToken { + denom: "ttt".to_owned(), + }, + block_height: app.block_info().height.into(), + }, + ) + .unwrap(); + assert_eq!(res.unwrap(), Uint128::new(999998006002)); + + // let another user do swap + + // Distribute token from admin to user + app.send_tokens( + owner.clone(), + user.clone(), + &[Coin { + denom: "inj".to_string(), + amount: Uint128::from(1_000000u128), + }], + ) + .unwrap(); + app.update_block(|b| b.height += 1); + assert_eq!( + app.wrap() + .query_balance(user.clone(), "inj") + .unwrap() + .amount, + Uint128::from(1_000000u128) + ); + let my_swap_msg = crate::msg::ExecuteMsg::Astro { + pair_address: pair_contract.addr().into_string(), + }; + let send_funds = vec![Coin { + denom: "inj".to_owned(), + amount: Uint128::new(1_000000), + }]; + app.execute_contract(user.clone(), my_contract.addr(), &my_swap_msg, &send_funds) + .unwrap(); + app.update_block(|b| b.height += 1); + let res: Option = app + .wrap() + .query_wasm_smart( + pair_contract.addr(), + &PairQueryMsg::AssetBalanceAt { + asset_info: AssetInfo::NativeToken { + denom: "ttt".to_owned(), + }, + block_height: app.block_info().height.into(), + }, + ) + .unwrap(); + assert_eq!(res.unwrap(), Uint128::new(999997009006)); + + // Check current balance of user + assert_eq!( + app.wrap() + .query_balance(user.clone(), "inj") + .unwrap() + .amount, + Uint128::from(0u128) + ); + // Check if user receive token from pool + assert!( + app.wrap() + .query_balance(user.clone(), "ttt") + .unwrap() + .amount + > 0u128.into() + ) + } } diff --git a/src/mock_querier.rs b/src/mock_querier.rs new file mode 100644 index 0000000..8f13c57 --- /dev/null +++ b/src/mock_querier.rs @@ -0,0 +1,175 @@ +use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; +use cosmwasm_std::{ + from_binary, from_slice, to_binary, Addr, Coin, Empty, OwnedDeps, Querier, QuerierResult, + QueryRequest, SystemError, SystemResult, Uint128, WasmQuery, +}; +use std::collections::HashMap; + +use astroport::factory::FeeInfoResponse; +use astroport::factory::QueryMsg::FeeInfo; +use cw20::{BalanceResponse, Cw20QueryMsg, TokenInfoResponse}; + +/// mock_dependencies is a drop-in replacement for cosmwasm_std::testing::mock_dependencies. +/// This uses the Astroport CustomQuerier. +pub fn mock_dependencies( + contract_balance: &[Coin], +) -> OwnedDeps { + let custom_querier: WasmMockQuerier = + WasmMockQuerier::new(MockQuerier::new(&[(MOCK_CONTRACT_ADDR, contract_balance)])); + + OwnedDeps { + storage: MockStorage::default(), + api: MockApi::default(), + querier: custom_querier, + custom_query_type: Default::default(), + } +} + +pub struct WasmMockQuerier { + base: MockQuerier, + token_querier: TokenQuerier, +} + +#[derive(Clone, Default)] +pub struct TokenQuerier { + // This lets us iterate over all pairs that match the first string + balances: HashMap>, +} + +impl TokenQuerier { + pub fn new(balances: &[(&String, &[(&String, &Uint128)])]) -> Self { + TokenQuerier { + balances: balances_to_map(balances), + } + } +} + +pub(crate) fn balances_to_map( + balances: &[(&String, &[(&String, &Uint128)])], +) -> HashMap> { + let mut balances_map: HashMap> = HashMap::new(); + for (contract_addr, balances) in balances.iter() { + let mut contract_balances_map: HashMap = HashMap::new(); + for (addr, balance) in balances.iter() { + contract_balances_map.insert(addr.to_string(), **balance); + } + + balances_map.insert(contract_addr.to_string(), contract_balances_map); + } + balances_map +} + +impl Querier for WasmMockQuerier { + fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { + // MockQuerier doesn't support Custom, so we ignore it completely + let request: QueryRequest = match from_slice(bin_request) { + Ok(v) => v, + Err(e) => { + return SystemResult::Err(SystemError::InvalidRequest { + error: format!("Parsing query request: {}", e), + request: bin_request.into(), + }) + } + }; + self.handle_query(&request) + } +} + +impl WasmMockQuerier { + pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { + match &request { + QueryRequest::Wasm(WasmQuery::Smart { contract_addr, msg }) => { + if contract_addr == "factory" { + match from_binary(&msg).unwrap() { + FeeInfo { .. } => SystemResult::Ok( + to_binary(&FeeInfoResponse { + fee_address: Some(Addr::unchecked("fee_address")), + total_fee_bps: 30, + maker_fee_bps: 1660, + }) + .into(), + ), + _ => panic!("DO NOT ENTER HERE"), + } + } else { + match from_binary(&msg).unwrap() { + Cw20QueryMsg::TokenInfo {} => { + let balances: &HashMap = + match self.token_querier.balances.get(contract_addr) { + Some(balances) => balances, + None => { + return SystemResult::Err(SystemError::Unknown {}); + } + }; + + let mut total_supply = Uint128::zero(); + + for balance in balances { + total_supply += *balance.1; + } + + SystemResult::Ok( + to_binary(&TokenInfoResponse { + name: "mAPPL".to_string(), + symbol: "mAPPL".to_string(), + decimals: 6, + total_supply: total_supply, + }) + .into(), + ) + } + Cw20QueryMsg::Balance { address } => { + let balances: &HashMap = + match self.token_querier.balances.get(contract_addr) { + Some(balances) => balances, + None => { + return SystemResult::Err(SystemError::Unknown {}); + } + }; + + let balance = match balances.get(&address) { + Some(v) => v, + None => { + return SystemResult::Err(SystemError::Unknown {}); + } + }; + + SystemResult::Ok( + to_binary(&BalanceResponse { balance: *balance }).into(), + ) + } + _ => panic!("DO NOT ENTER HERE"), + } + } + } + QueryRequest::Wasm(WasmQuery::Raw { contract_addr, .. }) => { + if contract_addr == "factory" { + SystemResult::Ok(to_binary(&Vec::::new()).into()) + } else { + panic!("DO NOT ENTER HERE"); + } + } + _ => self.base.handle_query(request), + } + } +} + +impl WasmMockQuerier { + pub fn new(base: MockQuerier) -> Self { + WasmMockQuerier { + base, + token_querier: TokenQuerier::default(), + } + } + + // Configure the mint whitelist mock querier + pub fn with_token_balances(&mut self, balances: &[(&String, &[(&String, &Uint128)])]) { + self.token_querier = TokenQuerier::new(balances); + } + + pub fn with_balance(&mut self, balances: &[(&String, &[Coin])]) { + for (addr, balance) in balances { + self.base.update_balance(addr.to_string(), balance.to_vec()); + } + } +} diff --git a/src/msg.rs b/src/msg.rs index 7256d90..8603f8e 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -1,27 +1,23 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; #[cw_serde] -pub struct InstantiateMsg { - pub count: i32, -} +pub struct InstantiateMsg {} #[cw_serde] pub enum ExecuteMsg { - Increment {}, - Reset { count: i32 }, - Astro {}, + Astro { pair_address: String }, } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { // GetCount returns the current count as a json-encoded number - #[returns(GetCountResponse)] - GetCount {}, + // #[returns(GetCountResponse)] + // GetCount {}, } // We define a custom struct for each query response -#[cw_serde] -pub struct GetCountResponse { - pub count: i32, -} +// #[cw_serde] +// pub struct GetCountResponse { +// pub count: i32, +// } diff --git a/src/state.rs b/src/state.rs index bad9202..48cf02f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -6,7 +6,6 @@ use cw_storage_plus::Item; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] pub struct State { - pub count: i32, pub owner: Addr, }