From f6a009c64f53f5f39c46829b77f6e98ef47be9c7 Mon Sep 17 00:00:00 2001 From: Kayanski Date: Thu, 11 Apr 2024 23:50:55 +0000 Subject: [PATCH 1/2] Added ans assets in assetshares --- .../carrot-app/examples/localnet_install.rs | 4 +- contracts/carrot-app/src/check.rs | 8 ++-- .../carrot-app/src/distribution/deposit.rs | 20 ++++++--- .../carrot-app/src/distribution/query.rs | 14 +++++-- contracts/carrot-app/src/error.rs | 3 ++ contracts/carrot-app/src/helpers.rs | 9 ++++ .../carrot-app/src/yield_sources/mars.rs | 1 + contracts/carrot-app/src/yield_sources/mod.rs | 31 +++++++++----- contracts/carrot-app/tests/common.rs | 4 +- contracts/carrot-app/tests/config.rs | 41 ++++++++++--------- .../carrot-app/tests/deposit_withdraw.rs | 19 +++++---- 11 files changed, 98 insertions(+), 56 deletions(-) diff --git a/contracts/carrot-app/examples/localnet_install.rs b/contracts/carrot-app/examples/localnet_install.rs index e7e87565..fe9cdad1 100644 --- a/contracts/carrot-app/examples/localnet_install.rs +++ b/contracts/carrot-app/examples/localnet_install.rs @@ -84,11 +84,11 @@ fn main() -> anyhow::Result<()> { yield_source: YieldSourceBase { asset_distribution: vec![ AssetShare { - denom: ION.to_string(), + asset: AssetEntry::new(ION), share: Decimal::percent(50), }, AssetShare { - denom: OSMO.to_string(), + asset: AssetEntry::new(OSMO), share: Decimal::percent(50), }, ], diff --git a/contracts/carrot-app/src/check.rs b/contracts/carrot-app/src/check.rs index 6761ae93..7e16e3db 100644 --- a/contracts/carrot-app/src/check.rs +++ b/contracts/carrot-app/src/check.rs @@ -214,7 +214,7 @@ mod yield_sources { AppError::InvalidEmptyStrategy {} ); // We ensure all deposited tokens exist in ANS - let all_denoms = self.all_denoms(); + let all_denoms = self.all_denoms(deps, app)?; let ans = app.name_service(deps); ans.host() .query_assets_reverse( @@ -244,9 +244,11 @@ mod yield_sources { AppError::InvalidStrategy {} ); // We verify the first element correspond to the mars deposit denom + let ans = app.name_service(deps); + let asset = ans.query(&AssetInfo::native(params.denom.clone()))?; ensure_eq!( - self.asset_distribution[0].denom, - params.denom, + self.asset_distribution[0].asset, + asset, AppError::InvalidStrategy {} ); YieldParamsBase::Mars(params.check(deps, app)?) diff --git a/contracts/carrot-app/src/distribution/deposit.rs b/contracts/carrot-app/src/distribution/deposit.rs index f9519a72..2b624df4 100644 --- a/contracts/carrot-app/src/distribution/deposit.rs +++ b/contracts/carrot-app/src/distribution/deposit.rs @@ -1,11 +1,12 @@ use std::collections::HashMap; use cosmwasm_std::{coin, Coin, Coins, Decimal, Deps, Uint128}; +use cw_asset::AssetInfo; use crate::{ contract::{App, AppResult}, exchange_rate::query_all_exchange_rates, - helpers::{compute_total_value, compute_value}, + helpers::{compute_total_value, compute_value, unwrap_native}, state::STRATEGY_CONFIG, yield_sources::{yield_type::YieldType, AssetShare, Strategy, StrategyElement}, }; @@ -13,6 +14,7 @@ use crate::{ use cosmwasm_schema::cw_serde; use crate::{error::AppError, msg::InternalExecuteMsg}; +use abstract_app::traits::AbstractNameService; pub fn generate_deposit_strategy( deps: Deps, @@ -158,11 +160,14 @@ impl Strategy { // This is the algorithm that is implemented here fn fill_sources( &self, + deps: Deps, funds: Vec, exchange_rates: &HashMap, + app: &App, ) -> AppResult<(StrategyStatus, Coins)> { let total_value = compute_total_value(&funds, exchange_rates)?; let mut remaining_funds = Coins::default(); + let ans = app.name_service(deps); // We create the vector that holds the funds information let mut yield_source_status = self @@ -173,7 +178,9 @@ impl Strategy { .yield_source .asset_distribution .iter() - .map(|AssetShare { denom, share }| { + .map(|AssetShare { asset, share }| { + let denom = unwrap_native(&ans.query(asset)?)?; + // Amount to fill this denom completely is value / exchange_rate // Value we want to put here is share * source.share * total_value Ok::<_, AppError>(StrategyStatusElement { @@ -181,7 +188,7 @@ impl Strategy { raw_funds: Uint128::zero(), remaining_amount: (share * source.share / exchange_rates - .get(denom) + .get(&denom) .ok_or(AppError::NoExchangeRate(denom.clone()))?) * total_value, }) @@ -192,6 +199,7 @@ impl Strategy { for this_coin in funds { let mut remaining_amount = this_coin.amount; + let this_asset = ans.query(&AssetInfo::native(this_coin.denom.clone()))?; // We distribute those funds in to the accepting strategies for (strategy, status) in self.0.iter().zip(yield_source_status.iter_mut()) { // Find the share for the specific denom inside the strategy @@ -200,7 +208,7 @@ impl Strategy { .asset_distribution .iter() .zip(status.iter_mut()) - .find(|(AssetShare { denom, share: _ }, _status)| this_coin.denom.eq(denom)) + .find(|(AssetShare { asset, share: _ }, _status)| this_asset.eq(asset)) .map(|(_, status)| status); if let Some(status) = this_denom_status { @@ -232,10 +240,10 @@ impl Strategy { funds .iter() .map(|f| f.denom.clone()) - .chain(self.all_denoms()), + .chain(self.all_denoms(deps, app)?), app, )?; - let (status, remaining_funds) = self.fill_sources(funds, &exchange_rates)?; + let (status, remaining_funds) = self.fill_sources(deps, funds, &exchange_rates, app)?; status.fill_with_remaining_funds(remaining_funds, &exchange_rates) } diff --git a/contracts/carrot-app/src/distribution/query.rs b/contracts/carrot-app/src/distribution/query.rs index 03757cf7..149e1d5c 100644 --- a/contracts/carrot-app/src/distribution/query.rs +++ b/contracts/carrot-app/src/distribution/query.rs @@ -1,4 +1,5 @@ use cosmwasm_std::{Coins, Decimal, Deps, Uint128}; +use cw_asset::AssetInfo; use crate::{ contract::{App, AppResult}, @@ -9,6 +10,7 @@ use crate::{ yield_type::YieldTypeImplementation, AssetShare, Strategy, StrategyElement, YieldSource, }, }; +use abstract_app::traits::AbstractNameService; impl Strategy { // Returns the total balance @@ -123,14 +125,18 @@ impl StrategyElement { self.yield_source.asset_distribution.clone(), )); } + let ans = app.name_service(deps); let each_shares = each_value .into_iter() - .map(|(denom, amount)| AssetShare { - denom, - share: Decimal::from_ratio(amount, total_value), + .map(|(denom, amount)| { + let asset = ans.query(&AssetInfo::native(denom))?; + Ok::<_, AppError>(AssetShare { + asset, + share: Decimal::from_ratio(amount, total_value), + }) }) - .collect(); + .collect::>()?; Ok((total_value, each_shares)) } } diff --git a/contracts/carrot-app/src/error.rs b/contracts/carrot-app/src/error.rs index 7232c2f4..89371415 100644 --- a/contracts/carrot-app/src/error.rs +++ b/contracts/carrot-app/src/error.rs @@ -103,4 +103,7 @@ pub enum AppError { #[error("Invalid strategy format, check shares and parameters")] InvalidStrategy {}, + + #[error("Expected native asset, got cw20")] + NonNativeAsset {}, } diff --git a/contracts/carrot-app/src/helpers.rs b/contracts/carrot-app/src/helpers.rs index 18752e4c..11473426 100644 --- a/contracts/carrot-app/src/helpers.rs +++ b/contracts/carrot-app/src/helpers.rs @@ -7,6 +7,15 @@ use abstract_app::traits::AccountIdentification; use abstract_app::{objects::AssetEntry, traits::AbstractNameService}; use abstract_sdk::Resolve; use cosmwasm_std::{Addr, Coin, Coins, Decimal, Deps, Env, MessageInfo, StdResult, Uint128}; +use cw_asset::AssetInfo; + +pub fn unwrap_native(asset: &AssetInfo) -> AppResult { + match asset { + cw_asset::AssetInfoBase::Native(denom) => Ok(denom.clone()), + cw_asset::AssetInfoBase::Cw20(_) => Err(AppError::NonNativeAsset {}), + _ => Err(AppError::NonNativeAsset {}), + } +} pub fn assert_contract(info: &MessageInfo, env: &Env) -> AppResult<()> { if info.sender == env.contract.address { diff --git a/contracts/carrot-app/src/yield_sources/mars.rs b/contracts/carrot-app/src/yield_sources/mars.rs index 48ccd22c..e5656605 100644 --- a/contracts/carrot-app/src/yield_sources/mars.rs +++ b/contracts/carrot-app/src/yield_sources/mars.rs @@ -16,6 +16,7 @@ pub const MARS_MONEY_MARKET: &str = "mars"; #[cw_serde] pub struct MarsDepositParams { + /// This should stay a denom because that's a parameter that's accepted by Mars when depositing/withdrawing pub denom: String, } diff --git a/contracts/carrot-app/src/yield_sources/mod.rs b/contracts/carrot-app/src/yield_sources/mod.rs index 33d9bea7..4ba1ad49 100644 --- a/contracts/carrot-app/src/yield_sources/mod.rs +++ b/contracts/carrot-app/src/yield_sources/mod.rs @@ -1,15 +1,18 @@ pub mod mars; pub mod osmosis_cl_pool; pub mod yield_type; - +use abstract_app::objects::AssetEntry; use cosmwasm_schema::cw_serde; -use cosmwasm_std::Decimal; +use cosmwasm_std::{Decimal, Deps}; +use cw_asset::AssetInfo; use crate::{ check::{Checked, Unchecked}, + contract::{App, AppResult}, + helpers::unwrap_native, yield_sources::yield_type::YieldParamsBase, }; - +use abstract_app::traits::AbstractNameService; /// A yield sources has the following elements /// A vector of tokens that NEED to be deposited inside the yield source with a repartition of tokens /// A type that allows routing to the right smart-contract integration internally @@ -23,10 +26,15 @@ pub type YieldSourceUnchecked = YieldSourceBase; pub type YieldSource = YieldSourceBase; impl YieldSourceBase { - pub fn all_denoms(&self) -> Vec { + pub fn all_denoms(&self, deps: Deps, app: &App) -> AppResult> { + let ans = app.name_service(deps); + self.asset_distribution .iter() - .map(|e| e.denom.clone()) + .map(|e| { + let denom = unwrap_native(&ans.query(&e.asset)?)?; + Ok(denom) + }) .collect() } } @@ -34,7 +42,7 @@ impl YieldSourceBase { /// This is used to express a share of tokens inside a strategy #[cw_serde] pub struct AssetShare { - pub denom: String, + pub asset: AssetEntry, pub share: Decimal, } @@ -55,12 +63,15 @@ pub type StrategyUnchecked = StrategyBase; pub type Strategy = StrategyBase; impl Strategy { - pub fn all_denoms(&self) -> Vec { - self.0 + pub fn all_denoms(&self, deps: Deps, app: &App) -> AppResult> { + let results = self + .0 .clone() .iter() - .flat_map(|s| s.yield_source.all_denoms()) - .collect() + .map(|s| s.yield_source.all_denoms(deps, app)) + .collect::, _>>()?; + + Ok(results.into_iter().flatten().collect()) } } diff --git a/contracts/carrot-app/tests/common.rs b/contracts/carrot-app/tests/common.rs index 503cc9dd..a9c05d2b 100644 --- a/contracts/carrot-app/tests/common.rs +++ b/contracts/carrot-app/tests/common.rs @@ -136,11 +136,11 @@ pub fn deploy( yield_source: YieldSourceBase { asset_distribution: vec![ AssetShare { - denom: USDT.to_string(), + asset: AssetEntry::new(USDT), share: Decimal::percent(50), }, AssetShare { - denom: USDC.to_string(), + asset: AssetEntry::new(USDC), share: Decimal::percent(50), }, ], diff --git a/contracts/carrot-app/tests/config.rs b/contracts/carrot-app/tests/config.rs index 0a2ce4b1..3e500200 100644 --- a/contracts/carrot-app/tests/config.rs +++ b/contracts/carrot-app/tests/config.rs @@ -1,6 +1,7 @@ mod common; use crate::common::{create_pool, setup_test_tube, USDC, USDT}; +use abstract_app::objects::AssetEntry; use carrot_app::{ msg::{AppExecuteMsgFns, AppQueryMsgFns}, yield_sources::{ @@ -26,11 +27,11 @@ fn rebalance_fails() -> anyhow::Result<()> { yield_source: YieldSourceBase { asset_distribution: vec![ AssetShare { - denom: USDT.to_string(), + asset: AssetEntry::new(USDT), share: Decimal::percent(50), }, AssetShare { - denom: USDC.to_string(), + asset: AssetEntry::new(USDC), share: Decimal::percent(50), }, ], @@ -50,11 +51,11 @@ fn rebalance_fails() -> anyhow::Result<()> { yield_source: YieldSourceBase { asset_distribution: vec![ AssetShare { - denom: USDT.to_string(), + asset: AssetEntry::new(USDT), share: Decimal::percent(50), }, AssetShare { - denom: USDC.to_string(), + asset: AssetEntry::new(USDC), share: Decimal::percent(50), }, ], @@ -89,11 +90,11 @@ fn rebalance_success() -> anyhow::Result<()> { yield_source: YieldSourceBase { asset_distribution: vec![ AssetShare { - denom: USDT.to_string(), + asset: AssetEntry::new(USDT), share: Decimal::percent(50), }, AssetShare { - denom: USDC.to_string(), + asset: AssetEntry::new(USDC), share: Decimal::percent(50), }, ], @@ -111,11 +112,11 @@ fn rebalance_success() -> anyhow::Result<()> { yield_source: YieldSourceBase { asset_distribution: vec![ AssetShare { - denom: USDT.to_string(), + asset: AssetEntry::new(USDT), share: Decimal::percent(50), }, AssetShare { - denom: USDC.to_string(), + asset: AssetEntry::new(USDC), share: Decimal::percent(50), }, ], @@ -166,11 +167,11 @@ fn rebalance_with_new_pool_success() -> anyhow::Result<()> { yield_source: YieldSourceBase { asset_distribution: vec![ AssetShare { - denom: USDT.to_string(), + asset: AssetEntry::new(USDT), share: Decimal::percent(50), }, AssetShare { - denom: USDC.to_string(), + asset: AssetEntry::new(USDC), share: Decimal::percent(50), }, ], @@ -188,11 +189,11 @@ fn rebalance_with_new_pool_success() -> anyhow::Result<()> { yield_source: YieldSourceBase { asset_distribution: vec![ AssetShare { - denom: USDT.to_string(), + asset: AssetEntry::new(USDT), share: Decimal::percent(50), }, AssetShare { - denom: USDC.to_string(), + asset: AssetEntry::new(USDC), share: Decimal::percent(50), }, ], @@ -244,11 +245,11 @@ fn rebalance_with_stale_strategy_success() -> anyhow::Result<()> { let common_yield_source = YieldSourceBase { asset_distribution: vec![ AssetShare { - denom: USDT.to_string(), + asset: AssetEntry::new(USDT), share: Decimal::percent(50), }, AssetShare { - denom: USDC.to_string(), + asset: AssetEntry::new(USDC), share: Decimal::percent(50), }, ], @@ -270,11 +271,11 @@ fn rebalance_with_stale_strategy_success() -> anyhow::Result<()> { yield_source: YieldSourceBase { asset_distribution: vec![ AssetShare { - denom: USDT.to_string(), + asset: AssetEntry::new(USDT), share: Decimal::percent(50), }, AssetShare { - denom: USDC.to_string(), + asset: AssetEntry::new(USDC), share: Decimal::percent(50), }, ], @@ -338,11 +339,11 @@ fn rebalance_with_current_and_stale_strategy_success() -> anyhow::Result<()> { let moving_strategy = YieldSourceBase { asset_distribution: vec![ AssetShare { - denom: USDT.to_string(), + asset: AssetEntry::new(USDT), share: Decimal::percent(50), }, AssetShare { - denom: USDC.to_string(), + asset: AssetEntry::new(USDC), share: Decimal::percent(50), }, ], @@ -360,11 +361,11 @@ fn rebalance_with_current_and_stale_strategy_success() -> anyhow::Result<()> { yield_source: YieldSourceBase { asset_distribution: vec![ AssetShare { - denom: USDT.to_string(), + asset: AssetEntry::new(USDT), share: Decimal::percent(50), }, AssetShare { - denom: USDC.to_string(), + asset: AssetEntry::new(USDC), share: Decimal::percent(50), }, ], diff --git a/contracts/carrot-app/tests/deposit_withdraw.rs b/contracts/carrot-app/tests/deposit_withdraw.rs index fbb3ed44..c17a1003 100644 --- a/contracts/carrot-app/tests/deposit_withdraw.rs +++ b/contracts/carrot-app/tests/deposit_withdraw.rs @@ -1,6 +1,7 @@ mod common; use crate::common::{setup_test_tube, USDC, USDT}; +use abstract_app::objects::AssetEntry; use abstract_client::Application; use carrot_app::{ msg::{AppExecuteMsgFns, AppQueryMsgFns, AssetsBalanceResponse}, @@ -152,11 +153,11 @@ fn deposit_multiple_positions() -> anyhow::Result<()> { yield_source: YieldSourceBase { asset_distribution: vec![ AssetShare { - denom: USDT.to_string(), + asset: AssetEntry::new(USDT), share: Decimal::percent(50), }, AssetShare { - denom: USDC.to_string(), + asset: AssetEntry::new(USDC), share: Decimal::percent(50), }, ], @@ -174,11 +175,11 @@ fn deposit_multiple_positions() -> anyhow::Result<()> { yield_source: YieldSourceBase { asset_distribution: vec![ AssetShare { - denom: USDT.to_string(), + asset: AssetEntry::new(USDT), share: Decimal::percent(50), }, AssetShare { - denom: USDC.to_string(), + asset: AssetEntry::new(USDC), share: Decimal::percent(50), }, ], @@ -224,11 +225,11 @@ fn deposit_multiple_positions_with_empty() -> anyhow::Result<()> { yield_source: YieldSourceBase { asset_distribution: vec![ AssetShare { - denom: USDT.to_string(), + asset: AssetEntry::new(USDT), share: Decimal::percent(50), }, AssetShare { - denom: USDC.to_string(), + asset: AssetEntry::new(USDC), share: Decimal::percent(50), }, ], @@ -246,11 +247,11 @@ fn deposit_multiple_positions_with_empty() -> anyhow::Result<()> { yield_source: YieldSourceBase { asset_distribution: vec![ AssetShare { - denom: USDT.to_string(), + asset: AssetEntry::new(USDT), share: Decimal::percent(50), }, AssetShare { - denom: USDC.to_string(), + asset: AssetEntry::new(USDC), share: Decimal::percent(50), }, ], @@ -267,7 +268,7 @@ fn deposit_multiple_positions_with_empty() -> anyhow::Result<()> { StrategyElementBase { yield_source: YieldSourceBase { asset_distribution: vec![AssetShare { - denom: USDT.to_string(), + asset: AssetEntry::new(USDT), share: Decimal::percent(100), }], params: YieldParamsBase::Mars(MarsDepositParams { From 0c6fe8eaf333dbcd5a1dcb9b5b063424bb43bc6f Mon Sep 17 00:00:00 2001 From: Kayanski Date: Wed, 17 Apr 2024 10:34:13 +0000 Subject: [PATCH 2/2] Transformed everything to ans asset --- .../examples/install_savings_app.rs | 4 +- .../carrot-app/examples/localnet_test.rs | 4 +- contracts/carrot-app/src/ans_assets.rs | 514 ++++++++++++++++++ contracts/carrot-app/src/check.rs | 19 +- .../carrot-app/src/distribution/deposit.rs | 84 ++- .../carrot-app/src/distribution/query.rs | 18 +- .../carrot-app/src/distribution/rewards.rs | 23 +- .../carrot-app/src/distribution/withdraw.rs | 14 +- contracts/carrot-app/src/error.rs | 14 +- contracts/carrot-app/src/exchange_rate.rs | 13 +- contracts/carrot-app/src/handlers/execute.rs | 63 ++- .../carrot-app/src/handlers/instantiate.rs | 4 +- contracts/carrot-app/src/handlers/internal.rs | 50 +- contracts/carrot-app/src/handlers/preview.rs | 9 +- contracts/carrot-app/src/handlers/query.rs | 35 +- contracts/carrot-app/src/helpers.rs | 41 +- contracts/carrot-app/src/lib.rs | 1 + contracts/carrot-app/src/msg.rs | 29 +- .../carrot-app/src/replies/after_swaps.rs | 18 +- contracts/carrot-app/src/state.rs | 7 +- .../carrot-app/src/yield_sources/mars.rs | 59 +- contracts/carrot-app/src/yield_sources/mod.rs | 20 +- .../src/yield_sources/osmosis_cl_pool.rs | 96 +++- .../src/yield_sources/yield_type.rs | 23 +- contracts/carrot-app/tests/autocompound.rs | 21 +- contracts/carrot-app/tests/common.rs | 64 ++- contracts/carrot-app/tests/config.rs | 16 +- .../carrot-app/tests/deposit_withdraw.rs | 64 +-- contracts/carrot-app/tests/pool_inbalance.rs | 23 +- contracts/carrot-app/tests/query.rs | 23 +- 30 files changed, 966 insertions(+), 407 deletions(-) create mode 100644 contracts/carrot-app/src/ans_assets.rs diff --git a/contracts/carrot-app/examples/install_savings_app.rs b/contracts/carrot-app/examples/install_savings_app.rs index dd3ffd10..0937aeff 100644 --- a/contracts/carrot-app/examples/install_savings_app.rs +++ b/contracts/carrot-app/examples/install_savings_app.rs @@ -1,5 +1,5 @@ #![allow(unused)] -use abstract_app::objects::{AccountId, AssetEntry}; +use abstract_app::objects::{AccountId, AnsAsset, AssetEntry}; use abstract_client::AbstractClient; use cosmwasm_std::{coins, Coin, Uint128, Uint256, Uint64}; use cw_orch::{ @@ -77,7 +77,7 @@ fn main() -> anyhow::Result<()> { dex: OSMOSIS.to_string(), }, strategy: StrategyBase(vec![]), - deposit: Some(coins(100, "usdc")), + deposit: Some(vec![AnsAsset::new("usdc", 100u128)]), }; let create_sub_account_message = utils::create_account_message(&client, init_msg)?; diff --git a/contracts/carrot-app/examples/localnet_test.rs b/contracts/carrot-app/examples/localnet_test.rs index bebfebff..6ed65578 100644 --- a/contracts/carrot-app/examples/localnet_test.rs +++ b/contracts/carrot-app/examples/localnet_test.rs @@ -1,5 +1,5 @@ use abstract_app::objects::{ - module::ModuleInfo, namespace::ABSTRACT_NAMESPACE, AccountId, AssetEntry, + module::ModuleInfo, namespace::ABSTRACT_NAMESPACE, AccountId, AnsAsset, AssetEntry, }; use abstract_client::{Application, Namespace}; use abstract_dex_adapter::{interface::DexAdapter, DEX_ADAPTER_ID}; @@ -76,7 +76,7 @@ fn main() -> anyhow::Result<()> { )?; // carrot.deposit(coins(10_000, "uosmo"), None)?; - carrot.deposit(coins(10_000, "uosmo"), None)?; + carrot.deposit(vec![AnsAsset::new("uosmo", 10_000u128)], None)?; // carrot.withdraw(None)?; diff --git a/contracts/carrot-app/src/ans_assets.rs b/contracts/carrot-app/src/ans_assets.rs new file mode 100644 index 00000000..ccda958d --- /dev/null +++ b/contracts/carrot-app/src/ans_assets.rs @@ -0,0 +1,514 @@ +extern crate alloc; + +use abstract_app::objects::{AnsAsset, AssetEntry}; +use alloc::collections::BTreeMap; +use core::fmt; +use cosmwasm_std::{OverflowError, OverflowOperation, StdError, StdResult, Uint128}; + +#[derive(thiserror::Error, Debug, PartialEq, Eq)] +pub enum AnsAssetsError { + #[error("Duplicate denom")] + DuplicateDenom, +} + +impl From for StdError { + fn from(value: AnsAssetsError) -> Self { + Self::generic_err(format!("Creating Coins: {value}")) + } +} + +/// A collection of assets, similar to Cosmos SDK's `sdk.AnsAssets` struct. +/// +/// Differently from `sdk.AnsAssets`, which is a vector of `sdk.AnsAsset`, here we +/// implement AnsAssets as a BTreeMap that maps from asset denoms to `AnsAsset`. +/// This has a number of advantages: +/// +/// - assets are naturally sorted alphabetically by denom +/// - duplicate denoms are automatically removed +/// - cheaper for searching/inserting/deleting: O(log(n)) compared to O(n) +#[derive(Clone, Default, Debug, PartialEq, Eq)] +pub struct AnsAssets(BTreeMap); + +/// Casting a Vec to AnsAssets. +/// The Vec can be out of order, but must not contain duplicate denoms. +/// If you want to sum up duplicates, create an empty instance using `AnsAssets::default` and +/// use `AnsAssets::add` to add your assets. +impl TryFrom> for AnsAssets { + type Error = AnsAssetsError; + + fn try_from(vec: Vec) -> Result { + let mut map = BTreeMap::new(); + for asset in vec { + if asset.amount.is_zero() { + continue; + } + + // if the insertion returns a previous value, we have a duplicate denom + if map.insert(asset.name.clone(), asset).is_some() { + return Err(AnsAssetsError::DuplicateDenom); + } + } + + Ok(Self(map)) + } +} + +impl TryFrom<&[AnsAsset]> for AnsAssets { + type Error = AnsAssetsError; + + fn try_from(slice: &[AnsAsset]) -> Result { + slice.to_vec().try_into() + } +} + +impl From for AnsAssets { + fn from(value: AnsAsset) -> Self { + let mut assets = AnsAssets::default(); + // this can never overflow (because there are no assets in there yet), so we can unwrap + assets.add(value).unwrap(); + assets + } +} + +impl TryFrom<[AnsAsset; N]> for AnsAssets { + type Error = AnsAssetsError; + + fn try_from(slice: [AnsAsset; N]) -> Result { + slice.to_vec().try_into() + } +} + +impl From for Vec { + fn from(value: AnsAssets) -> Self { + value.into_vec() + } +} + +impl fmt::Display for AnsAssets { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = self + .0 + .values() + .map(|asset| asset.to_string()) + .collect::>() + .join(","); + write!(f, "{s}") + } +} + +impl AnsAssets { + /// Conversion to Vec, while NOT consuming the original object. + /// + /// This produces a vector of assets that is sorted alphabetically by denom with + /// no duplicate denoms. + pub fn to_vec(&self) -> Vec { + self.0.values().cloned().collect() + } + + /// Conversion to Vec, consuming the original object. + /// + /// This produces a vector of assets that is sorted alphabetically by denom with + /// no duplicate denoms. + pub fn into_vec(self) -> Vec { + self.0.into_values().collect() + } + + /// Returns the number of different denoms in this collection. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns `true` if this collection contains no assets. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Returns the denoms as a vector of strings. + /// The vector is guaranteed to not contain duplicates and sorted alphabetically. + pub fn entries(&self) -> Vec { + self.0.keys().cloned().collect() + } + + /// Returns the amount of the given denom or zero if the denom is not present. + pub fn amount_of(&self, name: impl Into) -> Uint128 { + self.0 + .get(&name.into()) + .map(|c| c.amount) + .unwrap_or_else(Uint128::zero) + } + + /// Returns the amount of the given denom if and only if this collection contains only + /// the given denom. Otherwise `None` is returned. + /// + /// # Examples + /// + /// ```rust + /// use carrot_app::ans_assets::{AnsAssets}; + /// use abstract_app::objects::{AnsAsset}; + /// + /// let assets: AnsAssets = [AnsAsset::new("uatom", 100u128)].try_into().unwrap(); + /// assert_eq!(assets.contains_only("uatom").unwrap().u128(), 100); + /// assert_eq!(assets.contains_only("uluna"), None); + /// ``` + /// + /// ```rust + /// use carrot_app::ans_assets::{AnsAssets}; + /// use abstract_app::objects::{AnsAsset}; + /// + /// let assets: AnsAssets = [AnsAsset::new("uatom", 100u128), AnsAsset::new("uusd", 200u128)].try_into().unwrap(); + /// assert_eq!(assets.contains_only("uatom"), None); + /// ``` + pub fn contains_only(&self, name: impl Into) -> Option { + if self.len() == 1 { + self.0.get(&name.into()).map(|c| c.amount) + } else { + None + } + } + + /// Adds the given asset to this `AnsAssets` instance. + /// Errors in case of overflow. + pub fn add(&mut self, asset: AnsAsset) -> StdResult<()> { + if asset.amount.is_zero() { + return Ok(()); + } + + // if the asset is not present yet, insert it, otherwise add to existing amount + match self.0.get_mut(&asset.name) { + None => { + self.0.insert(asset.name.clone(), asset); + } + Some(existing) => { + existing.amount = existing.amount.checked_add(asset.amount)?; + } + } + Ok(()) + } + + /// Subtracts the given asset from this `AnsAssets` instance. + /// Errors in case of overflow or if the denom is not present. + pub fn sub(&mut self, asset: AnsAsset) -> StdResult<()> { + match self.0.get_mut(&asset.name) { + Some(existing) => { + existing.amount = existing.amount.checked_sub(asset.amount)?; + // make sure to remove zero asset + if existing.amount.is_zero() { + self.0.remove(&asset.name); + } + } + None => { + // ignore zero subtraction + if asset.amount.is_zero() { + return Ok(()); + } + return Err(OverflowError::new( + OverflowOperation::Sub, + Uint128::zero(), + asset.amount, + ) + .into()); + } + } + + Ok(()) + } + + /// Returns an iterator over the assets. + /// + /// # Examples + /// + /// ``` + /// # use cosmwasm_std::Uint128; + /// use abstract_app::objects::AnsAsset; + /// use carrot_app::ans_assets::AnsAssets; + /// let mut assets = AnsAssets::default(); + /// assets.add(AnsAsset::new("uluna", 500u128)).unwrap(); + /// assets.add(AnsAsset::new("uatom", 1000u128)).unwrap(); + /// let mut iterator = assets.iter(); + /// + /// let uatom = iterator.next().unwrap(); + /// assert_eq!(uatom.name.to_string(), "uatom"); + /// assert_eq!(uatom.amount.u128(), 1000); + /// + /// let uluna = iterator.next().unwrap(); + /// assert_eq!(uluna.name.to_string(), "uluna"); + /// assert_eq!(uluna.amount.u128(), 500); + /// + /// assert_eq!(iterator.next(), None); + /// ``` + pub fn iter(&self) -> AnsAssetsIter<'_> { + AnsAssetsIter(self.0.iter()) + } + + /// This is added by Abstract to extend the object with an iterator of assets + pub fn extend(&mut self, other: impl IntoIterator) -> StdResult<()> { + other.into_iter().try_for_each(|a| self.add(a))?; + + Ok(()) + } +} + +impl IntoIterator for AnsAssets { + type Item = AnsAsset; + type IntoIter = AnsAssetsIntoIter; + + fn into_iter(self) -> Self::IntoIter { + AnsAssetsIntoIter(self.0.into_iter()) + } +} + +impl<'a> IntoIterator for &'a AnsAssets { + type Item = &'a AnsAsset; + type IntoIter = AnsAssetsIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +#[derive(Debug)] +pub struct AnsAssetsIntoIter(alloc::collections::btree_map::IntoIter); + +impl Iterator for AnsAssetsIntoIter { + type Item = AnsAsset; + + fn next(&mut self) -> Option { + self.0.next().map(|(_, asset)| asset) + } + + fn size_hint(&self) -> (usize, Option) { + // Since btree_map::IntoIter implements ExactSizeIterator, this is guaranteed to return the exact length + self.0.size_hint() + } +} + +impl DoubleEndedIterator for AnsAssetsIntoIter { + fn next_back(&mut self) -> Option { + self.0.next_back().map(|(_, asset)| asset) + } +} + +impl ExactSizeIterator for AnsAssetsIntoIter { + fn len(&self) -> usize { + self.0.len() + } +} + +#[derive(Debug)] +pub struct AnsAssetsIter<'a>(alloc::collections::btree_map::Iter<'a, AssetEntry, AnsAsset>); + +impl<'a> Iterator for AnsAssetsIter<'a> { + type Item = &'a AnsAsset; + + fn next(&mut self) -> Option { + self.0.next().map(|(_, asset)| asset) + } + + fn size_hint(&self) -> (usize, Option) { + // Since btree_map::Iter implements ExactSizeIterator, this is guaranteed to return the exact length + self.0.size_hint() + } +} + +impl<'a> DoubleEndedIterator for AnsAssetsIter<'a> { + fn next_back(&mut self) -> Option { + self.0.next_back().map(|(_, asset)| asset) + } +} + +impl<'a> ExactSizeIterator for AnsAssetsIter<'a> { + fn len(&self) -> usize { + self.0.len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Sort a Vec by denom alphabetically + fn sort_by_denom(vec: &mut [AnsAsset]) { + vec.sort_by(|a, b| a.name.cmp(&b.name)); + } + + /// Returns a mockup Vec. In this example, the assets are not in order + fn mock_vec() -> Vec { + vec![ + AnsAsset::new("uatom", 12345u128), + AnsAsset::new("ibc/1234ABCD", 69420u128), + AnsAsset::new("factory/osmo1234abcd/subdenom", 88888u128), + ] + } + + /// Return a mockup AnsAssets that contains the same assets as in `mock_vec` + fn mock_assets() -> AnsAssets { + let mut assets = AnsAssets::default(); + for asset in mock_vec() { + assets.add(asset).unwrap(); + } + assets + } + + #[test] + fn converting_vec() { + let mut vec = mock_vec(); + let assets = mock_assets(); + + // &[AnsAsset] --> AnsAssets + assert_eq!(AnsAssets::try_from(vec.as_slice()).unwrap(), assets); + // Vec --> AnsAssets + assert_eq!(AnsAssets::try_from(vec.clone()).unwrap(), assets); + + sort_by_denom(&mut vec); + + // &AnsAssets --> Vec + // NOTE: the returned vec should be sorted + assert_eq!(assets.to_vec(), vec); + // AnsAssets --> Vec + // NOTE: the returned vec should be sorted + assert_eq!(assets.into_vec(), vec); + } + + #[test] + fn handling_duplicates() { + // create a Vec that contains duplicate denoms + let mut vec = mock_vec(); + vec.push(AnsAsset::new("uatom", 67890u128)); + + let err = AnsAssets::try_from(vec).unwrap_err(); + assert_eq!(err, AnsAssetsError::DuplicateDenom); + } + + #[test] + fn handling_zero_amount() { + // create a Vec that contains zero amounts + let mut vec = mock_vec(); + vec[0].amount = Uint128::zero(); + + let assets = AnsAssets::try_from(vec).unwrap(); + assert_eq!(assets.len(), 2); + assert_ne!(assets.amount_of("ibc/1234ABCD"), Uint128::zero()); + assert_ne!( + assets.amount_of("factory/osmo1234abcd/subdenom"), + Uint128::zero() + ); + + // adding a asset with zero amount should not be added + let mut assets = AnsAssets::default(); + assets.add(AnsAsset::new("uusd", 0u128)).unwrap(); + assert!(assets.is_empty()); + } + + #[test] + fn length() { + let assets = AnsAssets::default(); + assert_eq!(assets.len(), 0); + assert!(assets.is_empty()); + + let assets = mock_assets(); + assert_eq!(assets.len(), 3); + assert!(!assets.is_empty()); + } + + #[test] + fn add_asset() { + let mut assets = mock_assets(); + + // existing denom + assets.add(AnsAsset::new("uatom", 12345u128)).unwrap(); + assert_eq!(assets.len(), 3); + assert_eq!(assets.amount_of("uatom").u128(), 24690); + + // new denom + assets.add(AnsAsset::new("uusd", 123u128)).unwrap(); + assert_eq!(assets.len(), 4); + + // zero amount + assets.add(AnsAsset::new("uusd", 0u128)).unwrap(); + assert_eq!(assets.amount_of("uusd").u128(), 123); + + // zero amount, new denom + assets.add(AnsAsset::new("utest", 0u128)).unwrap(); + assert_eq!(assets.len(), 4); + } + + #[test] + fn sub_assets() { + let mut assets: AnsAssets = AnsAsset::new("uatom", 12345u128).into(); + + // sub more than available + let err = assets.sub(AnsAsset::new("uatom", 12346u128)).unwrap_err(); + assert!(matches!(err, StdError::Overflow { .. })); + + // sub non-existent denom + let err = assets.sub(AnsAsset::new("uusd", 12345u128)).unwrap_err(); + assert!(matches!(err, StdError::Overflow { .. })); + + // partial sub + assets.sub(AnsAsset::new("uatom", 1u128)).unwrap(); + assert_eq!(assets.len(), 1); + assert_eq!(assets.amount_of("uatom").u128(), 12344); + + // full sub + assets.sub(AnsAsset::new("uatom", 12344u128)).unwrap(); + assert!(assets.is_empty()); + + // sub zero, existing denom + assets.sub(AnsAsset::new("uusd", 0u128)).unwrap(); + assert!(assets.is_empty()); + let mut assets: AnsAssets = AnsAsset::new("uatom", 12345u128).into(); + + // sub zero, non-existent denom + assets.sub(AnsAsset::new("uatom", 0u128)).unwrap(); + assert_eq!(assets.len(), 1); + assert_eq!(assets.amount_of("uatom").u128(), 12345); + } + + #[test] + fn asset_to_assets() { + // zero asset results in empty collection + let assets: AnsAssets = AnsAsset::new("uusd", 0u128).into(); + assert!(assets.is_empty()); + + // happy path + let assets = AnsAssets::from(AnsAsset::new("uatom", 12345u128)); + assert_eq!(assets.len(), 1); + assert_eq!(assets.amount_of("uatom").u128(), 12345); + } + + #[test] + fn exact_size_iterator() { + let assets = mock_assets(); + let iter = assets.iter(); + assert_eq!(iter.len(), 3); + assert_eq!(iter.size_hint(), (3, Some(3))); + + let iter = assets.into_iter(); + assert_eq!(iter.len(), 3); + assert_eq!(iter.size_hint(), (3, Some(3))); + } + + #[test] + fn can_iterate_owned() { + let assets = mock_assets(); + let mut moved = AnsAssets::default(); + for c in assets { + moved.add(c).unwrap(); + } + assert_eq!(moved.len(), 3); + + assert!(mock_assets().into_iter().eq(mock_assets().to_vec())); + } + + #[test] + fn can_iterate_borrowed() { + let assets = mock_assets(); + assert!(assets + .iter() + .map(|c| &c.name) + .eq(assets.to_vec().iter().map(|c| &c.name))); + + // can still use the assets afterwards + assert_eq!(assets.amount_of("uatom").u128(), 12345); + } +} diff --git a/contracts/carrot-app/src/check.rs b/contracts/carrot-app/src/check.rs index 7e16e3db..9c40f8ee 100644 --- a/contracts/carrot-app/src/check.rs +++ b/contracts/carrot-app/src/check.rs @@ -114,7 +114,6 @@ mod yield_sources { use std::marker::PhantomData; use cosmwasm_std::{ensure, ensure_eq, Decimal, Deps}; - use cw_asset::AssetInfo; use osmosis_std::types::osmosis::{ concentratedliquidity::v1beta1::Pool, poolmanager::v1beta1::PoolmanagerQuerier, }; @@ -214,17 +213,11 @@ mod yield_sources { AppError::InvalidEmptyStrategy {} ); // We ensure all deposited tokens exist in ANS - let all_denoms = self.all_denoms(deps, app)?; + let all_names = self.all_names()?; let ans = app.name_service(deps); ans.host() - .query_assets_reverse( - &deps.querier, - &all_denoms - .iter() - .map(|denom| AssetInfo::native(denom.clone())) - .collect::>(), - ) - .map_err(|_| AppError::AssetsNotRegistered(all_denoms))?; + .query_assets(&deps.querier, &all_names) + .map_err(|_| AppError::AssetsNotRegistered(all_names))?; let params = match self.params { YieldParamsBase::ConcentratedLiquidityPool(params) => { @@ -243,12 +236,10 @@ mod yield_sources { 1, AppError::InvalidStrategy {} ); - // We verify the first element correspond to the mars deposit denom - let ans = app.name_service(deps); - let asset = ans.query(&AssetInfo::native(params.denom.clone()))?; + // We verify the first element correspond to the mars deposit asset ensure_eq!( self.asset_distribution[0].asset, - asset, + params.asset, AppError::InvalidStrategy {} ); YieldParamsBase::Mars(params.check(deps, app)?) diff --git a/contracts/carrot-app/src/distribution/deposit.rs b/contracts/carrot-app/src/distribution/deposit.rs index 2b624df4..6065e0ba 100644 --- a/contracts/carrot-app/src/distribution/deposit.rs +++ b/contracts/carrot-app/src/distribution/deposit.rs @@ -1,12 +1,12 @@ use std::collections::HashMap; -use cosmwasm_std::{coin, Coin, Coins, Decimal, Deps, Uint128}; -use cw_asset::AssetInfo; +use cosmwasm_std::{Decimal, Deps, Uint128}; use crate::{ + ans_assets::AnsAssets, contract::{App, AppResult}, exchange_rate::query_all_exchange_rates, - helpers::{compute_total_value, compute_value, unwrap_native}, + helpers::{compute_total_value, compute_value}, state::STRATEGY_CONFIG, yield_sources::{yield_type::YieldType, AssetShare, Strategy, StrategyElement}, }; @@ -14,11 +14,11 @@ use crate::{ use cosmwasm_schema::cw_serde; use crate::{error::AppError, msg::InternalExecuteMsg}; -use abstract_app::traits::AbstractNameService; +use abstract_app::objects::{AnsAsset, AssetEntry}; pub fn generate_deposit_strategy( deps: Deps, - funds: Vec, + mut usable_funds: AnsAssets, yield_source_params: Option>>>, app: &App, ) -> AppResult<(Vec<(StrategyElement, Decimal)>, Vec)> { @@ -28,7 +28,6 @@ pub fn generate_deposit_strategy( // This is the current distribution of funds inside the strategies let current_strategy_status = target_strategy.query_current_status(deps, app)?; - let mut usable_funds: Coins = funds.try_into()?; let (withdraw_strategy, mut this_deposit_strategy) = target_strategy.current_deposit_strategy( deps, &mut usable_funds, @@ -43,8 +42,7 @@ pub fn generate_deposit_strategy( this_deposit_strategy.correct_with(yield_source_params); // We fill the strategies with the current deposited funds and get messages to execute those deposits - let deposit_msgs = - this_deposit_strategy.fill_all_and_get_messages(deps, usable_funds.into(), app)?; + let deposit_msgs = this_deposit_strategy.fill_all_and_get_messages(deps, usable_funds, app)?; Ok((withdraw_strategy, deposit_msgs)) } @@ -56,7 +54,7 @@ impl Strategy { pub fn current_deposit_strategy( &self, deps: Deps, - funds: &mut Coins, + funds: &mut AnsAssets, current_strategy_status: Self, app: &App, ) -> AppResult<(Vec<(StrategyElement, Decimal)>, Self)> { @@ -160,14 +158,13 @@ impl Strategy { // This is the algorithm that is implemented here fn fill_sources( &self, - deps: Deps, - funds: Vec, + _deps: Deps, + assets: AnsAssets, exchange_rates: &HashMap, - app: &App, - ) -> AppResult<(StrategyStatus, Coins)> { - let total_value = compute_total_value(&funds, exchange_rates)?; - let mut remaining_funds = Coins::default(); - let ans = app.name_service(deps); + _app: &App, + ) -> AppResult<(StrategyStatus, AnsAssets)> { + let total_value = compute_total_value(&assets.to_vec(), exchange_rates)?; + let mut remaining_funds = AnsAssets::default(); // We create the vector that holds the funds information let mut yield_source_status = self @@ -179,17 +176,15 @@ impl Strategy { .asset_distribution .iter() .map(|AssetShare { asset, share }| { - let denom = unwrap_native(&ans.query(asset)?)?; - // Amount to fill this denom completely is value / exchange_rate // Value we want to put here is share * source.share * total_value Ok::<_, AppError>(StrategyStatusElement { - denom: denom.clone(), + asset: asset.clone(), raw_funds: Uint128::zero(), remaining_amount: (share * source.share / exchange_rates - .get(&denom) - .ok_or(AppError::NoExchangeRate(denom.clone()))?) + .get(&asset.to_string()) + .ok_or(AppError::NoExchangeRate(asset.clone()))?) * total_value, }) }) @@ -197,9 +192,8 @@ impl Strategy { }) .collect::, _>>()?; - for this_coin in funds { - let mut remaining_amount = this_coin.amount; - let this_asset = ans.query(&AssetInfo::native(this_coin.denom.clone()))?; + for this_asset in assets { + let mut remaining_amount = this_asset.amount; // We distribute those funds in to the accepting strategies for (strategy, status) in self.0.iter().zip(yield_source_status.iter_mut()) { // Find the share for the specific denom inside the strategy @@ -208,7 +202,7 @@ impl Strategy { .asset_distribution .iter() .zip(status.iter_mut()) - .find(|(AssetShare { asset, share: _ }, _status)| this_asset.eq(asset)) + .find(|(AssetShare { asset, share: _ }, _status)| this_asset.name.eq(asset)) .map(|(_, status)| status); if let Some(status) = this_denom_status { @@ -222,7 +216,7 @@ impl Strategy { status.remaining_amount -= funds_to_use_here; } } - remaining_funds.add(coin(remaining_amount.into(), this_coin.denom))?; + remaining_funds.add(AnsAsset::new(this_asset.name, remaining_amount))?; } Ok((yield_source_status.into(), remaining_funds)) @@ -231,19 +225,19 @@ impl Strategy { fn fill_all( &self, deps: Deps, - funds: Vec, + assets: AnsAssets, app: &App, ) -> AppResult> { // We determine the value of all tokens that will be used inside this function let exchange_rates = query_all_exchange_rates( deps, - funds + assets .iter() - .map(|f| f.denom.clone()) - .chain(self.all_denoms(deps, app)?), + .map(|f| f.name.clone()) + .chain(self.all_names()?), app, )?; - let (status, remaining_funds) = self.fill_sources(deps, funds, &exchange_rates, app)?; + let (status, remaining_funds) = self.fill_sources(deps, assets, &exchange_rates, app)?; status.fill_with_remaining_funds(remaining_funds, &exchange_rates) } @@ -251,10 +245,10 @@ impl Strategy { pub fn fill_all_and_get_messages( &self, deps: Deps, - funds: Vec, + assets: AnsAssets, app: &App, ) -> AppResult> { - let deposit_strategies = self.fill_all(deps, funds, app)?; + let deposit_strategies = self.fill_all(deps, assets, app)?; Ok(deposit_strategies .iter() .zip(self.0.iter().map(|s| s.yield_source.params.clone())) @@ -278,7 +272,7 @@ impl Strategy { #[cw_serde] struct StrategyStatusElement { - pub denom: String, + pub asset: AssetEntry, pub raw_funds: Uint128, pub remaining_amount: Uint128, } @@ -298,7 +292,7 @@ impl From>> for StrategyStatus { impl StrategyStatus { pub fn fill_with_remaining_funds( &self, - mut funds: Coins, + mut funds: AnsAssets, exchange_rates: &HashMap, ) -> AppResult> { self.0 @@ -310,11 +304,11 @@ impl StrategyStatus { let mut swaps = vec![]; for fund in funds.to_vec() { let direct_e_r = exchange_rates - .get(&fund.denom) - .ok_or(AppError::NoExchangeRate(fund.denom.clone()))? + .get(&fund.name.to_string()) + .ok_or(AppError::NoExchangeRate(fund.name.clone()))? / exchange_rates - .get(&status.denom) - .ok_or(AppError::NoExchangeRate(status.denom.clone()))?; + .get(&status.asset.to_string()) + .ok_or(AppError::NoExchangeRate(status.asset.clone()))?; let available_coin_in_destination_amount = fund.amount * direct_e_r; let fill_amount = @@ -324,18 +318,18 @@ impl StrategyStatus { if swap_in_amount != Uint128::zero() { status.remaining_amount -= fill_amount; - let swap_funds = coin(swap_in_amount.into(), fund.denom); + let swap_funds = AnsAsset::new(fund.name, swap_in_amount); funds.sub(swap_funds.clone())?; swaps.push(DepositStep::Swap { asset_in: swap_funds, - denom_out: status.denom.clone(), + denom_out: status.asset.clone(), expected_amount: fill_amount, }); } } if !status.raw_funds.is_zero() { swaps.push(DepositStep::UseFunds { - asset: coin(status.raw_funds.into(), status.denom.clone()), + asset: AnsAsset::new(status.asset.clone(), status.raw_funds), }) } @@ -351,12 +345,12 @@ impl StrategyStatus { #[cw_serde] pub enum DepositStep { Swap { - asset_in: Coin, - denom_out: String, + asset_in: AnsAsset, + denom_out: AssetEntry, expected_amount: Uint128, }, UseFunds { - asset: Coin, + asset: AnsAsset, }, } diff --git a/contracts/carrot-app/src/distribution/query.rs b/contracts/carrot-app/src/distribution/query.rs index 149e1d5c..bc5410b6 100644 --- a/contracts/carrot-app/src/distribution/query.rs +++ b/contracts/carrot-app/src/distribution/query.rs @@ -1,7 +1,7 @@ -use cosmwasm_std::{Coins, Decimal, Deps, Uint128}; -use cw_asset::AssetInfo; +use cosmwasm_std::{Decimal, Deps, Uint128}; use crate::{ + ans_assets::AnsAssets, contract::{App, AppResult}, error::AppError, exchange_rate::query_exchange_rate, @@ -10,12 +10,10 @@ use crate::{ yield_type::YieldTypeImplementation, AssetShare, Strategy, StrategyElement, YieldSource, }, }; -use abstract_app::traits::AbstractNameService; - impl Strategy { // Returns the total balance pub fn current_balance(&self, deps: Deps, app: &App) -> AppResult { - let mut funds = Coins::default(); + let mut funds = AnsAssets::default(); let mut total_value = Uint128::zero(); self.0.iter().try_for_each(|s| { let deposit_value = s @@ -24,7 +22,7 @@ impl Strategy { .user_deposit(deps, app) .unwrap_or_default(); for fund in deposit_value { - let exchange_rate = query_exchange_rate(deps, fund.denom.clone(), app)?; + let exchange_rate = query_exchange_rate(deps, &fund.name, app)?; funds.add(fund.clone())?; total_value += fund.amount * exchange_rate; } @@ -110,9 +108,9 @@ impl StrategyElement { let each_value = user_deposit .iter() .map(|fund| { - let exchange_rate = query_exchange_rate(deps, fund.denom.clone(), app)?; + let exchange_rate = query_exchange_rate(deps, &fund.name, app)?; - Ok::<_, AppError>((fund.denom.clone(), exchange_rate * fund.amount)) + Ok::<_, AppError>((fund.name.clone(), exchange_rate * fund.amount)) }) .collect::, _>>()?; @@ -125,12 +123,10 @@ impl StrategyElement { self.yield_source.asset_distribution.clone(), )); } - let ans = app.name_service(deps); let each_shares = each_value .into_iter() - .map(|(denom, amount)| { - let asset = ans.query(&AssetInfo::native(denom))?; + .map(|(asset, amount)| { Ok::<_, AppError>(AssetShare { asset, share: Decimal::from_ratio(amount, total_value), diff --git a/contracts/carrot-app/src/distribution/rewards.rs b/contracts/carrot-app/src/distribution/rewards.rs index b416c582..eb1836f2 100644 --- a/contracts/carrot-app/src/distribution/rewards.rs +++ b/contracts/carrot-app/src/distribution/rewards.rs @@ -1,7 +1,8 @@ use abstract_sdk::{AccountAction, Execution, ExecutorMsg}; -use cosmwasm_std::{Coin, Deps}; +use cosmwasm_std::Deps; use crate::{ + ans_assets::AnsAssets, contract::{App, AppResult}, error::AppError, yield_sources::{yield_type::YieldTypeImplementation, Strategy}, @@ -12,23 +13,25 @@ impl Strategy { self, deps: Deps, app: &App, - ) -> AppResult<(Vec, Vec)> { - let (rewards, msgs): (Vec>, _) = self + ) -> AppResult<(AnsAssets, Vec)> { + let mut all_rewards = AnsAssets::default(); + + let msgs = self .0 .into_iter() .map(|s| { let (rewards, raw_msgs) = s.yield_source.params.withdraw_rewards(deps, app)?; - Ok::<_, AppError>(( - rewards, + for asset in rewards { + all_rewards.add(asset)?; + } + Ok::<_, AppError>( app.executor(deps) .execute(vec![AccountAction::from_vec(raw_msgs)])?, - )) + ) }) - .collect::, _>>()? - .into_iter() - .unzip(); + .collect::, _>>()?; - Ok((rewards.into_iter().flatten().collect(), msgs)) + Ok((all_rewards, msgs)) } } diff --git a/contracts/carrot-app/src/distribution/withdraw.rs b/contracts/carrot-app/src/distribution/withdraw.rs index c69029d7..83db5843 100644 --- a/contracts/carrot-app/src/distribution/withdraw.rs +++ b/contracts/carrot-app/src/distribution/withdraw.rs @@ -1,7 +1,9 @@ +use abstract_app::objects::AnsAsset; use abstract_sdk::{AccountAction, Execution, ExecutorMsg}; -use cosmwasm_std::{Coin, Decimal, Deps}; +use cosmwasm_std::{Decimal, Deps}; use crate::{ + ans_assets::AnsAssets, contract::{App, AppResult}, error::AppError, yield_sources::{yield_type::YieldTypeImplementation, Strategy, StrategyElement}, @@ -52,17 +54,15 @@ impl StrategyElement { deps: Deps, withdraw_share: Option, app: &App, - ) -> AppResult> { + ) -> AppResult { let current_deposit = self.yield_source.params.user_deposit(deps, app)?; if let Some(share) = withdraw_share { Ok(current_deposit .into_iter() - .map(|funds| Coin { - denom: funds.denom, - amount: funds.amount * share, - }) - .collect()) + .map(|funds| AnsAsset::new(funds.name, funds.amount * share)) + .collect::>() + .try_into()?) } else { Ok(current_deposit) } diff --git a/contracts/carrot-app/src/error.rs b/contracts/carrot-app/src/error.rs index 89371415..b9e1e4dd 100644 --- a/contracts/carrot-app/src/error.rs +++ b/contracts/carrot-app/src/error.rs @@ -1,12 +1,15 @@ use abstract_app::abstract_sdk::AbstractSdkError; +use abstract_app::objects::AssetEntry; use abstract_app::AppError as AbstractAppError; use abstract_app::{abstract_core::AbstractError, objects::ans_host::AnsHostError}; -use cosmwasm_std::{Coin, Decimal, StdError}; +use cosmwasm_std::{Coin, CoinsError, Decimal, StdError}; use cw_asset::{AssetError, AssetInfo}; use cw_controllers::AdminError; use cw_utils::ParseReplyError; use thiserror::Error; +use crate::ans_assets::AnsAssetsError; + #[derive(Error, Debug, PartialEq)] pub enum AppError { #[error("{0}")] @@ -37,7 +40,10 @@ pub enum AppError { ProstDecodeError(#[from] prost::DecodeError), #[error(transparent)] - CoinsError(#[from] cosmwasm_std::CoinsError), + AnsAssets(#[from] AnsAssetsError), + + #[error(transparent)] + Coins(#[from] CoinsError), #[error("Unauthorized")] Unauthorized {}, @@ -58,7 +64,7 @@ pub enum AppError { PoolNotFound {}, #[error("Deposit assets were not found in Abstract ANS : {0:?}")] - AssetsNotRegistered(Vec), + AssetsNotRegistered(Vec), #[error("No swap fund to swap assets into each other")] NoSwapPossibility {}, @@ -93,7 +99,7 @@ pub enum AppError { InvalidEmptyStrategy {}, #[error("Exchange Rate not given for {0}")] - NoExchangeRate(String), + NoExchangeRate(AssetEntry), #[error("Deposited total value is zero")] NoDeposit {}, diff --git a/contracts/carrot-app/src/exchange_rate.rs b/contracts/carrot-app/src/exchange_rate.rs index 649f61f6..3f30116c 100644 --- a/contracts/carrot-app/src/exchange_rate.rs +++ b/contracts/carrot-app/src/exchange_rate.rs @@ -1,14 +1,11 @@ use std::collections::HashMap; +use abstract_app::objects::AssetEntry; use cosmwasm_std::{Decimal, Deps}; use crate::contract::{App, AppResult}; -pub fn query_exchange_rate( - _deps: Deps, - _denom: impl Into, - _app: &App, -) -> AppResult { +pub fn query_exchange_rate(_deps: Deps, _name: &AssetEntry, _app: &App) -> AppResult { // In the first iteration, all deposited tokens are assumed to be equal to 1 Ok(Decimal::one()) } @@ -16,11 +13,11 @@ pub fn query_exchange_rate( // Returns a hashmap with all request exchange rates pub fn query_all_exchange_rates( deps: Deps, - denoms: impl Iterator, + assets: impl Iterator, app: &App, ) -> AppResult> { - denoms + assets .into_iter() - .map(|denom| Ok((denom.clone(), query_exchange_rate(deps, denom, app)?))) + .map(|asset| Ok((asset.to_string(), query_exchange_rate(deps, &asset, app)?))) .collect() } diff --git a/contracts/carrot-app/src/handlers/execute.rs b/contracts/carrot-app/src/handlers/execute.rs index 395dabad..905f823b 100644 --- a/contracts/carrot-app/src/handlers/execute.rs +++ b/contracts/carrot-app/src/handlers/execute.rs @@ -1,4 +1,6 @@ +use super::internal::execute_internal_action; use crate::{ + ans_assets::AnsAssets, autocompound::AutocompoundState, check::Checkable, contract::{App, AppResult}, @@ -10,13 +12,11 @@ use crate::{ state::{AUTOCOMPOUND_STATE, CONFIG, STRATEGY_CONFIG}, yield_sources::{AssetShare, StrategyUnchecked}, }; -use abstract_app::abstract_sdk::features::AbstractResponse; +use abstract_app::{abstract_sdk::features::AbstractResponse, objects::AnsAsset}; use abstract_sdk::ExecutorMsg; use cosmwasm_std::{ - to_json_binary, Coin, Coins, CosmosMsg, Decimal, Deps, DepsMut, Env, MessageInfo, Uint128, - WasmMsg, + to_json_binary, CosmosMsg, Decimal, Deps, DepsMut, Env, MessageInfo, Uint128, WasmMsg, }; -use super::internal::execute_internal_action; pub fn execute_handler( deps: DepsMut, @@ -27,13 +27,13 @@ pub fn execute_handler( ) -> AppResult { match msg { AppExecuteMsg::Deposit { - funds, + assets, yield_sources_params, - } => deposit(deps, env, info, funds, yield_sources_params, app), + } => deposit(deps, env, info, assets, yield_sources_params, app), AppExecuteMsg::Withdraw { amount } => withdraw(deps, env, info, amount, app), AppExecuteMsg::Autocompound {} => autocompound(deps, env, info, app), - AppExecuteMsg::UpdateStrategy { strategy, funds } => { - update_strategy(deps, env, info, strategy, funds, app) + AppExecuteMsg::UpdateStrategy { strategy, assets } => { + update_strategy(deps, env, info, strategy, assets, app) } // Endpoints called by the contract directly AppExecuteMsg::Internal(internal_msg) => { @@ -47,7 +47,7 @@ fn deposit( deps: DepsMut, env: Env, info: MessageInfo, - funds: Vec, + assets: Vec, yield_source_params: Option>>>, app: App, ) -> AppResult { @@ -56,7 +56,13 @@ fn deposit( .assert_admin(deps.as_ref(), &info.sender) .or(assert_contract(&info, &env))?; - let deposit_msgs = _inner_deposit(deps.as_ref(), &env, funds, yield_source_params, &app)?; + let deposit_msgs = _inner_deposit( + deps.as_ref(), + &env, + assets.try_into()?, + yield_source_params, + &app, + )?; AUTOCOMPOUND_STATE.save( deps.storage, @@ -88,7 +94,7 @@ fn update_strategy( env: Env, _info: MessageInfo, strategy: StrategyUnchecked, - funds: Vec, + assets: Vec, app: App, ) -> AppResult { // We load it raw because we're changing the strategy @@ -98,7 +104,7 @@ fn update_strategy( let strategy = strategy.check(deps.as_ref(), &app)?; // We execute operations to rebalance the funds between the strategies - let mut available_funds: Coins = funds.try_into()?; + let mut available_assets: AnsAssets = assets.try_into()?; // 1. We withdraw all yield_sources that are not included in the new strategies let all_stale_sources: Vec<_> = old_strategy .0 @@ -106,29 +112,22 @@ fn update_strategy( .filter(|x| !strategy.0.contains(x)) .collect(); - let (withdrawn_funds, withdraw_msgs): (Vec>, Vec>) = - all_stale_sources - .into_iter() - .map(|s| { - Ok::<_, AppError>(( - s.withdraw_preview(deps.as_ref(), None, &app) - .unwrap_or_default(), - s.withdraw(deps.as_ref(), None, &app).ok(), - )) - }) - .collect::, _>>()? - .into_iter() - .unzip(); - - withdrawn_funds + let withdraw_msgs = all_stale_sources .into_iter() - .try_for_each(|f| f.into_iter().try_for_each(|f| available_funds.add(f)))?; + .map(|s| { + let assets = s + .withdraw_preview(deps.as_ref(), None, &app) + .unwrap_or_default(); + available_assets.extend(assets)?; + Ok::<_, AppError>(s.withdraw(deps.as_ref(), None, &app).ok()) + }) + .collect::, _>>()?; // 2. We replace the strategy with the new strategy STRATEGY_CONFIG.save(deps.storage, &strategy)?; // 3. We deposit the funds into the new strategy - let deposit_msgs = _inner_deposit(deps.as_ref(), &env, available_funds.into(), None, &app)?; + let deposit_msgs = _inner_deposit(deps.as_ref(), &env, available_assets, None, &app)?; Ok(app .response("rebalance") @@ -159,7 +158,7 @@ fn autocompound(deps: DepsMut, env: Env, info: MessageInfo, app: App) -> AppResu let msg_deposit = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: env.contract.address.to_string(), msg: to_json_binary(&ExecuteMsg::Module(AppExecuteMsg::Deposit { - funds: all_rewards, + assets: all_rewards.into(), yield_sources_params: None, }))?, funds: vec![], @@ -232,12 +231,12 @@ fn autocompound(deps: DepsMut, env: Env, info: MessageInfo, app: App) -> AppResu pub fn _inner_deposit( deps: Deps, env: &Env, - funds: Vec, + assets: AnsAssets, yield_source_params: Option>>>, app: &App, ) -> AppResult> { let (withdraw_strategy, deposit_msgs) = - generate_deposit_strategy(deps, funds, yield_source_params, app)?; + generate_deposit_strategy(deps, assets, yield_source_params, app)?; let deposit_withdraw_msgs = withdraw_strategy .into_iter() .map(|(el, share)| el.withdraw(deps, Some(share), app).map(Into::into)) diff --git a/contracts/carrot-app/src/handlers/instantiate.rs b/contracts/carrot-app/src/handlers/instantiate.rs index e4c93852..0c01e042 100644 --- a/contracts/carrot-app/src/handlers/instantiate.rs +++ b/contracts/carrot-app/src/handlers/instantiate.rs @@ -26,8 +26,8 @@ pub fn instantiate_handler( let mut response = app.response("instantiate_savings_app"); // If provided - do an initial deposit - if let Some(funds) = msg.deposit { - let deposit_msgs = _inner_deposit(deps.as_ref(), &env, funds, None, &app)?; + if let Some(assets) = msg.deposit { + let deposit_msgs = _inner_deposit(deps.as_ref(), &env, assets.try_into()?, None, &app)?; response = response.add_messages(deposit_msgs); } diff --git a/contracts/carrot-app/src/handlers/internal.rs b/contracts/carrot-app/src/handlers/internal.rs index 68f9477c..55d5f729 100644 --- a/contracts/carrot-app/src/handlers/internal.rs +++ b/contracts/carrot-app/src/handlers/internal.rs @@ -1,11 +1,12 @@ use crate::{ + ans_assets::AnsAssets, contract::{App, AppResult}, distribution::deposit::{DepositStep, OneDepositStrategy}, helpers::get_proxy_balance, msg::{AppExecuteMsg, ExecuteMsg, InternalExecuteMsg}, replies::REPLY_AFTER_SWAPS_STEP, state::{ - CONFIG, STRATEGY_CONFIG, TEMP_CURRENT_COIN, TEMP_CURRENT_YIELD, TEMP_DEPOSIT_COINS, + CONFIG, STRATEGY_CONFIG, TEMP_CURRENT_ASSET, TEMP_CURRENT_YIELD, TEMP_DEPOSIT_ASSETS, TEMP_EXPECTED_SWAP_COIN, }, yield_sources::{ @@ -13,11 +14,12 @@ use crate::{ Strategy, }, }; -use abstract_app::{abstract_sdk::features::AbstractResponse, objects::AnsAsset}; +use abstract_app::{ + abstract_sdk::features::AbstractResponse, + objects::{AnsAsset, AssetEntry}, +}; use abstract_dex_adapter::DexInterface; -use abstract_sdk::features::AbstractNameService; -use cosmwasm_std::{wasm_execute, Coin, Coins, DepsMut, Env, SubMsg, Uint128}; -use cw_asset::AssetInfo; +use cosmwasm_std::{wasm_execute, DepsMut, Env, SubMsg, Uint128}; use crate::exchange_rate::query_exchange_rate; @@ -54,7 +56,7 @@ fn deposit_one_strategy( app: App, ) -> AppResult { deps.api.debug("Start deposit one strategy"); - let mut temp_deposit_coins = Coins::default(); + let mut temp_deposit_assets = AnsAssets::default(); // We go through all deposit steps. // If the step is a swap, we execute with a reply to catch the amount change and get the exact deposit amount @@ -82,7 +84,7 @@ fn deposit_one_strategy( .map(|msg| Some(SubMsg::reply_on_success(msg, REPLY_AFTER_SWAPS_STEP))), DepositStep::UseFunds { asset } => { - temp_deposit_coins.add(asset)?; + temp_deposit_assets.add(asset)?; Ok(None) } }) @@ -90,7 +92,7 @@ fn deposit_one_strategy( }) .collect::, _>>()?; - TEMP_DEPOSIT_COINS.save(deps.storage, &temp_deposit_coins.into())?; + TEMP_DEPOSIT_ASSETS.save(deps.storage, &temp_deposit_assets.into())?; let msgs = msg.into_iter().flatten().flatten().collect::>(); @@ -115,35 +117,29 @@ fn deposit_one_strategy( pub fn execute_one_deposit_step( deps: DepsMut, _env: Env, - asset_in: Coin, - denom_out: String, + asset_in: AnsAsset, + asset_out: AssetEntry, expected_amount: Uint128, app: App, ) -> AppResult { let config = CONFIG.load(deps.storage)?; deps.api.debug("Start onde deposit step"); - let exchange_rate_in = query_exchange_rate(deps.as_ref(), asset_in.denom.clone(), &app)?; - let exchange_rate_out = query_exchange_rate(deps.as_ref(), denom_out.clone(), &app)?; - - let ans = app.name_service(deps.as_ref()); - - let asset_entries = ans.query(&vec![ - AssetInfo::native(asset_in.denom.clone()), - AssetInfo::native(denom_out.clone()), - ])?; - let in_asset = asset_entries[0].clone(); - let out_asset = asset_entries[1].clone(); + let exchange_rate_in = query_exchange_rate(deps.as_ref(), &asset_in.name, &app)?; + let exchange_rate_out = query_exchange_rate(deps.as_ref(), &asset_out, &app)?; let msg = app.ans_dex(deps.as_ref(), config.dex).swap( - AnsAsset::new(in_asset, asset_in.amount), - out_asset, + asset_in, + asset_out.clone(), None, Some(exchange_rate_in / exchange_rate_out), )?; - let proxy_balance_before = get_proxy_balance(deps.as_ref(), &app, denom_out)?; - TEMP_CURRENT_COIN.save(deps.storage, &proxy_balance_before)?; + let proxy_balance_before = get_proxy_balance(deps.as_ref(), &asset_out, &app)?; + TEMP_CURRENT_ASSET.save( + deps.storage, + &AnsAsset::new(asset_out, proxy_balance_before), + )?; TEMP_EXPECTED_SWAP_COIN.save(deps.storage, &expected_amount)?; Ok(app.response("one-deposit-step").add_message(msg)) @@ -156,11 +152,11 @@ pub fn execute_finalize_deposit( app: App, ) -> AppResult { deps.api.debug("Start finalize deposit"); - let available_deposit_coins = TEMP_DEPOSIT_COINS.load(deps.storage)?; + let available_deposit_assets = TEMP_DEPOSIT_ASSETS.load(deps.storage)?; TEMP_CURRENT_YIELD.save(deps.storage, &yield_index)?; - let msgs = yield_type.deposit(deps.as_ref(), available_deposit_coins, &app)?; + let msgs = yield_type.deposit(deps.as_ref(), available_deposit_assets.try_into()?, &app)?; deps.api.debug("End finalize deposit"); Ok(app.response("finalize-deposit").add_submessages(msgs)) diff --git a/contracts/carrot-app/src/handlers/preview.rs b/contracts/carrot-app/src/handlers/preview.rs index 18664a56..dc751a65 100644 --- a/contracts/carrot-app/src/handlers/preview.rs +++ b/contracts/carrot-app/src/handlers/preview.rs @@ -1,4 +1,5 @@ -use cosmwasm_std::{Coin, Deps, Uint128}; +use abstract_app::objects::AnsAsset; +use cosmwasm_std::{Deps, Uint128}; use crate::{ contract::{App, AppResult}, @@ -9,12 +10,12 @@ use crate::{ pub fn deposit_preview( deps: Deps, - funds: Vec, + assets: Vec, yield_source_params: Option>>>, app: &App, ) -> AppResult { let (withdraw_strategy, deposit_strategy) = - generate_deposit_strategy(deps, funds, yield_source_params, app)?; + generate_deposit_strategy(deps, assets.try_into()?, yield_source_params, app)?; Ok(DepositPreviewResponse { withdraw: withdraw_strategy @@ -34,7 +35,7 @@ pub fn withdraw_preview( } pub fn update_strategy_preview( deps: Deps, - funds: Vec, + assets: Vec, strategy: StrategyUnchecked, app: &App, ) -> AppResult { diff --git a/contracts/carrot-app/src/handlers/query.rs b/contracts/carrot-app/src/handlers/query.rs index d5e36a79..d15b3eb3 100644 --- a/contracts/carrot-app/src/handlers/query.rs +++ b/contracts/carrot-app/src/handlers/query.rs @@ -4,9 +4,10 @@ use abstract_app::{ traits::{AbstractNameService, Resolve}, }; use abstract_dex_adapter::DexInterface; -use cosmwasm_std::{to_json_binary, Binary, Coins, Deps, Env, Uint128}; +use cosmwasm_std::{to_json_binary, Binary, Deps, Env, Uint128}; use cw_asset::Asset; +use crate::ans_assets::AnsAssets; use crate::autocompound::get_autocompound_status; use crate::exchange_rate::query_exchange_rate; use crate::msg::{PositionResponse, PositionsResponse}; @@ -35,14 +36,14 @@ pub fn query_handler(deps: Deps, env: Env, app: &App, msg: AppQueryMsg) -> AppRe AppQueryMsg::StrategyStatus {} => to_json_binary(&query_strategy_status(deps, app)?), AppQueryMsg::Positions {} => to_json_binary(&query_positions(deps, app)?), AppQueryMsg::DepositPreview { - funds, + assets, yield_sources_params, - } => to_json_binary(&deposit_preview(deps, funds, yield_sources_params, app)?), + } => to_json_binary(&deposit_preview(deps, assets, yield_sources_params, app)?), AppQueryMsg::WithdrawPreview { amount } => { to_json_binary(&withdraw_preview(deps, amount, app)?) } - AppQueryMsg::UpdateStrategyPreview { strategy, funds } => { - to_json_binary(&update_strategy_preview(deps, funds, strategy, app)?) + AppQueryMsg::UpdateStrategyPreview { strategy, assets } => { + to_json_binary(&update_strategy_preview(deps, assets, strategy, app)?) } } .map_err(Into::into) @@ -119,7 +120,7 @@ fn query_config(deps: Deps) -> AppResult { } pub fn query_balance(deps: Deps, app: &App) -> AppResult { - let mut funds = Coins::default(); + let mut assets = AnsAssets::default(); let mut total_value = Uint128::zero(); let strategy = STRATEGY_CONFIG.load(deps.storage)?; @@ -129,16 +130,16 @@ pub fn query_balance(deps: Deps, app: &App) -> AppResult .params .user_deposit(deps, app) .unwrap_or_default(); - for fund in deposit_value { - let exchange_rate = query_exchange_rate(deps, fund.denom.clone(), app)?; - funds.add(fund.clone())?; - total_value += fund.amount * exchange_rate; + for asset in deposit_value { + let exchange_rate = query_exchange_rate(deps, &asset.name, app)?; + assets.add(asset.clone())?; + total_value += asset.amount * exchange_rate; } Ok::<_, AppError>(()) })?; Ok(AssetsBalanceResponse { - balances: funds.into(), + balances: assets.into(), total_value, }) } @@ -146,17 +147,17 @@ pub fn query_balance(deps: Deps, app: &App) -> AppResult fn query_rewards(deps: Deps, app: &App) -> AppResult { let strategy = STRATEGY_CONFIG.load(deps.storage)?; - let mut rewards = Coins::default(); + let mut available_rewards = AnsAssets::default(); strategy.0.into_iter().try_for_each(|s| { let this_rewards = s.yield_source.params.user_rewards(deps, app)?; - for fund in this_rewards { - rewards.add(fund)?; + for asset in this_rewards { + available_rewards.add(asset)?; } Ok::<_, AppError>(()) })?; Ok(AvailableRewardsResponse { - available_rewards: rewards.into(), + available_rewards: available_rewards.into(), }) } @@ -173,7 +174,7 @@ pub fn query_positions(deps: Deps, app: &App) -> AppResult { let total_value = balance .iter() .map(|fund| { - let exchange_rate = query_exchange_rate(deps, fund.denom.clone(), app)?; + let exchange_rate = query_exchange_rate(deps, &fund.name, app)?; Ok(fund.amount * exchange_rate) }) .sum::>()?; @@ -181,7 +182,7 @@ pub fn query_positions(deps: Deps, app: &App) -> AppResult { Ok::<_, AppError>(PositionResponse { params: s.yield_source.params.into(), balance: AssetsBalanceResponse { - balances: balance, + balances: balance.into(), total_value, }, liquidity, diff --git a/contracts/carrot-app/src/helpers.rs b/contracts/carrot-app/src/helpers.rs index 11473426..2aafa1e1 100644 --- a/contracts/carrot-app/src/helpers.rs +++ b/contracts/carrot-app/src/helpers.rs @@ -1,13 +1,15 @@ use std::collections::HashMap; +use crate::ans_assets::AnsAssets; use crate::contract::{App, AppResult}; use crate::error::AppError; use crate::exchange_rate::query_exchange_rate; +use abstract_app::objects::AnsAsset; use abstract_app::traits::AccountIdentification; use abstract_app::{objects::AssetEntry, traits::AbstractNameService}; -use abstract_sdk::Resolve; -use cosmwasm_std::{Addr, Coin, Coins, Decimal, Deps, Env, MessageInfo, StdResult, Uint128}; -use cw_asset::AssetInfo; +use abstract_sdk::{AbstractSdkResult, Resolve}; +use cosmwasm_std::{Addr, Coins, Decimal, Deps, Env, MessageInfo, StdResult, Uint128}; +use cw_asset::{Asset, AssetInfo}; pub fn unwrap_native(asset: &AssetInfo) -> AppResult { match asset { @@ -31,16 +33,15 @@ pub fn get_balance(a: AssetEntry, deps: Deps, address: Addr, app: &App) -> AppRe Ok(user_gas_balance) } -pub fn get_proxy_balance(deps: Deps, app: &App, denom: String) -> AppResult { - Ok(deps - .querier - .query_balance(app.account_base(deps)?.proxy, denom.clone())?) +pub fn get_proxy_balance(deps: Deps, asset: &AssetEntry, app: &App) -> AppResult { + let fund = app.name_service(deps).query(asset)?; + Ok(fund.query_balance(&deps.querier, app.account_base(deps)?.proxy)?) } -pub fn add_funds(funds: Vec, to_add: Coin) -> StdResult> { - let mut funds: Coins = funds.try_into()?; - funds.add(to_add)?; - Ok(funds.into()) +pub fn add_funds(assets: Vec, to_add: AnsAsset) -> StdResult> { + let mut assets: AnsAssets = assets.try_into()?; + assets.add(to_add)?; + Ok(assets.into()) } pub const CLOSE_COEFF: Decimal = Decimal::permille(1); @@ -56,30 +57,38 @@ pub fn close_to(expected: Decimal, actual: Decimal) -> bool { } pub fn compute_total_value( - funds: &[Coin], + funds: &[AnsAsset], exchange_rates: &HashMap, ) -> AppResult { funds .iter() .map(|c| { let exchange_rate = exchange_rates - .get(&c.denom) - .ok_or(AppError::NoExchangeRate(c.denom.clone()))?; + .get(&c.name.to_string()) + .ok_or(AppError::NoExchangeRate(c.name.clone()))?; Ok(c.amount * *exchange_rate) }) .sum() } -pub fn compute_value(deps: Deps, funds: &[Coin], app: &App) -> AppResult { +pub fn compute_value(deps: Deps, funds: &[AnsAsset], app: &App) -> AppResult { funds .iter() .map(|c| { - let exchange_rate = query_exchange_rate(deps, c.denom.clone(), app)?; + let exchange_rate = query_exchange_rate(deps, &c.name, app)?; Ok(c.amount * exchange_rate) }) .sum() } +pub fn to_ans_assets(deps: Deps, funds: Coins, app: &App) -> AbstractSdkResult> { + let ans = app.name_service(deps); + funds + .into_iter() + .map(|fund| ans.query(&Asset::from(fund))) + .collect() +} + #[cfg(test)] mod test { use std::str::FromStr; diff --git a/contracts/carrot-app/src/lib.rs b/contracts/carrot-app/src/lib.rs index 90580176..88e5d5c0 100644 --- a/contracts/carrot-app/src/lib.rs +++ b/contracts/carrot-app/src/lib.rs @@ -1,3 +1,4 @@ +pub mod ans_assets; pub mod autocompound; pub mod check; pub mod contract; diff --git a/contracts/carrot-app/src/msg.rs b/contracts/carrot-app/src/msg.rs index 58f99aef..a2ddc41e 100644 --- a/contracts/carrot-app/src/msg.rs +++ b/contracts/carrot-app/src/msg.rs @@ -1,5 +1,6 @@ +use abstract_app::objects::{AnsAsset, AssetEntry}; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{wasm_execute, Coin, CosmosMsg, Decimal, Env, Uint128, Uint64}; +use cosmwasm_std::{wasm_execute, CosmosMsg, Decimal, Env, Uint128, Uint64}; use cw_asset::AssetBase; use crate::{ @@ -24,7 +25,7 @@ pub struct AppInstantiateMsg { pub strategy: StrategyUnchecked, /// Create position with instantiation. /// Will not create position if omitted - pub deposit: Option>, + pub deposit: Option>, } /// App execute messages @@ -32,13 +33,13 @@ pub struct AppInstantiateMsg { #[cfg_attr(feature = "interface", derive(cw_orch::ExecuteFns))] #[cfg_attr(feature = "interface", impl_into(ExecuteMsg))] pub enum AppExecuteMsg { - /// Deposit funds onto the app - /// Those funds will be distributed between yield sources according to the current strategy + /// Deposit assets onto the app + /// Those assets will be distributed between yield sources according to the current strategy /// TODO : for now only send stable coins that have the same value as USD /// More tokens can be included when the oracle adapter is live Deposit { - funds: Vec, - /// This is additional paramters used to change the funds repartition when doing an additional deposit + assets: Vec, + /// This is additional paramters used to change the asset repartition when doing an additional deposit /// This is not used for a first deposit into a strategy that hasn't changed for instance /// This is an options because this is not mandatory /// The vector then has option inside of it because we might not want to change parameters for all strategies @@ -52,7 +53,7 @@ pub enum AppExecuteMsg { Autocompound {}, /// Rebalances all investments according to a new balance strategy UpdateStrategy { - funds: Vec, + assets: Vec, strategy: StrategyUnchecked, }, @@ -71,8 +72,8 @@ pub enum InternalExecuteMsg { }, /// Execute one Deposit Swap Step ExecuteOneDepositSwapStep { - asset_in: Coin, - denom_out: String, + asset_in: AnsAsset, + denom_out: AssetEntry, expected_amount: Uint128, }, /// Finalize the deposit after all swaps are executed @@ -137,7 +138,7 @@ pub enum AppQueryMsg { // Their arguments match the arguments of the corresponding Execute Endpoint #[returns(DepositPreviewResponse)] DepositPreview { - funds: Vec, + assets: Vec, yield_sources_params: Option>>>, }, #[returns(WithdrawPreviewResponse)] @@ -147,7 +148,7 @@ pub enum AppQueryMsg { /// Returns [`RebalancePreviewResponse`] #[returns(UpdateStrategyPreviewResponse)] UpdateStrategyPreview { - funds: Vec, + assets: Vec, strategy: StrategyUnchecked, }, } @@ -157,16 +158,16 @@ pub struct AppMigrateMsg {} #[cosmwasm_schema::cw_serde] pub struct BalanceResponse { - pub balance: Vec, + pub balance: Vec, } #[cosmwasm_schema::cw_serde] pub struct AvailableRewardsResponse { - pub available_rewards: Vec, + pub available_rewards: Vec, } #[cw_serde] pub struct AssetsBalanceResponse { - pub balances: Vec, + pub balances: Vec, pub total_value: Uint128, } diff --git a/contracts/carrot-app/src/replies/after_swaps.rs b/contracts/carrot-app/src/replies/after_swaps.rs index 02a24976..e07e2be2 100644 --- a/contracts/carrot-app/src/replies/after_swaps.rs +++ b/contracts/carrot-app/src/replies/after_swaps.rs @@ -1,25 +1,23 @@ +use abstract_app::objects::AnsAsset; use abstract_sdk::AbstractResponse; -use cosmwasm_std::{coin, DepsMut, Env, Reply}; +use cosmwasm_std::{DepsMut, Env, Reply}; use crate::{ contract::{App, AppResult}, helpers::{add_funds, get_proxy_balance}, - state::{TEMP_CURRENT_COIN, TEMP_DEPOSIT_COINS}, + state::{TEMP_CURRENT_ASSET, TEMP_DEPOSIT_ASSETS}, }; pub fn after_swap_reply(deps: DepsMut, _env: Env, app: App, _reply: Reply) -> AppResult { - let coins_before = TEMP_CURRENT_COIN.load(deps.storage)?; - let current_coins = get_proxy_balance(deps.as_ref(), &app, coins_before.denom.clone())?; + let coins_before = TEMP_CURRENT_ASSET.load(deps.storage)?; + let current_coins = get_proxy_balance(deps.as_ref(), &coins_before.name, &app)?; // We just update the coins to deposit after the swap - if current_coins.amount > coins_before.amount { - TEMP_DEPOSIT_COINS.update(deps.storage, |f| { + if current_coins > coins_before.amount { + TEMP_DEPOSIT_ASSETS.update(deps.storage, |f| { add_funds( f, - coin( - (current_coins.amount - coins_before.amount).into(), - current_coins.denom, - ), + AnsAsset::new(coins_before.name, current_coins - coins_before.amount), ) })?; } diff --git a/contracts/carrot-app/src/state.rs b/contracts/carrot-app/src/state.rs index 6b432a7f..335620ba 100644 --- a/contracts/carrot-app/src/state.rs +++ b/contracts/carrot-app/src/state.rs @@ -1,5 +1,6 @@ +use abstract_app::objects::AnsAsset; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Coin, Uint128}; +use cosmwasm_std::{Addr, Uint128}; use cw_storage_plus::Item; use crate::autocompound::{AutocompoundConfigBase, AutocompoundState}; @@ -12,9 +13,9 @@ pub const AUTOCOMPOUND_STATE: Item = Item::new("position"); pub const CURRENT_EXECUTOR: Item = Item::new("executor"); // TEMP VARIABLES FOR DEPOSITING INTO ONE STRATEGY -pub const TEMP_CURRENT_COIN: Item = Item::new("temp_current_coins"); +pub const TEMP_CURRENT_ASSET: Item = Item::new("temp_current_asset"); pub const TEMP_EXPECTED_SWAP_COIN: Item = Item::new("temp_expected_swap_coin"); -pub const TEMP_DEPOSIT_COINS: Item> = Item::new("temp_deposit_coins"); +pub const TEMP_DEPOSIT_ASSETS: Item> = Item::new("temp_deposit_assets"); pub const TEMP_CURRENT_YIELD: Item = Item::new("temp_current_yield_type"); pub type Config = ConfigBase; diff --git a/contracts/carrot-app/src/yield_sources/mars.rs b/contracts/carrot-app/src/yield_sources/mars.rs index e5656605..512371ac 100644 --- a/contracts/carrot-app/src/yield_sources/mars.rs +++ b/contracts/carrot-app/src/yield_sources/mars.rs @@ -1,11 +1,13 @@ +use crate::ans_assets::AnsAssets; use crate::contract::{App, AppResult}; +use crate::error::AppError; +use abstract_app::objects::AnsAsset; +use abstract_app::objects::AssetEntry; use abstract_app::traits::AccountIdentification; -use abstract_app::{objects::AnsAsset, traits::AbstractNameService}; use abstract_money_market_adapter::msg::MoneyMarketQueryMsg; use abstract_money_market_adapter::MoneyMarketInterface; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{coins, Coin, CosmosMsg, Deps, SubMsg, Uint128}; -use cw_asset::AssetInfo; +use cosmwasm_std::{CosmosMsg, Deps, SubMsg, Uint128}; use abstract_money_market_standard::query::MoneyMarketAnsQuery; @@ -17,17 +19,21 @@ pub const MARS_MONEY_MARKET: &str = "mars"; #[cw_serde] pub struct MarsDepositParams { /// This should stay a denom because that's a parameter that's accepted by Mars when depositing/withdrawing - pub denom: String, + pub asset: AssetEntry, } impl YieldTypeImplementation for MarsDepositParams { - fn deposit(&self, deps: Deps, funds: Vec, app: &App) -> AppResult> { - let ans = app.name_service(deps); - let ans_fund = ans.query(&AssetInfo::native(self.denom.clone()))?; - + fn deposit(&self, deps: Deps, funds: AnsAssets, app: &App) -> AppResult> { Ok(vec![SubMsg::new( app.ans_money_market(deps, MARS_MONEY_MARKET.to_string()) - .deposit(AnsAsset::new(ans_fund, funds[0].amount))?, + .deposit(AnsAsset::new( + self.asset.clone(), + funds + .into_iter() + .next() + .ok_or(AppError::NoDeposit {})? + .amount, + ))?, )]) } @@ -37,29 +43,27 @@ impl YieldTypeImplementation for MarsDepositParams { amount: Option, app: &App, ) -> AppResult> { - let ans = app.name_service(deps); - let amount = if let Some(amount) = amount { amount } else { - self.user_deposit(deps, app)?[0].amount + self.user_deposit(deps, app)? + .into_iter() + .next() + .ok_or(AppError::NoDeposit {})? + .amount }; - let ans_fund = ans.query(&AssetInfo::native(self.denom.clone()))?; - Ok(vec![app .ans_money_market(deps, MARS_MONEY_MARKET.to_string()) - .withdraw(AnsAsset::new(ans_fund, amount))?]) + .withdraw(AnsAsset::new(self.asset.clone(), amount))?]) } - fn withdraw_rewards(&self, _deps: Deps, _app: &App) -> AppResult<(Vec, Vec)> { + fn withdraw_rewards(&self, _deps: Deps, _app: &App) -> AppResult<(AnsAssets, Vec)> { // Mars doesn't have rewards, it's automatically auto-compounded - Ok((vec![], vec![])) + Ok((AnsAssets::default(), vec![])) } - fn user_deposit(&self, deps: Deps, app: &App) -> AppResult> { - let ans = app.name_service(deps); - let asset = ans.query(&AssetInfo::native(self.denom.clone()))?; + fn user_deposit(&self, deps: Deps, app: &App) -> AppResult { let user = app.account_base(deps)?.proxy; let deposit: Uint128 = app @@ -67,22 +71,27 @@ impl YieldTypeImplementation for MarsDepositParams { .query(MoneyMarketQueryMsg::MoneyMarketAnsQuery { query: MoneyMarketAnsQuery::UserDeposit { user: user.to_string(), - asset, + asset: self.asset.clone(), }, money_market: MARS_MONEY_MARKET.to_string(), })?; - Ok(coins(deposit.u128(), self.denom.clone())) + Ok(vec![AnsAsset::new(self.asset.clone(), deposit.u128())].try_into()?) } - fn user_rewards(&self, _deps: Deps, _app: &App) -> AppResult> { + fn user_rewards(&self, _deps: Deps, _app: &App) -> AppResult { // No rewards, because mars is already auto-compounding - Ok(vec![]) + Ok(AnsAssets::default()) } fn user_liquidity(&self, deps: Deps, app: &App) -> AppResult { - Ok(self.user_deposit(deps, app)?[0].amount) + Ok(self + .user_deposit(deps, app)? + .into_iter() + .next() + .map(|d| d.amount) + .unwrap_or(Uint128::zero())) } fn share_type(&self) -> super::ShareType { diff --git a/contracts/carrot-app/src/yield_sources/mod.rs b/contracts/carrot-app/src/yield_sources/mod.rs index 4ba1ad49..0e3edf59 100644 --- a/contracts/carrot-app/src/yield_sources/mod.rs +++ b/contracts/carrot-app/src/yield_sources/mod.rs @@ -3,16 +3,13 @@ pub mod osmosis_cl_pool; pub mod yield_type; use abstract_app::objects::AssetEntry; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Decimal, Deps}; -use cw_asset::AssetInfo; +use cosmwasm_std::Decimal; use crate::{ check::{Checked, Unchecked}, - contract::{App, AppResult}, - helpers::unwrap_native, + contract::AppResult, yield_sources::yield_type::YieldParamsBase, }; -use abstract_app::traits::AbstractNameService; /// A yield sources has the following elements /// A vector of tokens that NEED to be deposited inside the yield source with a repartition of tokens /// A type that allows routing to the right smart-contract integration internally @@ -26,15 +23,10 @@ pub type YieldSourceUnchecked = YieldSourceBase; pub type YieldSource = YieldSourceBase; impl YieldSourceBase { - pub fn all_denoms(&self, deps: Deps, app: &App) -> AppResult> { - let ans = app.name_service(deps); - + pub fn all_names(&self) -> AppResult> { self.asset_distribution .iter() - .map(|e| { - let denom = unwrap_native(&ans.query(&e.asset)?)?; - Ok(denom) - }) + .map(|e| Ok(e.asset.clone())) .collect() } } @@ -63,12 +55,12 @@ pub type StrategyUnchecked = StrategyBase; pub type Strategy = StrategyBase; impl Strategy { - pub fn all_denoms(&self, deps: Deps, app: &App) -> AppResult> { + pub fn all_names(&self) -> AppResult> { let results = self .0 .clone() .iter() - .map(|s| s.yield_source.all_denoms(deps, app)) + .map(|s| s.yield_source.all_names()) .collect::, _>>()?; Ok(results.into_iter().flatten().collect()) diff --git a/contracts/carrot-app/src/yield_sources/osmosis_cl_pool.rs b/contracts/carrot-app/src/yield_sources/osmosis_cl_pool.rs index 446a85bc..89de4903 100644 --- a/contracts/carrot-app/src/yield_sources/osmosis_cl_pool.rs +++ b/contracts/carrot-app/src/yield_sources/osmosis_cl_pool.rs @@ -1,18 +1,23 @@ use std::{marker::PhantomData, str::FromStr}; +use super::{yield_type::YieldTypeImplementation, ShareType}; use crate::{ + ans_assets::AnsAssets, check::{Checked, Unchecked}, contract::{App, AppResult}, error::AppError, handlers::swap_helpers::DEFAULT_SLIPPAGE, + helpers::unwrap_native, replies::{OSMOSIS_ADD_TO_POSITION_REPLY_ID, OSMOSIS_CREATE_POSITION_REPLY_ID}, state::CONFIG, }; +use abstract_app::traits::AbstractNameService; use abstract_app::{objects::AnsAsset, traits::AccountIdentification}; use abstract_dex_adapter::DexInterface; use abstract_sdk::Execution; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ensure, Coin, Coins, CosmosMsg, Decimal, Deps, ReplyOn, SubMsg, Uint128}; +use cw_asset::{Asset, AssetInfo}; use osmosis_std::{ cosmwasm_to_proto_coins, try_proto_to_cosmwasm_coins, types::osmosis::concentratedliquidity::v1beta1::{ @@ -21,8 +26,6 @@ use osmosis_std::{ }, }; -use super::{yield_type::YieldTypeImplementation, ShareType}; - #[cw_serde] pub struct ConcentratedPoolParamsBase { // This is part of the pool parameters @@ -42,13 +45,13 @@ pub type ConcentratedPoolParamsUnchecked = ConcentratedPoolParamsBase pub type ConcentratedPoolParams = ConcentratedPoolParamsBase; impl YieldTypeImplementation for ConcentratedPoolParams { - fn deposit(&self, deps: Deps, funds: Vec, app: &App) -> AppResult> { + fn deposit(&self, deps: Deps, assets: AnsAssets, app: &App) -> AppResult> { // We verify there is a position stored if let Ok(position) = self.position(deps) { - self.raw_deposit(deps, funds, app, position) + self.raw_deposit(deps, assets, app, position) } else { // We need to create a position - self.create_position(deps, funds, app) + self.create_position(deps, assets, app) } } @@ -80,17 +83,18 @@ impl YieldTypeImplementation for ConcentratedPoolParams { .into()]) } - fn withdraw_rewards(&self, deps: Deps, app: &App) -> AppResult<(Vec, Vec)> { + fn withdraw_rewards(&self, deps: Deps, app: &App) -> AppResult<(AnsAssets, Vec)> { let position = self.position(deps)?; let position_details = position.position.unwrap(); + let ans = app.name_service(deps); let user = app.account_base(deps)?.proxy; - let mut rewards = Coins::default(); + let mut rewards = AnsAssets::default(); let mut msgs: Vec = vec![]; // If there are external incentives, claim them. if !position.claimable_incentives.is_empty() { for coin in try_proto_to_cosmwasm_coins(position.claimable_incentives)? { - rewards.add(coin)?; + rewards.add(ans.query(&Asset::from(coin))?)?; } msgs.push( MsgCollectIncentives { @@ -104,7 +108,7 @@ impl YieldTypeImplementation for ConcentratedPoolParams { // If there is income from swap fees, claim them. if !position.claimable_spread_rewards.is_empty() { for coin in try_proto_to_cosmwasm_coins(position.claimable_spread_rewards)? { - rewards.add(coin)?; + rewards.add(ans.query(&Asset::from(coin))?)?; } msgs.push( MsgCollectSpreadRewards { @@ -115,12 +119,13 @@ impl YieldTypeImplementation for ConcentratedPoolParams { ) } - Ok((rewards.to_vec(), msgs)) + Ok((rewards, msgs)) } /// This may return 0, 1 or 2 elements depending on the position's status - fn user_deposit(&self, deps: Deps, _app: &App) -> AppResult> { + fn user_deposit(&self, deps: Deps, app: &App) -> AppResult { let position = self.position(deps)?; + let ans = app.name_service(deps); Ok([ try_proto_to_cosmwasm_coins(position.asset0)?, @@ -131,24 +136,27 @@ impl YieldTypeImplementation for ConcentratedPoolParams { .map(|mut fund| { // This is used because osmosis seems to charge 1 amount for withdrawals on all positions fund.amount -= Uint128::one(); - fund + ans.query(&Asset::from(fund)) }) - .collect()) + .collect::, _>>()? + .try_into()?) } - fn user_rewards(&self, deps: Deps, _app: &App) -> AppResult> { + fn user_rewards(&self, deps: Deps, app: &App) -> AppResult { + let ans = app.name_service(deps); + let position = self.position(deps)?; - let mut rewards = cosmwasm_std::Coins::default(); + let mut rewards = AnsAssets::default(); for coin in try_proto_to_cosmwasm_coins(position.claimable_incentives)? { - rewards.add(coin)?; + rewards.add(ans.query(&Asset::from(coin))?)?; } for coin in try_proto_to_cosmwasm_coins(position.claimable_spread_rewards)? { - rewards.add(coin)?; + rewards.add(ans.query(&Asset::from(coin))?)?; } - Ok(rewards.into()) + Ok(rewards) } fn user_liquidity(&self, deps: Deps, _app: &App) -> AppResult { @@ -182,20 +190,33 @@ impl ConcentratedPoolParams { fn create_position( &self, deps: Deps, - funds: Vec, + funds: AnsAssets, app: &App, // create_position_msg: CreatePositionMessage, ) -> AppResult> { + let ans = app.name_service(deps); let proxy_addr = app.account_base(deps)?.proxy; // 2. Create a position - let tokens = cosmwasm_to_proto_coins(funds); + let tokens_provided = funds + .into_iter() + .map(|f| { + let asset = ans.query(&f)?; + unwrap_native(&asset.info).map(|denom| Coin { + denom, + amount: asset.amount, + }) + }) + .collect::, _>>()?; + let ordered_tokens_provided: Coins = tokens_provided.try_into()?; + + let tokens_provided = cosmwasm_to_proto_coins(ordered_tokens_provided); let msg = app.executor(deps).execute_with_reply_and_data( MsgCreatePosition { pool_id: self.pool_id, sender: proxy_addr.to_string(), lower_tick: self.lower_tick, upper_tick: self.upper_tick, - tokens_provided: tokens, + tokens_provided, token_min_amount0: "0".to_string(), token_min_amount1: "0".to_string(), } @@ -210,25 +231,44 @@ impl ConcentratedPoolParams { fn raw_deposit( &self, deps: Deps, - funds: Vec, + deposit_assets: AnsAssets, app: &App, position: FullPositionBreakdown, ) -> AppResult> { + let ans = app.name_service(deps); let position_id = position.position.unwrap().position_id; let proxy_addr = app.account_base(deps)?.proxy; // We need to make sure the amounts are in the right order - // We assume the funds vector has 2 coins associated - let (amount0, amount1) = match position + let resolved_asset0 = position .asset0 - .map(|c| c.denom == funds[0].denom) - .or(position.asset1.map(|c| c.denom == funds[1].denom)) + .map(|a| ans.query(&AssetInfo::native(a.denom))) + .transpose()?; + let resolved_asset1 = position + .asset1 + .map(|a| ans.query(&AssetInfo::native(a.denom))) + .transpose()?; + + let mut assets = deposit_assets.into_iter(); + let deposit0 = assets.next(); + let deposit1 = assets.next(); + + let (amount0, amount1) = match resolved_asset0 + .map(|c| c == deposit0.clone().map(|d| d.name).unwrap_or_default()) + .or(resolved_asset1.map(|c| c == deposit1.clone().map(|d| d.name).unwrap_or_default())) { - Some(true) => (funds[0].amount, funds[1].amount), // we already had the right order - Some(false) => (funds[1].amount, funds[0].amount), // we had the wrong order + Some(true) => ( + deposit0.map(|d| d.amount).unwrap_or_default(), + deposit1.map(|d| d.amount).unwrap_or_default(), + ), // we already had the right order + Some(false) => ( + deposit1.map(|d| d.amount).unwrap_or_default(), + deposit0.map(|d| d.amount).unwrap_or_default(), + ), // we had the wrong order None => return Err(AppError::NoPosition {}), // A position has to exist in order to execute this function. This should be unreachable }; + deps.api.debug("After amounts"); let deposit_msg = app.executor(deps).execute_with_reply_and_data( diff --git a/contracts/carrot-app/src/yield_sources/yield_type.rs b/contracts/carrot-app/src/yield_sources/yield_type.rs index eca0bcec..0dd5946a 100644 --- a/contracts/carrot-app/src/yield_sources/yield_type.rs +++ b/contracts/carrot-app/src/yield_sources/yield_type.rs @@ -1,7 +1,8 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Coin, CosmosMsg, Deps, SubMsg, Uint128}; +use cosmwasm_std::{CosmosMsg, Deps, SubMsg, Uint128}; use crate::{ + ans_assets::AnsAssets, check::{Checked, Unchecked}, contract::{App, AppResult}, }; @@ -21,11 +22,11 @@ pub type YieldTypeUnchecked = YieldParamsBase; pub type YieldType = YieldParamsBase; impl YieldTypeImplementation for YieldType { - fn deposit(&self, deps: Deps, funds: Vec, app: &App) -> AppResult> { - if funds.is_empty() { + fn deposit(&self, deps: Deps, assets: AnsAssets, app: &App) -> AppResult> { + if assets.is_empty() { return Ok(vec![]); } - self.inner().deposit(deps, funds, app) + self.inner().deposit(deps, assets, app) } fn withdraw( @@ -37,15 +38,15 @@ impl YieldTypeImplementation for YieldType { self.inner().withdraw(deps, amount, app) } - fn withdraw_rewards(&self, deps: Deps, app: &App) -> AppResult<(Vec, Vec)> { + fn withdraw_rewards(&self, deps: Deps, app: &App) -> AppResult<(AnsAssets, Vec)> { self.inner().withdraw_rewards(deps, app) } - fn user_deposit(&self, deps: Deps, app: &App) -> AppResult> { + fn user_deposit(&self, deps: Deps, app: &App) -> AppResult { Ok(self.inner().user_deposit(deps, app).unwrap_or_default()) } - fn user_rewards(&self, deps: Deps, app: &App) -> AppResult> { + fn user_rewards(&self, deps: Deps, app: &App) -> AppResult { Ok(self.inner().user_rewards(deps, app).unwrap_or_default()) } @@ -72,16 +73,16 @@ impl YieldType { } pub trait YieldTypeImplementation { - fn deposit(&self, deps: Deps, funds: Vec, app: &App) -> AppResult>; + fn deposit(&self, deps: Deps, funds: AnsAssets, app: &App) -> AppResult>; fn withdraw(&self, deps: Deps, amount: Option, app: &App) -> AppResult>; - fn withdraw_rewards(&self, deps: Deps, app: &App) -> AppResult<(Vec, Vec)>; + fn withdraw_rewards(&self, deps: Deps, app: &App) -> AppResult<(AnsAssets, Vec)>; - fn user_deposit(&self, deps: Deps, app: &App) -> AppResult>; + fn user_deposit(&self, deps: Deps, app: &App) -> AppResult; - fn user_rewards(&self, deps: Deps, app: &App) -> AppResult>; + fn user_rewards(&self, deps: Deps, app: &App) -> AppResult; fn user_liquidity(&self, deps: Deps, app: &App) -> AppResult; diff --git a/contracts/carrot-app/tests/autocompound.rs b/contracts/carrot-app/tests/autocompound.rs index 1a69f77f..02b3a227 100644 --- a/contracts/carrot-app/tests/autocompound.rs +++ b/contracts/carrot-app/tests/autocompound.rs @@ -1,29 +1,26 @@ mod common; -use crate::common::{setup_test_tube, DEX_NAME, USDC, USDT}; -use abstract_app::abstract_interface::{Abstract, AbstractAccount}; +use crate::common::{deposit_with_funds, setup_test_tube, DEX_NAME, USDC, USDT}; +use abstract_app::{ + abstract_interface::{Abstract, AbstractAccount}, + objects::AnsAsset, +}; use carrot_app::msg::{ AppExecuteMsgFns, AppQueryMsgFns, AssetsBalanceResponse, AvailableRewardsResponse, }; -use cosmwasm_std::{coin, coins}; +use cosmwasm_std::coin; use cw_orch::{anyhow, prelude::*}; #[test] fn check_autocompound() -> anyhow::Result<()> { let (_, carrot_app) = setup_test_tube(false)?; - let mut chain = carrot_app.get_chain().clone(); + let chain = carrot_app.get_chain().clone(); // Create position - let deposit_amount = 5_000; - let deposit_coins = coins(deposit_amount, USDT.to_owned()); - chain.add_balance( - carrot_app.account().proxy()?.to_string(), - deposit_coins.clone(), - )?; - + let deposit_amount = 5_000u128; // Do the deposit - carrot_app.deposit(deposit_coins.clone(), None)?; + deposit_with_funds(&carrot_app, vec![AnsAsset::new(USDT, deposit_amount)])?; // Do some swaps let dex: abstract_dex_adapter::interface::DexAdapter<_> = carrot_app.module()?; diff --git a/contracts/carrot-app/tests/common.rs b/contracts/carrot-app/tests/common.rs index a9c05d2b..735c00a8 100644 --- a/contracts/carrot-app/tests/common.rs +++ b/contracts/carrot-app/tests/common.rs @@ -1,17 +1,22 @@ use abstract_app::abstract_core::objects::{ pool_id::PoolAddressBase, AssetEntry, PoolMetadata, PoolType, }; +use abstract_app::objects::AnsAsset; use abstract_client::{AbstractClient, Application, Namespace}; use carrot_app::autocompound::{AutocompoundConfigBase, AutocompoundRewardsConfigBase}; use carrot_app::contract::OSMOSIS; +use carrot_app::error::AppError; +use carrot_app::helpers::unwrap_native; use carrot_app::msg::AppInstantiateMsg; use carrot_app::state::ConfigBase; use carrot_app::yield_sources::osmosis_cl_pool::ConcentratedPoolParamsBase; use carrot_app::yield_sources::yield_type::YieldParamsBase; use carrot_app::yield_sources::{AssetShare, StrategyBase, StrategyElementBase, YieldSourceBase}; -use cosmwasm_std::{coin, coins, Coins, Decimal, Uint128, Uint64}; +use carrot_app::{AppExecuteMsgFns, AppInterface}; +use cosmwasm_std::{coin, coins, Decimal, Uint128, Uint64}; use cw_asset::AssetInfoUnchecked; use cw_orch::environment::MutCwEnv; +use cw_orch::mock::cw_multi_test::AppResponse; use cw_orch::osmosis_test_tube::osmosis_test_tube::Gamm; use cw_orch::{ anyhow, @@ -51,16 +56,14 @@ pub fn deploy( mut chain: Chain, pool_id: u64, gas_pool_id: u64, - initial_deposit: Option>, + initial_deposit: Option>, ) -> anyhow::Result>> { - let asset0 = USDT.to_owned(); - let asset1 = USDC.to_owned(); // We register the pool inside the Abstract ANS let client = AbstractClient::builder(chain.clone()) .dex(DEX_NAME) .assets(vec![ - (USDC.to_string(), AssetInfoUnchecked::Native(asset0.clone())), - (USDT.to_string(), AssetInfoUnchecked::Native(asset1.clone())), + (USDT.to_string(), AssetInfoUnchecked::native(USDT)), + (USDC.to_string(), AssetInfoUnchecked::native(USDC)), ( REWARD_ASSET.to_string(), AssetInfoUnchecked::Native(REWARD_DENOM.to_owned()), @@ -113,7 +116,20 @@ pub fn deploy( publisher.publish_app::>()?; if let Some(deposit) = &initial_deposit { - chain.add_balance(publisher.account().proxy()?.to_string(), deposit.clone())?; + let deposit_coins = client.name_service().resolve(deposit)?; + + chain.add_balance( + publisher.account().proxy()?.to_string(), + deposit_coins + .into_iter() + .map(|f| { + Ok::<_, AppError>(Coin { + denom: unwrap_native(&f.info)?, + amount: f.amount, + }) + }) + .collect::>()?, + )?; } let init_msg = AppInstantiateMsg { @@ -295,16 +311,40 @@ pub fn setup_test_tube( // We create a usdt-usdc pool let (pool_id, gas_pool_id) = create_pool(chain.clone())?; - let initial_deposit: Option> = create_position + let initial_deposit: Option> = create_position .then(|| { // TODO: Requires instantiate2 to test it (we need to give authz authorization before instantiating) - let mut initial_coins = Coins::default(); - initial_coins.add(coin(10_000, USDT))?; - initial_coins.add(coin(10_000, USDC))?; - Ok::<_, anyhow::Error>(initial_coins.into()) + let initial_assets = vec![ + AnsAsset::new(USDC, 10_000u128), + AnsAsset::new(USDT, 10_000u128), + ]; + Ok::<_, anyhow::Error>(initial_assets) }) .transpose()?; let carrot_app = deploy(chain.clone(), pool_id, gas_pool_id, initial_deposit)?; Ok((pool_id, carrot_app)) } + +pub fn deposit_with_funds( + carrot_app: &Application>, + deposit: Vec, +) -> anyhow::Result { + let mut chain = carrot_app.get_chain().clone(); + let client = AbstractClient::new(chain.clone())?; + let deposit_coins = client + .name_service() + .resolve(&deposit)? + .into_iter() + .map(|f| { + Ok::<_, AppError>(Coin { + denom: unwrap_native(&f.info)?, + amount: f.amount, + }) + }) + .collect::>()?; + + chain.add_balance(carrot_app.account().proxy()?.to_string(), deposit_coins)?; + + Ok(carrot_app.deposit(deposit, None)?) +} diff --git a/contracts/carrot-app/tests/config.rs b/contracts/carrot-app/tests/config.rs index 3e500200..2b465705 100644 --- a/contracts/carrot-app/tests/config.rs +++ b/contracts/carrot-app/tests/config.rs @@ -1,7 +1,7 @@ mod common; use crate::common::{create_pool, setup_test_tube, USDC, USDT}; -use abstract_app::objects::AssetEntry; +use abstract_app::objects::{AnsAsset, AssetEntry}; use carrot_app::{ msg::{AppExecuteMsgFns, AppQueryMsgFns}, yield_sources::{ @@ -134,12 +134,13 @@ fn rebalance_success() -> anyhow::Result<()> { let strategy = carrot_app.strategy()?; assert_ne!(strategy.strategy, new_strat); let deposit_coins = coins(10, USDC); + let deposit_assets = vec![AnsAsset::new(USDC, 10u128)]; chain.add_balance( carrot_app.account().proxy()?.to_string(), deposit_coins.clone(), )?; - carrot_app.update_strategy(deposit_coins, new_strat.clone())?; + carrot_app.update_strategy(deposit_assets, new_strat.clone())?; // We query the new strategy let strategy = carrot_app.strategy()?; @@ -156,6 +157,7 @@ fn rebalance_with_new_pool_success() -> anyhow::Result<()> { let deposit_amount = 10_000; let deposit_coins = coins(deposit_amount, USDT); + let deposit_assets = vec![AnsAsset::new(USDT, deposit_amount)]; chain.add_balance( carrot_app.account().proxy()?.to_string(), @@ -208,7 +210,7 @@ fn rebalance_with_new_pool_success() -> anyhow::Result<()> { share: Decimal::percent(50), }, ]); - carrot_app.update_strategy(deposit_coins.clone(), new_strat.clone())?; + carrot_app.update_strategy(deposit_assets.clone(), new_strat.clone())?; carrot_app.strategy()?; @@ -237,6 +239,7 @@ fn rebalance_with_stale_strategy_success() -> anyhow::Result<()> { let deposit_amount = 10_000; let deposit_coins = coins(deposit_amount, USDT); + let deposit_assets = vec![AnsAsset::new(USDT, deposit_amount)]; chain.add_balance( carrot_app.account().proxy()?.to_string(), @@ -291,7 +294,7 @@ fn rebalance_with_stale_strategy_success() -> anyhow::Result<()> { }, ]); - carrot_app.update_strategy(deposit_coins.clone(), strat.clone())?; + carrot_app.update_strategy(deposit_assets.clone(), strat.clone())?; let new_strat = StrategyBase(vec![StrategyElementBase { yield_source: common_yield_source.clone(), @@ -329,8 +332,9 @@ fn rebalance_with_current_and_stale_strategy_success() -> anyhow::Result<()> { let mut chain = carrot_app.get_chain().clone(); let (new_pool_id, _) = create_pool(chain.clone())?; - let deposit_amount = 10_000; + let deposit_amount = 10_000u128; let deposit_coins = coins(deposit_amount, USDT); + let deposit_assets = vec![AnsAsset::new(USDT, deposit_amount)]; chain.add_balance( carrot_app.account().proxy()?.to_string(), @@ -385,7 +389,7 @@ fn rebalance_with_current_and_stale_strategy_success() -> anyhow::Result<()> { }, ]); - carrot_app.update_strategy(deposit_coins.clone(), strat.clone())?; + carrot_app.update_strategy(deposit_assets.clone(), strat.clone())?; let mut strategies = carrot_app.strategy()?.strategy; diff --git a/contracts/carrot-app/tests/deposit_withdraw.rs b/contracts/carrot-app/tests/deposit_withdraw.rs index c17a1003..e33ebc40 100644 --- a/contracts/carrot-app/tests/deposit_withdraw.rs +++ b/contracts/carrot-app/tests/deposit_withdraw.rs @@ -1,7 +1,7 @@ mod common; -use crate::common::{setup_test_tube, USDC, USDT}; -use abstract_app::objects::AssetEntry; +use crate::common::{deposit_with_funds, setup_test_tube, USDC, USDT}; +use abstract_app::objects::{AnsAsset, AssetEntry}; use abstract_client::Application; use carrot_app::{ msg::{AppExecuteMsgFns, AppQueryMsgFns, AssetsBalanceResponse}, @@ -36,29 +36,17 @@ fn deposit_lands() -> anyhow::Result<()> { let (_, carrot_app) = setup_test_tube(false)?; // We should add funds to the account proxy - let deposit_amount = 5_000; - let deposit_coins = coins(deposit_amount, USDT.to_owned()); - let mut chain = carrot_app.get_chain().clone(); - + let deposit_amount = 5_000u128; let balances_before = query_balances(&carrot_app)?; - chain.add_balance( - carrot_app.account().proxy()?.to_string(), - deposit_coins.clone(), - )?; - // Do the deposit - carrot_app.deposit(deposit_coins.clone(), None)?; + deposit_with_funds(&carrot_app, vec![AnsAsset::new(USDT, deposit_amount)])?; + // Check almost everything landed let balances_after = query_balances(&carrot_app)?; assert!(balances_before < balances_after); - // Add some more funds - chain.add_balance( - carrot_app.account().proxy()?.to_string(), - deposit_coins.clone(), - )?; // Do the second deposit - let response = carrot_app.deposit(vec![coin(deposit_amount, USDT.to_owned())], None)?; + let response = deposit_with_funds(&carrot_app, vec![AnsAsset::new(USDT, deposit_amount)])?; // Check almost everything landed let balances_after_second = query_balances(&carrot_app)?; assert!(balances_after < balances_after_second); @@ -73,14 +61,13 @@ fn deposit_lands() -> anyhow::Result<()> { fn withdraw_position() -> anyhow::Result<()> { let (_, carrot_app) = setup_test_tube(false)?; - let mut chain = carrot_app.get_chain().clone(); + let chain = carrot_app.get_chain().clone(); // Add some more funds - let deposit_amount = 10_000; - let deposit_coins = coins(deposit_amount, USDT.to_owned()); + let deposit_amount = 10_000u128; + // Do the deposit + deposit_with_funds(&carrot_app, vec![AnsAsset::new(USDT, deposit_amount)])?; let proxy_addr = carrot_app.account().proxy()?; - chain.add_balance(proxy_addr.to_string(), deposit_coins.clone())?; - carrot_app.deposit(deposit_coins, None)?; let balance: AssetsBalanceResponse = carrot_app.balance()?; let balance_usdc_before_withdraw = chain @@ -138,8 +125,9 @@ fn deposit_multiple_assets() -> anyhow::Result<()> { let mut chain = carrot_app.get_chain().clone(); let proxy_addr = carrot_app.account().proxy()?; let deposit_coins = vec![coin(234, USDC.to_owned()), coin(258, USDT.to_owned())]; + let deposit_assets = vec![AnsAsset::new(USDC, 234u128), AnsAsset::new(USDT, 258u128)]; chain.add_balance(proxy_addr.to_string(), deposit_coins.clone())?; - carrot_app.deposit(deposit_coins, None)?; + carrot_app.deposit(deposit_assets, None)?; Ok(()) } @@ -194,18 +182,15 @@ fn deposit_multiple_positions() -> anyhow::Result<()> { share: Decimal::percent(50), }, ]); - carrot_app.update_strategy(vec![], new_strat.clone())?; - - let deposit_amount = 5_000; - let deposit_coins = coins(deposit_amount, USDT.to_owned()); - let mut chain = carrot_app.get_chain().clone(); - let balances_before = query_balances(&carrot_app)?; + let deposit_amount = 5_000u128; + let mut chain = carrot_app.get_chain().clone(); chain.add_balance( carrot_app.account().proxy()?.to_string(), - deposit_coins.clone(), + coins(deposit_amount, USDT), )?; - carrot_app.deposit(deposit_coins, None)?; + carrot_app.update_strategy(vec![AnsAsset::new(USDT, deposit_amount)], new_strat.clone())?; + let balances_after = query_balances(&carrot_app)?; let slippage = Decimal::percent(4); @@ -272,24 +257,23 @@ fn deposit_multiple_positions_with_empty() -> anyhow::Result<()> { share: Decimal::percent(100), }], params: YieldParamsBase::Mars(MarsDepositParams { - denom: USDT.to_string(), + asset: AssetEntry::new(USDT), }), }, share: Decimal::percent(0), }, ]); - carrot_app.update_strategy(vec![], new_strat.clone())?; + let balances_before = query_balances(&carrot_app)?; + let deposit_amount = 5_000u128; - let deposit_amount = 5_000; - let deposit_coins = coins(deposit_amount, USDT.to_owned()); let mut chain = carrot_app.get_chain().clone(); - - let balances_before = query_balances(&carrot_app)?; chain.add_balance( carrot_app.account().proxy()?.to_string(), - deposit_coins.clone(), + coins(deposit_amount, USDT), )?; - carrot_app.deposit(deposit_coins, None)?; + carrot_app.update_strategy(vec![AnsAsset::new(USDT, deposit_amount)], new_strat.clone())?; + + // Do the deposit let balances_after = query_balances(&carrot_app)?; println!("{balances_before} --> {balances_after}"); diff --git a/contracts/carrot-app/tests/pool_inbalance.rs b/contracts/carrot-app/tests/pool_inbalance.rs index deebf02f..915f5baa 100644 --- a/contracts/carrot-app/tests/pool_inbalance.rs +++ b/contracts/carrot-app/tests/pool_inbalance.rs @@ -1,8 +1,7 @@ mod common; -use crate::common::{setup_test_tube, USDC, USDT}; -use carrot_app::msg::AppExecuteMsgFns; -use cosmwasm_std::{coin, coins}; +use crate::common::{deposit_with_funds, setup_test_tube, USDC, USDT}; +use abstract_app::objects::AnsAsset; use cw_orch::{anyhow, prelude::*}; use osmosis_std::types::osmosis::{ gamm::v1beta1::{MsgSwapExactAmountIn, MsgSwapExactAmountInResponse}, @@ -15,14 +14,11 @@ fn deposit_after_inbalance_works() -> anyhow::Result<()> { let (pool_id, carrot_app) = setup_test_tube(false)?; // We should add funds to the account proxy - let deposit_amount = 5_000; - let deposit_coins = coins(deposit_amount, USDT.to_owned()); - let mut chain = carrot_app.get_chain().clone(); - let proxy = carrot_app.account().proxy()?; - chain.add_balance(proxy.to_string(), deposit_coins.clone())?; - + let deposit_amount = 5_000u128; // Do the deposit - carrot_app.deposit(deposit_coins.clone(), None)?; + deposit_with_funds(&carrot_app, vec![AnsAsset::new(USDT, deposit_amount)])?; + let chain = carrot_app.get_chain().clone(); + let proxy = carrot_app.account().proxy()?; // Create a pool inbalance by swapping a lot deposit amount from one to the other. // All the positions in the pool are centered, so the price doesn't change, just the funds ratio inside the position @@ -52,11 +48,8 @@ fn deposit_after_inbalance_works() -> anyhow::Result<()> { .bank_querier() .balance(&proxy, Some(USDT.to_string()))?[0] .amount; - // Add some more funds - chain.add_balance(proxy.to_string(), deposit_coins.clone())?; - - // // Do the second deposit - carrot_app.deposit(vec![coin(deposit_amount, USDT.to_owned())], None)?; + // Do the deposit + deposit_with_funds(&carrot_app, vec![AnsAsset::new(USDT, deposit_amount)])?; // Check almost everything landed let proxy_balance_after_second = chain .bank_querier() diff --git a/contracts/carrot-app/tests/query.rs b/contracts/carrot-app/tests/query.rs index a6132fb7..38b72061 100644 --- a/contracts/carrot-app/tests/query.rs +++ b/contracts/carrot-app/tests/query.rs @@ -1,29 +1,20 @@ mod common; -use crate::common::{setup_test_tube, USDT}; -use carrot_app::{ - helpers::close_to, - msg::{AppExecuteMsgFns, AppQueryMsgFns}, -}; -use cosmwasm_std::{coins, Decimal}; -use cw_orch::{anyhow, prelude::*}; +use crate::common::{deposit_with_funds, setup_test_tube, USDT}; +use abstract_app::objects::AnsAsset; +use carrot_app::{helpers::close_to, msg::AppQueryMsgFns}; +use cosmwasm_std::Decimal; +use cw_orch::anyhow; #[test] fn query_strategy_status() -> anyhow::Result<()> { let (_, carrot_app) = setup_test_tube(false)?; // We should add funds to the account proxy - let deposit_amount = 5_000; - let deposit_coins = coins(deposit_amount, USDT.to_owned()); - let mut chain = carrot_app.get_chain().clone(); - - chain.add_balance( - carrot_app.account().proxy()?.to_string(), - deposit_coins.clone(), - )?; + let deposit_amount = 5_000u128; // Do the deposit - carrot_app.deposit(deposit_coins.clone(), None)?; + deposit_with_funds(&carrot_app, vec![AnsAsset::new(USDT, deposit_amount)])?; let strategy = carrot_app.strategy_status()?.strategy;