diff --git a/Cargo.lock b/Cargo.lock index 7b72e98d5..02707ec28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -726,13 +726,17 @@ dependencies = [ name = "cw-abc" version = "0.0.1" dependencies = [ + "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-address-like", + "cw-multi-test", "cw-ownable", + "cw-paginate-storage 2.3.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", + "getrandom", "integer-cbrt", "integer-sqrt", "rust_decimal", diff --git a/contracts/external/cw-abc/Cargo.toml b/contracts/external/cw-abc/Cargo.toml index d5fdc2b5a..bd2be85fd 100644 --- a/contracts/external/cw-abc/Cargo.toml +++ b/contracts/external/cw-abc/Cargo.toml @@ -20,7 +20,7 @@ crate-type = ["cdylib", "rlib"] backtraces = ["cosmwasm-std/backtraces"] # use library feature to disable all instantiate/execute/query exports library = [] -boot = ["dep:cw-orch"] +# interface = ["dep:cw-orch"] # Adds the dependency when the feature is enabled [dependencies] cw-utils = { workspace = true } @@ -35,18 +35,17 @@ rust_decimal = "1.14.3" integer-sqrt = "0.1.5" integer-cbrt = "0.1.2" getrandom = { version = "0.2", features = ["js"] } -# TODO publish this +# TODO publish this and move to workspace token-bindings = { git = "https://github.com/CosmosContracts/token-bindings", rev = "1412b94" } cw-ownable = { workspace = true } cw-paginate-storage = { workspace = true } -cw-orch = { version = "0.13.3", optional = true, git = "https://github.com/AbstractSDK/cw-orchestrator", branch = "main" } +# cw-orch = { version = "0.13.3", optional = true } [dev-dependencies] # TODO move to workspace speculoos = "0.11.0" -#cw-multi-test = { version = "0.16.0" } anyhow = { workspace = true } -cw-abc = { path = ".", features = ["boot"] } +cw-multi-test = { workspace = true } [profile.release] rpath = false diff --git a/contracts/external/cw-abc/src/boot.rs b/contracts/external/cw-abc/src/boot.rs deleted file mode 100644 index ad2309b0b..000000000 --- a/contracts/external/cw-abc/src/boot.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::msg::*; -use cosmwasm_std::Empty; -use cw_orch::{contract, Contract, CwEnv}; -#[cfg(feature = "daemon")] -use cw_orch::{ArtifactsDir, Daemon, WasmPath}; -use cw_orch::{ContractWrapper, Mock, MockState, TxHandler, Uploadable}; -use token_bindings::{TokenFactoryMsg, TokenFactoryQuery}; - -#[contract(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)] -pub struct CwAbc; - -impl CwAbc { - pub fn new(name: &str, chain: Chain) -> Self { - let contract = Contract::new(name, chain); - Self(contract) - } -} - -/// Basic app for the token factory contract -/// TODO: should be in the bindings, along with custom handler for multi-test -pub(crate) type TokenFactoryBasicApp = cw_orch::BasicApp; - -type TokenFactoryMock = Mock; - -impl Uploadable for CwAbc { - fn source(&self) -> ::ContractSource { - Box::new(ContractWrapper::new( - crate::contract::execute, - crate::contract::instantiate, - crate::contract::query, - )) - } -} - -#[cfg(feature = "daemon")] -impl Uploadable for CwAbc { - fn source(&self) -> ::ContractSource { - ArtifactsDir::env() - .expect("Expected ARTIFACTS_DIR in env") - .find_wasm_path("cw_abc") - .unwrap() - } -} diff --git a/contracts/external/cw-abc/src/contract.rs b/contracts/external/cw-abc/src/contract.rs index c36470c33..f21a9cd33 100644 --- a/contracts/external/cw-abc/src/contract.rs +++ b/contracts/external/cw-abc/src/contract.rs @@ -6,18 +6,18 @@ use std::collections::HashSet; use token_bindings::{TokenFactoryMsg, TokenFactoryQuery, TokenMsg}; -use crate::abc::CurveFn; +use crate::abc::{CommonsPhase, CurveFn}; use crate::curves::DecimalPlaces; use crate::error::ContractError; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg, UpdatePhaseConfigMsg}; use crate::state::{ - CurveState, CURVE_STATE, CURVE_TYPE, HATCHER_ALLOWLIST, PHASE_CONFIG, SUPPLY_DENOM, + CurveState, CURVE_STATE, CURVE_TYPE, HATCHER_ALLOWLIST, PHASE, PHASE_CONFIG, SUPPLY_DENOM, }; use crate::{commands, queries}; use cw_utils::nonpayable; // version info for migration info -pub(crate) const CONTRACT_NAME: &str = "crates.io:cw20-abc"; +pub(crate) const CONTRACT_NAME: &str = "crates.io:cw-abc"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); // By default, the prefix for token factory tokens is "factory" @@ -86,6 +86,9 @@ pub fn instantiate( PHASE_CONFIG.save(deps.storage, &phase_config)?; + // TODO don't hardcode this? + PHASE.save(deps.storage, &CommonsPhase::Hatch)?; + cw_ownable::initialize_owner(deps.storage, deps.api, Some(info.sender.as_str()))?; Ok(Response::default().add_message(create_supply_denom_msg)) diff --git a/contracts/external/cw-abc/src/integration.rs b/contracts/external/cw-abc/src/integration.rs index 767dbdf77..b7de8d3b4 100644 --- a/contracts/external/cw-abc/src/integration.rs +++ b/contracts/external/cw-abc/src/integration.rs @@ -1,38 +1,198 @@ -use crate::{abc::CurveType, boot::CwAbc}; -use cosmwasm_std::{Addr, Uint128}; -use cw_orch::{CwOrcUpload, Mock}; +use cosmwasm_std::{ + coin, coins, + testing::{MockApi, MockStorage}, + Addr, Api, Coin, Decimal, Empty, GovMsg, IbcMsg, IbcQuery, StdResult, Storage, Uint128, +}; +use cw_multi_test::{ + custom_app, + custom_handler::{CachingCustomHandler, CachingCustomHandlerState}, + App, AppBuilder, AppResponse, BankKeeper, BankSudo, Contract, ContractWrapper, + DistributionKeeper, Executor, FailingModule, Router, StakeKeeper, WasmKeeper, +}; +use cw_utils::PaymentError; +use token_bindings::{Metadata, TokenFactoryMsg, TokenFactoryQuery}; -use crate::testing::prelude::*; +use crate::{ + abc::{ + ClosedConfig, CommonsPhaseConfig, HatchConfig, MinMax, OpenConfig, ReserveToken, + SupplyToken, + }, + msg::{CurveInfoResponse, InstantiateMsg}, + ContractError, +}; -type AResult = anyhow::Result<()>; // alias for Result<(), anyhow::Error> +pub struct Test { + pub app: App< + BankKeeper, + MockApi, + MockStorage, + CachingCustomHandler, + WasmKeeper, + StakeKeeper, + DistributionKeeper, + FailingModule, + FailingModule, + >, + pub addr: Addr, + pub owner: Addr, + pub recipient: Addr, + pub custom_handler_state: CachingCustomHandlerState, +} + +impl Test { + pub fn new() -> Self { + let owner = Addr::unchecked("owner"); + let recipient = Addr::unchecked("recipient"); + + let custom_handler = CachingCustomHandler::::new(); + let custom_handler_state = custom_handler.state(); + + let mut app = AppBuilder::new_custom() + .with_custom(custom_handler) + .build(|_, _, _| {}); + + app.sudo(cw_multi_test::SudoMsg::Bank(BankSudo::Mint { + to_address: owner.to_string(), + amount: vec![coin(10000, "ujuno"), coin(10000, "uatom")], + })) + .unwrap(); + app.sudo(cw_multi_test::SudoMsg::Bank(BankSudo::Mint { + to_address: recipient.to_string(), + amount: vec![coin(10000, "ujuno"), coin(10000, "uatom")], + })) + .unwrap(); + + let code_id = app.store_code(abc_countract()); + let addr = app + .instantiate_contract( + code_id, + owner.clone(), + &InstantiateMsg { + supply: SupplyToken { + subdenom: "subdenom".to_string(), + metadata: Metadata { + description: None, + denom_units: vec![], + base: None, + display: None, + name: None, + symbol: None, + }, + decimals: 6, + }, + reserve: ReserveToken { + denom: "ujuno".to_string(), + decimals: 6, + }, + curve_type: crate::abc::CurveType::Linear { + slope: Uint128::new(1), + scale: 2, + }, + phase_config: CommonsPhaseConfig { + hatch: HatchConfig { + initial_raise: MinMax { + min: Uint128::new(100), + max: Uint128::new(1000), + }, + initial_price: Uint128::new(1), + initial_allocation_ratio: Decimal::percent(10), + exit_tax: Decimal::percent(10), + }, + open: OpenConfig { + allocation_percentage: Decimal::percent(10), + exit_tax: Decimal::percent(10), + }, + closed: ClosedConfig {}, + }, + hatcher_allowlist: None, + }, + &[], + "cw-bounties", + None, + ) + .unwrap(); + Self { + app, + addr, + owner, + recipient, + custom_handler_state, + } + } + + pub fn buy(&mut self, amount: Vec) -> Result { + let msg = crate::msg::ExecuteMsg::Buy {}; + let res = + self.app + .execute_contract(self.owner.clone(), self.addr.clone(), &msg, &amount)?; + Ok(res) + } + + pub fn query_curve_info(&self) -> StdResult { + let msg = crate::msg::QueryMsg::CurveInfo {}; + self.app.wrap().query_wasm_smart(&self.addr, &msg) + } +} + +fn abc_countract() -> Box> { + let contract = ContractWrapper::new( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ); + Box::new(contract) +} -// TODO: we need to make a PR to token factory bindings for the CustomHandler so that messages will actually execute #[test] -fn instantiate() -> AResult { - let sender = Addr::unchecked(TEST_CREATOR); - let chain = Mock::new(&sender)?; - - let abc = CwAbc::new("cw:abc", chain); - abc.upload()?; - - let curve_type = CurveType::SquareRoot { - slope: Uint128::new(1), - scale: 1, - }; - - let _init_msg = default_instantiate_msg(5u8, 5u8, curve_type); - // abc.instantiate(&init_msg, None, None)?; - // - // let expected_config = msg::CurveInfoResponse { - // reserve: Default::default(), - // supply: Default::default(), - // funding: Default::default(), - // spot_price: Default::default(), - // reserve_denom: "".to_string(), - // }; - // - // let actual_config = abc.curve_info()?; - // - // assert_that!(&actual_config).is_equal_to(&expected_config); - Ok(()) +pub fn test_happy_path() { + let mut test = Test::new(); + + // Curve has been initialized + let curve_info = test.query_curve_info().unwrap(); + assert_eq!( + curve_info, + CurveInfoResponse { + reserve: Uint128::zero(), + supply: Uint128::zero(), + funding: Uint128::zero(), + spot_price: Decimal::zero(), + reserve_denom: "ujuno".to_string(), + } + ); + + let balance_before = test + .app + .wrap() + .query_balance(test.owner.clone(), "ujuno") + .unwrap(); + + // Buy some coins + test.buy(coins(100, "ujuno")).unwrap(); + + // Curve has been updated + let curve_info = test.query_curve_info().unwrap(); + assert_eq!( + curve_info, + CurveInfoResponse { + reserve: Uint128::new(90), + supply: Uint128::new(134164), + funding: Uint128::new(10), + // TODO investigate why does this take 8 for decimals? + spot_price: Decimal::from_atomics(Uint128::new(134164), 8).unwrap(), + reserve_denom: "ujuno".to_string(), + } + ); + + // assert balance + let balance_after = test + .app + .wrap() + .query_balance(test.owner.clone(), "ujuno") + .unwrap(); + assert!( + balance_before.amount.u128() == (balance_after.amount.u128() + 100), + "before: {}, after: {}", + balance_before.amount.u128(), + balance_after.amount.u128() + ); } diff --git a/contracts/external/cw-abc/src/lib.rs b/contracts/external/cw-abc/src/lib.rs index 24ac48a7d..599b96043 100644 --- a/contracts/external/cw-abc/src/lib.rs +++ b/contracts/external/cw-abc/src/lib.rs @@ -1,6 +1,4 @@ pub mod abc; -#[cfg(feature = "boot")] -pub mod boot; pub(crate) mod commands; pub mod contract; pub mod curves; diff --git a/contracts/external/cw-abc/src/msg.rs b/contracts/external/cw-abc/src/msg.rs index 92de30f70..89b43239c 100644 --- a/contracts/external/cw-abc/src/msg.rs +++ b/contracts/external/cw-abc/src/msg.rs @@ -41,17 +41,13 @@ pub enum UpdatePhaseConfigMsg { #[cw_ownable::cw_ownable_execute] #[cw_serde] -#[cfg_attr(feature = "boot", derive(cw_orch::ExecuteFns))] pub enum ExecuteMsg { /// Buy will attempt to purchase as many supply tokens as possible. /// You must send only reserve tokens in that message - #[payable] Buy {}, /// Burn is a base message to destroy tokens forever - #[payable] Burn {}, /// Donate will add reserve tokens to the funding pool - #[payable] Donate {}, /// Update the hatch phase allowlist UpdateHatchAllowlist { @@ -66,7 +62,6 @@ pub enum ExecuteMsg { #[cw_ownable::cw_ownable_query] #[cw_serde] #[derive(QueryResponses)] -#[cfg_attr(feature = "boot", derive(cw_orch::QueryFns))] pub enum QueryMsg { /// Returns the reserve and supply quantities, as well as the spot price to buy 1 token /// Returns [`CurveInfoResponse`] @@ -132,3 +127,6 @@ pub struct HatchersResponse { // the hatchers mapped to their contribution in the reserve token pub hatchers: Vec<(Addr, Uint128)>, } + +#[cw_serde] +pub enum MigrateMsg {} diff --git a/contracts/external/cw-abc/src/queries.rs b/contracts/external/cw-abc/src/queries.rs index c6d12117b..94670499f 100644 --- a/contracts/external/cw-abc/src/queries.rs +++ b/contracts/external/cw-abc/src/queries.rs @@ -62,7 +62,7 @@ pub fn query_donations( start_aftor: Option, limit: Option, ) -> StdResult { - let donations = cw_paginate::paginate_map( + let donations = cw_paginate_storage::paginate_map( Deps { storage: deps.storage, api: deps.api, @@ -85,7 +85,7 @@ pub fn query_hatchers( start_aftor: Option, limit: Option, ) -> StdResult { - let hatchers = cw_paginate::paginate_map( + let hatchers = cw_paginate_storage::paginate_map( Deps { storage: deps.storage, api: deps.api,