diff --git a/.codecov.yml b/.codecov.yml index 087a840..a30cd51 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -11,3 +11,6 @@ coverage: patch: default: enabled: no + +ignore: + - "contracts/infinity-builder/.*" diff --git a/contracts/infinity-pair/src/execute.rs b/contracts/infinity-pair/src/execute.rs index 621c62e..042f56e 100644 --- a/contracts/infinity-pair/src/execute.rs +++ b/contracts/infinity-pair/src/execute.rs @@ -6,12 +6,12 @@ use crate::state::{BondingCurve, PairType, TokenId, INFINITY_GLOBAL, NFT_DEPOSIT use cosmwasm_std::{ coin, ensure, ensure_eq, has_coins, Addr, Coin, DepsMut, Env, MessageInfo, Order, StdResult, - Uint128, }; +use cw721::{Cw721QueryMsg, TokensResponse}; use cw_utils::{maybe_addr, must_pay, nonpayable}; use infinity_shared::InfinityError; use sg_marketplace_common::address::address_or; -use sg_marketplace_common::coin::transfer_coin; +use sg_marketplace_common::coin::transfer_coins; use sg_marketplace_common::nft::{only_with_owner_approval, transfer_nft}; use sg_std::Response; @@ -58,34 +58,56 @@ pub fn handle_execute_msg( execute_receive_nft(deps, info, pair, cw721_receive_msg.token_id) }, ExecuteMsg::WithdrawNfts { + collection, token_ids, + asset_recipient, } => { nonpayable(&info)?; only_pair_owner(&info.sender, &pair)?; - execute_withdraw_nfts(deps, info, pair, token_ids) + execute_withdraw_nfts( + deps, + info, + pair, + api.addr_validate(&collection)?, + token_ids, + maybe_addr(api, asset_recipient)?, + ) }, ExecuteMsg::WithdrawAnyNfts { + collection, limit, + asset_recipient, } => { nonpayable(&info)?; only_pair_owner(&info.sender, &pair)?; - execute_withdraw_any_nfts(deps, info, pair, limit) + execute_withdraw_any_nfts( + deps, + env, + info, + pair, + api.addr_validate(&collection)?, + limit, + maybe_addr(api, asset_recipient)?, + ) }, ExecuteMsg::DepositTokens {} => { only_pair_owner(&info.sender, &pair)?; execute_deposit_tokens(deps, info, env, pair) }, ExecuteMsg::WithdrawTokens { - amount, + funds, + asset_recipient, } => { nonpayable(&info)?; only_pair_owner(&info.sender, &pair)?; - execute_withdraw_tokens(deps, info, env, pair, amount) + execute_withdraw_tokens(deps, info, env, pair, funds, maybe_addr(api, asset_recipient)?) }, - ExecuteMsg::WithdrawAllTokens {} => { + ExecuteMsg::WithdrawAllTokens { + asset_recipient, + } => { nonpayable(&info)?; only_pair_owner(&info.sender, &pair)?; - execute_withdraw_all_tokens(deps, info, env, pair) + execute_withdraw_all_tokens(deps, info, env, pair, maybe_addr(api, asset_recipient)?) }, ExecuteMsg::UpdatePairConfig { is_active, @@ -182,7 +204,9 @@ pub fn execute_withdraw_nfts( deps: DepsMut, _info: MessageInfo, mut pair: Pair, + collection: Addr, token_ids: Vec, + asset_recipient: Option, ) -> Result<(Pair, Response), ContractError> { ensure!( !token_ids.is_empty(), @@ -191,17 +215,17 @@ pub fn execute_withdraw_nfts( let mut response = Response::new(); - for token_id in &token_ids { - ensure!( - NFT_DEPOSITS.has(deps.storage, token_id.to_string()), - InfinityError::InvalidInput("token_id is not owned by pair".to_string()) - ); - NFT_DEPOSITS.remove(deps.storage, token_id.to_string()); - - pair.internal.total_nfts -= 1u64; + let asset_recipient = address_or(asset_recipient.as_ref(), &pair.asset_recipient()); - response = - transfer_nft(&pair.immutable.collection, token_id, &pair.asset_recipient(), response); + for token_id in &token_ids { + response = transfer_nft(&collection, token_id, &asset_recipient, response); + + if pair.immutable.collection == collection + && NFT_DEPOSITS.has(deps.storage, token_id.to_string()) + { + pair.internal.total_nfts -= 1u64; + NFT_DEPOSITS.remove(deps.storage, token_id.to_string()); + } } Ok((pair, response)) @@ -209,17 +233,26 @@ pub fn execute_withdraw_nfts( pub fn execute_withdraw_any_nfts( deps: DepsMut, + env: Env, info: MessageInfo, pair: Pair, + collection: Addr, limit: u32, + asset_recipient: Option, ) -> Result<(Pair, Response), ContractError> { - let token_ids = NFT_DEPOSITS - .range(deps.storage, None, None, Order::Ascending) - .take(limit as usize) - .map(|item| item.map(|(v, _)| v)) - .collect::>>()?; - - execute_withdraw_nfts(deps, info, pair, token_ids) + let token_ids = deps + .querier + .query_wasm_smart::( + &collection, + &Cw721QueryMsg::Tokens { + owner: env.contract.address.to_string(), + start_after: None, + limit: Some(limit), + }, + )? + .tokens; + + execute_withdraw_nfts(deps, info, pair, collection, token_ids, asset_recipient) } pub fn execute_deposit_tokens( @@ -237,22 +270,20 @@ pub fn execute_withdraw_tokens( _info: MessageInfo, _env: Env, mut pair: Pair, - amount: Uint128, + funds: Vec, + asset_recipient: Option, ) -> Result<(Pair, Response), ContractError> { - ensure!( - amount <= pair.total_tokens, - InfinityError::InvalidInput("amount exceeds total tokens".to_string()) - ); - - pair.total_tokens -= amount; + for fund in &funds { + if fund.denom == pair.immutable.denom { + pair.total_tokens -= fund.amount; + } + } let mut response = Response::new(); - response = transfer_coin( - coin(amount.u128(), pair.immutable.denom.clone()), - &pair.asset_recipient(), - response, - ); + let asset_recipient = address_or(asset_recipient.as_ref(), &pair.asset_recipient()); + + response = transfer_coins(funds, &asset_recipient, response); Ok((pair, response)) } @@ -262,9 +293,10 @@ pub fn execute_withdraw_all_tokens( info: MessageInfo, env: Env, pair: Pair, + asset_recipient: Option, ) -> Result<(Pair, Response), ContractError> { - let total_tokens = pair.total_tokens; - execute_withdraw_tokens(deps, info, env, pair, total_tokens) + let all_tokens = deps.querier.query_all_balances(&env.contract.address)?; + execute_withdraw_tokens(deps, info, env, pair, all_tokens, asset_recipient) } #[allow(clippy::too_many_arguments)] diff --git a/contracts/infinity-pair/src/msg.rs b/contracts/infinity-pair/src/msg.rs index f762ecd..bfae8f9 100644 --- a/contracts/infinity-pair/src/msg.rs +++ b/contracts/infinity-pair/src/msg.rs @@ -32,20 +32,27 @@ pub enum ExecuteMsg { ReceiveNft(Cw721ReceiveMsg), /// Withdraw NFTs from the pair WithdrawNfts { + collection: String, token_ids: Vec, + asset_recipient: Option, }, /// Withdraw any NFTs, from the pair WithdrawAnyNfts { + collection: String, limit: u32, + asset_recipient: Option, }, /// Deposit tokens into the pair DepositTokens {}, /// Withdraw tokens from the pair WithdrawTokens { - amount: Uint128, + funds: Vec, + asset_recipient: Option, }, /// Withdraw all tokens from the pair - WithdrawAllTokens {}, + WithdrawAllTokens { + asset_recipient: Option, + }, /// Update the parameters of a pair UpdatePairConfig { is_active: Option, @@ -68,11 +75,6 @@ pub enum ExecuteMsg { SwapTokensForAnyNft { asset_recipient: Option, }, - // /// Remove a pair from contract storage and indexing - // RemovePair { - // pair_id: u64, - // asset_recipient: Option, - // }, } #[cw_serde] @@ -84,6 +86,14 @@ pub enum QueryMsg { NftDeposits { query_options: Option>, }, + #[returns(QuotesResponse)] + SellToPairQuotes { + limit: u32, + }, + #[returns(QuotesResponse)] + BuyFromPairQuotes { + limit: u32, + }, } #[cw_serde] @@ -91,3 +101,9 @@ pub struct NftDepositsResponse { pub collection: Addr, pub token_ids: Vec, } + +#[cw_serde] +pub struct QuotesResponse { + pub denom: String, + pub quotes: Vec, +} diff --git a/contracts/infinity-pair/src/query.rs b/contracts/infinity-pair/src/query.rs index ebdfdc7..a99dd6a 100644 --- a/contracts/infinity-pair/src/query.rs +++ b/contracts/infinity-pair/src/query.rs @@ -1,11 +1,11 @@ use crate::{ - helpers::load_pair, - msg::{NftDepositsResponse, QueryMsg}, + helpers::{load_pair, load_payout_context}, + msg::{NftDepositsResponse, QueryMsg, QuotesResponse}, pair::Pair, - state::{NFT_DEPOSITS, PAIR_IMMUTABLE}, + state::{INFINITY_GLOBAL, NFT_DEPOSITS, PAIR_IMMUTABLE}, }; -use cosmwasm_std::{to_binary, Binary, Deps, Env, StdError, StdResult}; +use cosmwasm_std::{to_binary, Binary, Deps, Env, StdError, StdResult, Uint128}; use sg_index_query::{QueryOptions, QueryOptionsInternal}; #[cfg(not(feature = "library"))] @@ -18,6 +18,12 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { QueryMsg::NftDeposits { query_options, } => to_binary(&query_nft_deposits(deps, query_options.unwrap_or_default())?), + QueryMsg::SellToPairQuotes { + limit, + } => to_binary(&query_sell_to_pair_quotes(deps, env, limit)?), + QueryMsg::BuyFromPairQuotes { + limit, + } => to_binary(&query_buy_from_pair_quotes(deps, env, limit)?), } } @@ -52,3 +58,71 @@ pub fn query_nft_deposits( token_ids, }) } + +pub fn query_sell_to_pair_quotes(deps: Deps, env: Env, limit: u32) -> StdResult { + let mut pair = load_pair(&env.contract.address, deps.storage, &deps.querier) + .map_err(|_| StdError::generic_err("failed to load pair".to_string()))?; + + let infinity_global = INFINITY_GLOBAL.load(deps.storage)?; + let payout_context = load_payout_context( + deps, + &infinity_global, + &pair.immutable.collection, + &pair.immutable.denom, + ) + .map_err(|_| StdError::generic_err("failed to load payout context".to_string()))?; + + let mut quotes: Vec = vec![]; + + let mut idx = 0u32; + while idx < limit { + if let Some(quote_summary) = &pair.internal.sell_to_pair_quote_summary { + quotes.push(quote_summary.seller_amount); + } else { + break; + } + + pair.sim_swap_nft_for_tokens(&payout_context); + + idx += 1; + } + + Ok(QuotesResponse { + denom: pair.immutable.denom, + quotes, + }) +} + +pub fn query_buy_from_pair_quotes(deps: Deps, env: Env, limit: u32) -> StdResult { + let mut pair = load_pair(&env.contract.address, deps.storage, &deps.querier) + .map_err(|_| StdError::generic_err("failed to load pair".to_string()))?; + + let infinity_global = INFINITY_GLOBAL.load(deps.storage)?; + let payout_context = load_payout_context( + deps, + &infinity_global, + &pair.immutable.collection, + &pair.immutable.denom, + ) + .map_err(|_| StdError::generic_err("failed to load payout context".to_string()))?; + + let mut quotes: Vec = vec![]; + + let mut idx = 0u32; + while idx < limit { + if let Some(quote_summary) = &pair.internal.buy_from_pair_quote_summary { + quotes.push(quote_summary.total()); + } else { + break; + } + + pair.sim_swap_tokens_for_nft(&payout_context); + + idx += 1; + } + + Ok(QuotesResponse { + denom: pair.immutable.denom, + quotes, + }) +} diff --git a/unit-tests/src/helpers/nft_functions.rs b/unit-tests/src/helpers/nft_functions.rs index 86fe2c9..3ddd95c 100644 --- a/unit-tests/src/helpers/nft_functions.rs +++ b/unit-tests/src/helpers/nft_functions.rs @@ -72,19 +72,19 @@ pub fn _approve_all( assert!(res.is_ok()); } -pub fn _transfer( +pub fn transfer( router: &mut StargazeApp, creator: &Addr, recipient: &Addr, collection: &Addr, - token_id: u32, + token_id: &String, ) { let transfer_msg: Sg721ExecuteMsg = Sg721ExecuteMsg::TransferNft { recipient: recipient.to_string(), token_id: token_id.to_string(), }; - let res = router.execute_contract(creator.clone(), collection.clone(), &transfer_msg, &[]); - assert!(res.is_ok()); + let response = router.execute_contract(creator.clone(), collection.clone(), &transfer_msg, &[]); + assert!(response.is_ok()); } pub fn _burn(router: &mut StargazeApp, creator: &Addr, collection: &Addr, token_id: u32) { diff --git a/unit-tests/src/infinity_pair_tests/deposit_assets_tests.rs b/unit-tests/src/infinity_pair_tests/deposit_assets_tests.rs index 7618b8c..0650b93 100644 --- a/unit-tests/src/infinity_pair_tests/deposit_assets_tests.rs +++ b/unit-tests/src/infinity_pair_tests/deposit_assets_tests.rs @@ -1,13 +1,18 @@ -use crate::helpers::nft_functions::{assert_nft_owner, mint_to}; -use crate::helpers::pair_functions::create_pair; +use crate::helpers::nft_functions::{assert_nft_owner, mint_to, transfer}; +use crate::helpers::pair_functions::{create_pair, create_pair_with_deposits}; use crate::helpers::utils::assert_error; -use crate::setup::templates::{setup_infinity_test, standard_minter_template, InfinityTestSetup}; +use crate::setup::setup_accounts::MarketAccounts; +use crate::setup::setup_infinity_contracts::UOSMO; +use crate::setup::templates::{ + minter_two_collections, setup_infinity_test, standard_minter_template, InfinityTestSetup, +}; -use cosmwasm_std::{coin, to_binary, Empty, Uint128}; +use cosmwasm_std::{coin, to_binary, Addr, Decimal, Empty, Uint128}; use cw721::Cw721ExecuteMsg; use cw_multi_test::Executor; use infinity_pair::msg::{ExecuteMsg as InfinityPairExecuteMsg, QueryMsg as InfinityPairQueryMsg}; use infinity_pair::pair::Pair; +use infinity_pair::state::{BondingCurve, PairConfig, PairType}; use infinity_shared::InfinityError; use sg_std::NATIVE_DENOM; use test_suite::common_setup::msg::MinterTemplateResponse; @@ -31,13 +36,8 @@ fn try_deposit_nft() { let minter = collection_resp.minter.clone().unwrap(); let collection = collection_resp.collection.clone().unwrap(); - let (pair_addr, _pair) = create_pair( - &mut router, - &infinity_global, - &infinity_factory, - &collection, - &accts.owner, - ); + let (pair_addr, _pair) = + create_pair(&mut router, &infinity_global, &infinity_factory, &collection, &accts.owner); let token_id = mint_to(&mut router, &accts.creator, &accts.owner, &minter); @@ -55,10 +55,8 @@ fn try_deposit_nft() { assert_nft_owner(&router, &collection, token_id, &pair_addr); - let pair = router - .wrap() - .query_wasm_smart::(pair_addr, &InfinityPairQueryMsg::Pair {}) - .unwrap(); + let pair = + router.wrap().query_wasm_smart::(pair_addr, &InfinityPairQueryMsg::Pair {}).unwrap(); assert!(pair.internal.total_nfts == 1); } @@ -81,13 +79,8 @@ fn try_withdraw_nfts() { let minter = collection_resp.minter.clone().unwrap(); let collection = collection_resp.collection.clone().unwrap(); - let (pair_addr, _pair) = create_pair( - &mut router, - &infinity_global, - &infinity_factory, - &collection, - &accts.owner, - ); + let (pair_addr, _pair) = + create_pair(&mut router, &infinity_global, &infinity_factory, &collection, &accts.owner); let num_nfts: usize = 10; let mut token_ids: Vec = vec![]; @@ -122,7 +115,9 @@ fn try_withdraw_nfts() { accts.creator.clone(), pair_addr.clone(), &InfinityPairExecuteMsg::WithdrawNfts { + collection: collection.to_string(), token_ids: withdraw_nfts.clone(), + asset_recipient: None, }, &[], ); @@ -136,7 +131,9 @@ fn try_withdraw_nfts() { accts.owner.clone(), pair_addr.clone(), &InfinityPairExecuteMsg::WithdrawNfts { + collection: collection.to_string(), token_ids: withdraw_nfts.clone(), + asset_recipient: None, }, &[], ); @@ -151,7 +148,9 @@ fn try_withdraw_nfts() { accts.creator.clone(), pair_addr.clone(), &InfinityPairExecuteMsg::WithdrawAnyNfts { + collection: collection.to_string(), limit: 100u32, + asset_recipient: None, }, &[], ); @@ -160,12 +159,15 @@ fn try_withdraw_nfts() { InfinityError::Unauthorized("sender is not the owner of the pair".to_string()).to_string(), ); - // Withdraw rest of the token ids + // Withdraw rest of the token ids to asset recipient + let asset_recipient = Addr::unchecked("asset_recipient"); let response = router.execute_contract( accts.owner.clone(), pair_addr.clone(), &InfinityPairExecuteMsg::WithdrawAnyNfts { + collection: collection.to_string(), limit: 100u32, + asset_recipient: Some(asset_recipient.to_string()), }, &[], ); @@ -173,14 +175,107 @@ fn try_withdraw_nfts() { let withdraw_nfts = token_ids[num_nfts / 2..].to_vec(); for withdraw_token_id in withdraw_nfts { - assert_nft_owner(&router, &collection, withdraw_token_id, &accts.owner.clone()); + assert_nft_owner(&router, &collection, withdraw_token_id, &asset_recipient.clone()); } + let pair = + router.wrap().query_wasm_smart::(pair_addr, &InfinityPairQueryMsg::Pair {}).unwrap(); + assert!(pair.internal.total_nfts == 0); +} + +#[test] +fn try_withdraw_other_collection_nfts() { + let vt = minter_two_collections(1000u32); + let InfinityTestSetup { + vending_template: + MinterTemplateResponse { + collection_response_vec, + mut router, + accts: + MarketAccounts { + creator, + owner, + .. + }, + }, + infinity_global, + infinity_factory, + .. + } = setup_infinity_test(vt).unwrap(); + + let collection_resp = &collection_response_vec[0]; + let minter = collection_resp.minter.clone().unwrap(); + let collection = collection_resp.collection.clone().unwrap(); + + let test_pair = create_pair_with_deposits( + &mut router, + &infinity_global, + &infinity_factory, + &minter, + &collection, + &creator, + &owner, + PairConfig { + pair_type: PairType::Trade { + swap_fee_percent: Decimal::zero(), + reinvest_tokens: false, + reinvest_nfts: false, + }, + bonding_curve: BondingCurve::Linear { + spot_price: Uint128::from(10_000_000u128), + delta: Uint128::from(1_000_000u128), + }, + is_active: true, + asset_recipient: None, + }, + 10u64, + Uint128::from(100_000_000u128), + ); + assert!(test_pair.pair.internal.sell_to_pair_quote_summary.is_some()); + assert!(test_pair.pair.internal.buy_from_pair_quote_summary.is_some(),); + + // Send NFT from other collection to pair + let other_collection = &collection_response_vec[1].collection.clone().unwrap(); + let other_minter = &collection_response_vec[1].minter.clone().unwrap(); + let token_id = mint_to(&mut router, &creator.clone(), &owner.clone(), other_minter); + transfer(&mut router, &owner, &test_pair.address, &other_collection, &token_id); + assert_nft_owner(&router, &other_collection, token_id.clone(), &test_pair.address); + + // Non owner cannot withdraw other collection nfts + let response = router.execute_contract( + creator.clone(), + test_pair.address.clone(), + &InfinityPairExecuteMsg::WithdrawNfts { + collection: other_collection.to_string(), + token_ids: vec![token_id.clone()], + asset_recipient: None, + }, + &[], + ); + assert_error( + response, + InfinityError::Unauthorized("sender is not the owner of the pair".to_string()).to_string(), + ); + + // Owner can withdraw other collection nfts + let response = router.execute_contract( + owner.clone(), + test_pair.address.clone(), + &InfinityPairExecuteMsg::WithdrawNfts { + collection: other_collection.to_string(), + token_ids: vec![token_id.clone()], + asset_recipient: None, + }, + &[], + ); + assert!(response.is_ok()); + assert_nft_owner(&router, &other_collection, token_id, &owner); + let pair = router .wrap() - .query_wasm_smart::(pair_addr, &InfinityPairQueryMsg::Pair {}) + .query_wasm_smart::(test_pair.address.clone(), &InfinityPairQueryMsg::Pair {}) .unwrap(); - assert!(pair.internal.total_nfts == 0); + assert_eq!(test_pair.pair, pair); } #[test] @@ -202,13 +297,8 @@ fn try_deposit_tokens() { let _minter = collection_resp.minter.clone().unwrap(); let collection = collection_resp.collection.clone().unwrap(); - let (pair_addr, _pair) = create_pair( - &mut router, - &infinity_global, - &infinity_factory, - &collection, - &accts.owner, - ); + let (pair_addr, _pair) = + create_pair(&mut router, &infinity_global, &infinity_factory, &collection, &accts.owner); // Non owner cannot deposit tokens let response = router.execute_contract( @@ -241,10 +331,8 @@ fn try_deposit_tokens() { ); assert!(response.is_ok()); - let pair = router - .wrap() - .query_wasm_smart::(pair_addr, &InfinityPairQueryMsg::Pair {}) - .unwrap(); + let pair = + router.wrap().query_wasm_smart::(pair_addr, &InfinityPairQueryMsg::Pair {}).unwrap(); assert_eq!(pair.total_tokens.u128(), deposit_amount); } @@ -267,13 +355,8 @@ fn try_withdraw_tokens() { let _minter = collection_resp.minter.clone().unwrap(); let collection = collection_resp.collection.clone().unwrap(); - let (pair_addr, _pair) = create_pair( - &mut router, - &infinity_global, - &infinity_factory, - &collection, - &accts.owner, - ); + let (pair_addr, _pair) = + create_pair(&mut router, &infinity_global, &infinity_factory, &collection, &accts.owner); let deposit_amount = 100_000_000u128; let response = router.execute_contract( @@ -289,7 +372,8 @@ fn try_withdraw_tokens() { accts.creator.clone(), pair_addr.clone(), &InfinityPairExecuteMsg::WithdrawTokens { - amount: Uint128::from(10_000_000u128), + funds: vec![coin(10_000_000u128, NATIVE_DENOM)], + asset_recipient: None, }, &[], ); @@ -303,7 +387,8 @@ fn try_withdraw_tokens() { accts.owner.clone(), pair_addr.clone(), &InfinityPairExecuteMsg::WithdrawTokens { - amount: Uint128::from(10_000_000u128), + funds: vec![coin(10_000_000u128, NATIVE_DENOM)], + asset_recipient: None, }, &[], ); @@ -313,7 +398,9 @@ fn try_withdraw_tokens() { let response = router.execute_contract( accts.creator.clone(), pair_addr.clone(), - &InfinityPairExecuteMsg::WithdrawAllTokens {}, + &InfinityPairExecuteMsg::WithdrawAllTokens { + asset_recipient: None, + }, &[], ); assert_error( @@ -321,18 +408,110 @@ fn try_withdraw_tokens() { InfinityError::Unauthorized("sender is not the owner of the pair".to_string()).to_string(), ); - // Owner can withdraw all tokens + // Owner can withdraw all tokens to asset recipient + let asset_recipient = Addr::unchecked("asset_recipient"); let response = router.execute_contract( accts.owner, pair_addr.clone(), - &InfinityPairExecuteMsg::WithdrawAllTokens {}, + &InfinityPairExecuteMsg::WithdrawAllTokens { + asset_recipient: Some(asset_recipient.to_string()), + }, + &[], + ); + assert!(response.is_ok()); + + let pair = + router.wrap().query_wasm_smart::(pair_addr, &InfinityPairQueryMsg::Pair {}).unwrap(); + assert_eq!(pair.total_tokens.u128(), 0u128); +} + +#[test] +fn try_withdraw_other_denom_tokens() { + let vt = minter_two_collections(1000u32); + let InfinityTestSetup { + vending_template: + MinterTemplateResponse { + collection_response_vec, + mut router, + accts: + MarketAccounts { + creator, + owner, + .. + }, + }, + infinity_global, + infinity_factory, + .. + } = setup_infinity_test(vt).unwrap(); + + let collection_resp = &collection_response_vec[0]; + let minter = collection_resp.minter.clone().unwrap(); + let collection = collection_resp.collection.clone().unwrap(); + + let test_pair = create_pair_with_deposits( + &mut router, + &infinity_global, + &infinity_factory, + &minter, + &collection, + &creator, + &owner, + PairConfig { + pair_type: PairType::Trade { + swap_fee_percent: Decimal::zero(), + reinvest_tokens: false, + reinvest_nfts: false, + }, + bonding_curve: BondingCurve::Linear { + spot_price: Uint128::from(10_000_000u128), + delta: Uint128::from(1_000_000u128), + }, + is_active: true, + asset_recipient: None, + }, + 10u64, + Uint128::from(100_000_000u128), + ); + assert!(test_pair.pair.internal.sell_to_pair_quote_summary.is_some()); + assert!(test_pair.pair.internal.buy_from_pair_quote_summary.is_some(),); + + // Send other denom tokens to pair + let other_denom_funds = coin(2_000_000u128, UOSMO); + let response = + router.send_tokens(owner.clone(), test_pair.address.clone(), &[other_denom_funds.clone()]); + assert!(response.is_ok()); + + // Non owner cannot withdraw denom tokens + let response = router.execute_contract( + creator.clone(), + test_pair.address.clone(), + &InfinityPairExecuteMsg::WithdrawTokens { + funds: vec![other_denom_funds.clone()], + asset_recipient: None, + }, + &[], + ); + assert_error( + response, + InfinityError::Unauthorized("sender is not the owner of the pair".to_string()).to_string(), + ); + + // Owner can withdraw other denom tokens + let response = router.execute_contract( + owner.clone(), + test_pair.address.clone(), + &InfinityPairExecuteMsg::WithdrawTokens { + funds: vec![other_denom_funds], + asset_recipient: None, + }, &[], ); assert!(response.is_ok()); let pair = router .wrap() - .query_wasm_smart::(pair_addr, &InfinityPairQueryMsg::Pair {}) + .query_wasm_smart::(test_pair.address.clone(), &InfinityPairQueryMsg::Pair {}) .unwrap(); - assert_eq!(pair.total_tokens.u128(), 0u128); + assert_eq!(test_pair.pair, pair); } diff --git a/unit-tests/src/setup/templates.rs b/unit-tests/src/setup/templates.rs index 11f6f5e..9d22303 100644 --- a/unit-tests/src/setup/templates.rs +++ b/unit-tests/src/setup/templates.rs @@ -63,7 +63,7 @@ pub fn standard_minter_template(num_tokens: u32) -> MinterTemplateResponse MinterTemplateResponse { +pub fn minter_two_collections(num_tokens: u32) -> MinterTemplateResponse { let mut app = custom_mock_app(); let (owner, bidder, creator) = setup_accounts(&mut app).unwrap(); let start_time = Timestamp::from_nanos(GENESIS_MINT_START_TIME); @@ -99,6 +99,13 @@ pub struct InfinityTestSetup { pub infinity_pair_code_id: u64, } +fn increment_number_in_string(s: &str, increment: u32) -> String { + let prefix: String = s.chars().take_while(|c| c.is_alphabetic()).collect(); + let number: String = s.chars().skip_while(|c| c.is_alphabetic()).collect(); + let incremented_number = number.parse::().unwrap_or(0) + increment; + format!("{}{}", prefix, incremented_number) +} + pub fn setup_infinity_test( mut vt: MinterTemplateResponse, ) -> Result { @@ -108,7 +115,8 @@ pub fn setup_infinity_test( let royalty_registry = setup_royalty_registry(&mut vt.router, &vt.accts.creator); let marketplace = setup_marketplace(&mut vt.router, &vt.accts.creator.clone()); - let pre_infinity_global = Addr::unchecked("contract9"); + let pre_infinity_global = + Addr::unchecked(increment_number_in_string(&marketplace.to_string(), 4)); let infinity_factory = setup_infinity_factory(&mut vt.router, &vt.accts.creator.clone(), &pre_infinity_global);