diff --git a/contracts/carrot-app/examples/localnet_install.rs b/contracts/carrot-app/examples/localnet_install.rs index 3869620e..5bf4f06e 100644 --- a/contracts/carrot-app/examples/localnet_install.rs +++ b/contracts/carrot-app/examples/localnet_install.rs @@ -116,6 +116,7 @@ fn one_element(upper_tick: i64, lower_tick: i64, share: Decimal) -> StrategyElem upper_tick, position_id: None, _phantom: std::marker::PhantomData, + position_cache: None, }), }, share, diff --git a/contracts/carrot-app/examples/localnet_test.rs b/contracts/carrot-app/examples/localnet_test.rs index 76c9b314..dae495e0 100644 --- a/contracts/carrot-app/examples/localnet_test.rs +++ b/contracts/carrot-app/examples/localnet_test.rs @@ -44,6 +44,7 @@ fn main() -> anyhow::Result<()> { )?; carrot.deposit(coins(10_000, "uosmo"), None)?; + // carrot.deposit(coins(10_000, "uosmo"), None)?; carrot.update_strategy(coins(10_000, "uosmo"), five_strategy())?; carrot.withdraw(None)?; diff --git a/contracts/carrot-app/src/check.rs b/contracts/carrot-app/src/check.rs index 6761ae93..40231751 100644 --- a/contracts/carrot-app/src/check.rs +++ b/contracts/carrot-app/src/check.rs @@ -154,6 +154,7 @@ mod yield_sources { upper_tick: self.upper_tick, position_id: self.position_id, _phantom: PhantomData, + position_cache: self.position_cache, }) } } @@ -179,6 +180,7 @@ mod yield_sources { upper_tick: params.upper_tick, position_id: params.position_id, _phantom: std::marker::PhantomData, + position_cache: params.position_cache, }) } YieldParamsBase::Mars(params) => YieldParamsBase::Mars(params), diff --git a/contracts/carrot-app/src/distribution/deposit.rs b/contracts/carrot-app/src/distribution/deposit.rs index 5c949278..983113e2 100644 --- a/contracts/carrot-app/src/distribution/deposit.rs +++ b/contracts/carrot-app/src/distribution/deposit.rs @@ -13,6 +13,11 @@ use cosmwasm_schema::cw_serde; use crate::{error::AppError, msg::InternalExecuteMsg}; +pub struct DepositStrategy { + pub withdraw_strategy: Vec<(StrategyElement, Decimal)>, + pub deposit_msgs: Vec, +} + /// This functions creates the current deposit strategy // /// 1. We query the target strategy in storage (target strategy) // /// 2. We query the current status of the strategy (current strategy) from all deposits (external queries) @@ -22,10 +27,10 @@ use crate::{error::AppError, msg::InternalExecuteMsg}; pub fn generate_deposit_strategy( deps: Deps, funds: Vec, - target_strategy: Strategy, + mut target_strategy: Strategy, yield_source_params: Option>>>, app: &App, -) -> AppResult<(Vec<(StrategyElement, Decimal)>, Vec)> { +) -> AppResult { // This is the current distribution of funds inside the strategies let current_strategy_status = target_strategy.query_current_status(deps, app)?; @@ -44,7 +49,10 @@ pub fn generate_deposit_strategy( let deposit_msgs = this_deposit_strategy.fill_all_and_get_messages(deps, usable_funds.into(), app)?; - Ok((withdraw_strategy, deposit_msgs)) + Ok(DepositStrategy { + withdraw_strategy, + deposit_msgs, + }) } impl Strategy { @@ -52,10 +60,10 @@ impl Strategy { // This method needs to be called on the stored strategy // We error if deposit value is non-zero here pub fn current_deposit_strategy( - &self, + &mut self, deps: Deps, funds: &mut Coins, - current_strategy_status: Self, + mut current_strategy_status: Self, app: &App, ) -> AppResult<(Vec<(StrategyElement, Decimal)>, Self)> { let total_value = self.current_balance(deps, app)?.total_value; @@ -78,7 +86,7 @@ impl Strategy { // First we generate the messages for withdrawing strategies that have too much funds let withdraw_strategy: Vec<(StrategyElement, Decimal)> = current_strategy_status .0 - .iter() + .iter_mut() .zip(self.0.clone()) .map(|(current, target)| { // We need to take into account the total value added by the current shares diff --git a/contracts/carrot-app/src/distribution/query.rs b/contracts/carrot-app/src/distribution/query.rs index 67260fb6..741e65dc 100644 --- a/contracts/carrot-app/src/distribution/query.rs +++ b/contracts/carrot-app/src/distribution/query.rs @@ -12,10 +12,10 @@ use crate::{ impl Strategy { // Returns the total balance - pub fn current_balance(&self, deps: Deps, app: &App) -> AppResult { + pub fn current_balance(&mut self, deps: Deps, app: &App) -> AppResult { let mut funds = Coins::default(); let mut total_value = Uint128::zero(); - self.0.iter().try_for_each(|s| { + self.0.iter_mut().try_for_each(|s| { let deposit_value = s .yield_source .params @@ -36,10 +36,10 @@ impl Strategy { } /// Returns the current status of the full strategy. It returns shares reflecting the underlying positions - pub fn query_current_status(&self, deps: Deps, app: &App) -> AppResult { + pub fn query_current_status(&mut self, deps: Deps, app: &App) -> AppResult { let all_strategy_values = self .0 - .iter() + .iter_mut() .map(|s| s.query_current_value(deps, app)) .collect::, _>>()?; @@ -89,7 +89,7 @@ impl StrategyElement { /// If there is no deposit or the query for the user deposit value fails /// the function returns 0 value with the registered asset distribution pub fn query_current_value( - &self, + &mut self, deps: Deps, app: &App, ) -> AppResult<(Uint128, Vec)> { diff --git a/contracts/carrot-app/src/distribution/rewards.rs b/contracts/carrot-app/src/distribution/rewards.rs index b416c582..4a142fb8 100644 --- a/contracts/carrot-app/src/distribution/rewards.rs +++ b/contracts/carrot-app/src/distribution/rewards.rs @@ -16,7 +16,7 @@ impl Strategy { let (rewards, msgs): (Vec>, _) = self .0 .into_iter() - .map(|s| { + .map(|mut s| { let (rewards, raw_msgs) = s.yield_source.params.withdraw_rewards(deps, app)?; Ok::<_, AppError>(( diff --git a/contracts/carrot-app/src/distribution/withdraw.rs b/contracts/carrot-app/src/distribution/withdraw.rs index f68eb9c0..6dcea498 100644 --- a/contracts/carrot-app/src/distribution/withdraw.rs +++ b/contracts/carrot-app/src/distribution/withdraw.rs @@ -20,13 +20,13 @@ impl Strategy { .collect() } pub fn withdraw_preview( - self, + &mut self, deps: Deps, withdraw_share: Option, app: &App, ) -> AppResult> { let mut withdraw_result = Coins::default(); - self.0.into_iter().try_for_each(|s| { + self.0.iter_mut().try_for_each(|s| { let funds = s.withdraw_preview(deps, withdraw_share, app)?; funds.into_iter().try_for_each(|f| withdraw_result.add(f))?; Ok::<_, AppError>(()) @@ -37,7 +37,7 @@ impl Strategy { impl StrategyElement { pub fn withdraw( - self, + mut self, deps: Deps, withdraw_share: Option, app: &App, @@ -62,7 +62,7 @@ impl StrategyElement { } pub fn withdraw_preview( - &self, + &mut self, deps: Deps, withdraw_share: Option, app: &App, diff --git a/contracts/carrot-app/src/handlers/execute.rs b/contracts/carrot-app/src/handlers/execute.rs index 4af43b64..a52f1ada 100644 --- a/contracts/carrot-app/src/handlers/execute.rs +++ b/contracts/carrot-app/src/handlers/execute.rs @@ -3,12 +3,12 @@ use crate::{ autocompound::AutocompoundState, check::Checkable, contract::{App, AppResult}, - distribution::deposit::generate_deposit_strategy, + distribution::deposit::{generate_deposit_strategy, DepositStrategy}, error::AppError, handlers::query::query_balance, helpers::assert_contract, msg::{AppExecuteMsg, ExecuteMsg}, - state::{AUTOCOMPOUND_STATE, CONFIG, STRATEGY_CONFIG}, + state::{load_strategy, save_strategy, AUTOCOMPOUND_STATE, CONFIG}, yield_sources::{AssetShare, Strategy, StrategyUnchecked}, }; use abstract_app::abstract_sdk::features::AbstractResponse; @@ -56,7 +56,7 @@ fn deposit( .assert_admin(deps.as_ref(), &info.sender) .or(assert_contract(&info, &env))?; - let target_strategy = STRATEGY_CONFIG.load(deps.storage)?; + let target_strategy = load_strategy(deps.as_ref())?; let deposit_msgs = _inner_deposit( deps.as_ref(), &env, @@ -92,7 +92,7 @@ fn withdraw( } fn update_strategy( - deps: DepsMut, + mut deps: DepsMut, env: Env, _info: MessageInfo, strategy: StrategyUnchecked, @@ -100,10 +100,10 @@ fn update_strategy( app: App, ) -> AppResult { // We load it raw because we're changing the strategy - let old_strategy = STRATEGY_CONFIG.load(deps.storage)?; + let old_strategy = load_strategy(deps.as_ref())?; // We check the new strategy - let strategy = strategy.check(deps.as_ref(), &app)?; + let mut 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()?; @@ -117,7 +117,7 @@ fn update_strategy( let (withdrawn_funds, withdraw_msgs): (Vec>, Vec>) = all_stale_sources .into_iter() - .map(|s| { + .map(|mut s| { Ok::<_, AppError>(( s.withdraw_preview(deps.as_ref(), None, &app) .unwrap_or_default(), @@ -133,7 +133,7 @@ fn update_strategy( .try_for_each(|f| f.into_iter().try_for_each(|f| available_funds.add(f)))?; // 2. We replace the strategy with the new strategy - STRATEGY_CONFIG.save(deps.storage, &strategy)?; + save_strategy(deps.branch(), &mut strategy)?; // 3. We deposit the funds into the new strategy let deposit_msgs = _inner_deposit( @@ -160,7 +160,7 @@ fn update_strategy( fn autocompound(deps: DepsMut, env: Env, info: MessageInfo, app: App) -> AppResult { // Everyone can autocompound - let strategy = STRATEGY_CONFIG.load(deps.storage)?; + let strategy = load_strategy(deps.as_ref())?; // We withdraw all rewards from protocols let (all_rewards, collect_rewards_msgs) = strategy.withdraw_rewards(deps.as_ref(), &app)?; @@ -207,8 +207,10 @@ pub fn _inner_deposit( yield_source_params: Option>>>, app: &App, ) -> AppResult> { - let (withdraw_strategy, deposit_msgs) = - generate_deposit_strategy(deps, funds, target_strategy, yield_source_params, app)?; + let DepositStrategy { + withdraw_strategy, + deposit_msgs, + } = generate_deposit_strategy(deps, funds, target_strategy, yield_source_params, app)?; let deposit_withdraw_msgs = withdraw_strategy .into_iter() @@ -242,9 +244,7 @@ fn _inner_withdraw( // We withdraw the necessary share from all registered investments let withdraw_msgs = - STRATEGY_CONFIG - .load(deps.storage)? - .withdraw(deps.as_ref(), withdraw_share, app)?; + load_strategy(deps.as_ref())?.withdraw(deps.as_ref(), withdraw_share, app)?; Ok(withdraw_msgs.into_iter().collect()) } diff --git a/contracts/carrot-app/src/handlers/instantiate.rs b/contracts/carrot-app/src/handlers/instantiate.rs index 73dd9d00..7c3dc819 100644 --- a/contracts/carrot-app/src/handlers/instantiate.rs +++ b/contracts/carrot-app/src/handlers/instantiate.rs @@ -2,7 +2,7 @@ use crate::{ check::Checkable, contract::{App, AppResult}, msg::AppInstantiateMsg, - state::{CONFIG, STRATEGY_CONFIG}, + state::{save_strategy, CONFIG}, }; use abstract_app::abstract_sdk::AbstractResponse; use cosmwasm_std::{DepsMut, Env, MessageInfo}; @@ -10,7 +10,7 @@ use cosmwasm_std::{DepsMut, Env, MessageInfo}; use super::execute::_inner_deposit; pub fn instantiate_handler( - deps: DepsMut, + mut deps: DepsMut, env: Env, _info: MessageInfo, app: App, @@ -20,8 +20,8 @@ pub fn instantiate_handler( let config = msg.config.check(deps.as_ref(), &app)?; CONFIG.save(deps.storage, &config)?; - let strategy = msg.strategy.check(deps.as_ref(), &app)?; - STRATEGY_CONFIG.save(deps.storage, &strategy)?; + let mut strategy = msg.strategy.check(deps.as_ref(), &app)?; + save_strategy(deps.branch(), &mut strategy)?; let mut response = app.response("instantiate_savings_app"); diff --git a/contracts/carrot-app/src/handlers/internal.rs b/contracts/carrot-app/src/handlers/internal.rs index 95625cf4..5690f2ae 100644 --- a/contracts/carrot-app/src/handlers/internal.rs +++ b/contracts/carrot-app/src/handlers/internal.rs @@ -5,13 +5,9 @@ use crate::{ msg::{AppExecuteMsg, ExecuteMsg, InternalExecuteMsg}, replies::REPLY_AFTER_SWAPS_STEP, state::{ - CONFIG, STRATEGY_CONFIG, TEMP_CURRENT_COIN, TEMP_CURRENT_YIELD, TEMP_DEPOSIT_COINS, - TEMP_EXPECTED_SWAP_COIN, - }, - yield_sources::{ - yield_type::{YieldType, YieldTypeImplementation}, - Strategy, + CONFIG, TEMP_CURRENT_COIN, TEMP_CURRENT_YIELD, TEMP_DEPOSIT_COINS, TEMP_EXPECTED_SWAP_COIN, }, + yield_sources::yield_type::{YieldType, YieldTypeImplementation}, }; use abstract_app::{abstract_sdk::features::AbstractResponse, objects::AnsAsset}; use abstract_dex_adapter::DexInterface; @@ -149,7 +145,7 @@ pub fn execute_one_deposit_step( pub fn execute_finalize_deposit( deps: DepsMut, - yield_type: YieldType, + mut yield_type: YieldType, yield_index: usize, app: App, ) -> AppResult { @@ -161,8 +157,3 @@ pub fn execute_finalize_deposit( Ok(app.response("finalize-deposit").add_submessages(msgs)) } - -pub fn save_strategy(deps: DepsMut, strategy: Strategy) -> AppResult<()> { - STRATEGY_CONFIG.save(deps.storage, &strategy)?; - Ok(()) -} diff --git a/contracts/carrot-app/src/handlers/preview.rs b/contracts/carrot-app/src/handlers/preview.rs index 8f26660d..b8c60eaa 100644 --- a/contracts/carrot-app/src/handlers/preview.rs +++ b/contracts/carrot-app/src/handlers/preview.rs @@ -4,10 +4,10 @@ use cosmwasm_std::{Coin, Coins, Decimal, Deps, Uint128}; use crate::{ check::Checkable, contract::{App, AppResult}, - distribution::deposit::generate_deposit_strategy, + distribution::deposit::{generate_deposit_strategy, DepositStrategy}, error::AppError, msg::{DepositPreviewResponse, UpdateStrategyPreviewResponse, WithdrawPreviewResponse}, - state::STRATEGY_CONFIG, + state::load_strategy, yield_sources::{AssetShare, StrategyUnchecked}, }; @@ -19,16 +19,18 @@ pub fn deposit_preview( yield_source_params: Option>>>, app: &App, ) -> AppResult { - let target_strategy = STRATEGY_CONFIG.load(deps.storage)?; - let (withdraw_strategy, deposit_strategy) = - generate_deposit_strategy(deps, funds, target_strategy, yield_source_params, app)?; + let target_strategy = load_strategy(deps)?; + let DepositStrategy { + withdraw_strategy, + deposit_msgs, + } = generate_deposit_strategy(deps, funds, target_strategy, yield_source_params, app)?; Ok(DepositPreviewResponse { withdraw: withdraw_strategy .into_iter() .map(|(el, share)| (el.into(), share)) .collect(), - deposit: deposit_strategy, + deposit: deposit_msgs, }) } @@ -38,13 +40,10 @@ pub fn withdraw_preview( app: &App, ) -> AppResult { let withdraw_share = withdraw_share(deps, amount, app)?; - let funds = STRATEGY_CONFIG - .load(deps.storage)? - .withdraw_preview(deps, withdraw_share, app)?; + let mut strategy = load_strategy(deps)?; + let funds = strategy.withdraw_preview(deps, withdraw_share, app)?; - let msgs = STRATEGY_CONFIG - .load(deps.storage)? - .withdraw(deps, withdraw_share, app)?; + let msgs = strategy.withdraw(deps, withdraw_share, app)?; Ok(WithdrawPreviewResponse { share: withdraw_share.unwrap_or(Decimal::one()), @@ -61,7 +60,7 @@ pub fn update_strategy_preview( ) -> AppResult { // We withdraw outstanding strategies - let old_strategy = STRATEGY_CONFIG.load(deps.storage)?; + let old_strategy = load_strategy(deps)?; // We check the new strategy let strategy = strategy.check(deps, app)?; @@ -79,7 +78,7 @@ pub fn update_strategy_preview( all_stale_sources .clone() .into_iter() - .map(|s| { + .map(|mut s| { Ok::<_, AppError>(( s.withdraw_preview(deps, None, app).unwrap_or_default(), s.withdraw(deps, None, app).ok(), @@ -94,8 +93,10 @@ pub fn update_strategy_preview( .try_for_each(|f| f.into_iter().try_for_each(|f| available_funds.add(f)))?; // 3. We deposit the funds into the new strategy - let (withdraw_strategy, deposit_strategy) = - generate_deposit_strategy(deps, available_funds.into(), strategy, None, app)?; + let DepositStrategy { + withdraw_strategy, + deposit_msgs, + } = generate_deposit_strategy(deps, available_funds.into(), strategy, None, app)?; let withdraw_strategy = [ all_stale_sources @@ -111,6 +112,6 @@ pub fn update_strategy_preview( .into_iter() .map(|(el, share)| (el.into(), share)) .collect(), - deposit: deposit_strategy, + deposit: deposit_msgs, }) } diff --git a/contracts/carrot-app/src/handlers/query.rs b/contracts/carrot-app/src/handlers/query.rs index 3b505147..7eea03c6 100644 --- a/contracts/carrot-app/src/handlers/query.rs +++ b/contracts/carrot-app/src/handlers/query.rs @@ -10,7 +10,7 @@ use cw_asset::Asset; use crate::autocompound::get_autocompound_status; use crate::exchange_rate::query_exchange_rate; use crate::msg::{PositionResponse, PositionsResponse}; -use crate::state::STRATEGY_CONFIG; +use crate::state::load_strategy; use crate::yield_sources::yield_type::YieldTypeImplementation; use crate::{ contract::{App, AppResult}, @@ -99,7 +99,7 @@ fn query_compound_status(deps: Deps, env: Env, app: &App) -> AppResult AppResult { - let strategy = STRATEGY_CONFIG.load(deps.storage)?; + let strategy = load_strategy(deps)?; Ok(StrategyResponse { strategy: strategy.into(), @@ -107,7 +107,7 @@ pub fn query_strategy(deps: Deps) -> AppResult { } pub fn query_strategy_status(deps: Deps, app: &App) -> AppResult { - let strategy = STRATEGY_CONFIG.load(deps.storage)?; + let mut strategy = load_strategy(deps)?; Ok(StrategyResponse { strategy: strategy.query_current_status(deps, app)?.into(), @@ -122,8 +122,8 @@ pub fn query_balance(deps: Deps, app: &App) -> AppResult let mut funds = Coins::default(); let mut total_value = Uint128::zero(); - let strategy = STRATEGY_CONFIG.load(deps.storage)?; - strategy.0.iter().try_for_each(|s| { + let mut strategy = load_strategy(deps)?; + strategy.0.iter_mut().try_for_each(|s| { let deposit_value = s .yield_source .params @@ -144,10 +144,10 @@ 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 strategy = load_strategy(deps)?; let mut rewards = Coins::default(); - strategy.0.into_iter().try_for_each(|s| { + strategy.0.into_iter().try_for_each(|mut s| { let this_rewards = s.yield_source.params.user_rewards(deps, app)?; for fund in this_rewards { rewards.add(fund)?; @@ -162,11 +162,10 @@ fn query_rewards(deps: Deps, app: &App) -> AppResult { pub fn query_positions(deps: Deps, app: &App) -> AppResult { Ok(PositionsResponse { - positions: STRATEGY_CONFIG - .load(deps.storage)? + positions: load_strategy(deps)? .0 .into_iter() - .map(|s| { + .map(|mut s| { let balance = s.yield_source.params.user_deposit(deps, app)?; let liquidity = s.yield_source.params.user_liquidity(deps, app)?; diff --git a/contracts/carrot-app/src/replies/osmosis/add_to_position.rs b/contracts/carrot-app/src/replies/osmosis/add_to_position.rs index e65a6d4e..0e4829ee 100644 --- a/contracts/carrot-app/src/replies/osmosis/add_to_position.rs +++ b/contracts/carrot-app/src/replies/osmosis/add_to_position.rs @@ -5,8 +5,7 @@ use osmosis_std::types::osmosis::concentratedliquidity::v1beta1::MsgAddToPositio use crate::{ contract::{App, AppResult}, error::AppError, - handlers::internal::save_strategy, - state::{STRATEGY_CONFIG, TEMP_CURRENT_YIELD}, + state::{load_strategy, save_strategy, TEMP_CURRENT_YIELD}, yield_sources::yield_type::YieldType, }; @@ -25,7 +24,7 @@ pub fn add_to_position_reply(deps: DepsMut, _env: Env, app: App, reply: Reply) - // We update the position let current_position_index = TEMP_CURRENT_YIELD.load(deps.storage)?; - let mut strategy = STRATEGY_CONFIG.load(deps.storage)?; + let mut strategy = load_strategy(deps.as_ref())?; let current_yield = strategy.0.get_mut(current_position_index).unwrap(); @@ -37,7 +36,7 @@ pub fn add_to_position_reply(deps: DepsMut, _env: Env, app: App, reply: Reply) - YieldType::Mars(_) => return Err(AppError::WrongYieldType {}), }; - save_strategy(deps, strategy)?; + save_strategy(deps, &mut strategy)?; Ok(app .response("create_position_reply") diff --git a/contracts/carrot-app/src/replies/osmosis/create_position.rs b/contracts/carrot-app/src/replies/osmosis/create_position.rs index e9ba404c..1f180ad0 100644 --- a/contracts/carrot-app/src/replies/osmosis/create_position.rs +++ b/contracts/carrot-app/src/replies/osmosis/create_position.rs @@ -5,8 +5,7 @@ use osmosis_std::types::osmosis::concentratedliquidity::v1beta1::MsgCreatePositi use crate::{ contract::{App, AppResult}, error::AppError, - handlers::internal::save_strategy, - state::{STRATEGY_CONFIG, TEMP_CURRENT_YIELD}, + state::{load_strategy, save_strategy, TEMP_CURRENT_YIELD}, yield_sources::yield_type::YieldType, }; @@ -24,7 +23,7 @@ pub fn create_position_reply(deps: DepsMut, _env: Env, app: App, reply: Reply) - // We save the position let current_position_index = TEMP_CURRENT_YIELD.load(deps.storage)?; - let mut strategy = STRATEGY_CONFIG.load(deps.storage)?; + let mut strategy = load_strategy(deps.as_ref())?; let current_yield = strategy.0.get_mut(current_position_index).unwrap(); @@ -36,7 +35,7 @@ pub fn create_position_reply(deps: DepsMut, _env: Env, app: App, reply: Reply) - YieldType::Mars(_) => return Err(AppError::WrongYieldType {}), }; - save_strategy(deps, strategy)?; + save_strategy(deps, &mut strategy)?; Ok(app .response("create_position_reply") diff --git a/contracts/carrot-app/src/state.rs b/contracts/carrot-app/src/state.rs index 6b432a7f..948b87f5 100644 --- a/contracts/carrot-app/src/state.rs +++ b/contracts/carrot-app/src/state.rs @@ -1,13 +1,15 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Coin, Uint128}; +use cosmwasm_std::{Addr, Coin, Deps, DepsMut, StdResult, Uint128}; use cw_storage_plus::Item; use crate::autocompound::{AutocompoundConfigBase, AutocompoundState}; use crate::check::{Checked, Unchecked}; +use crate::contract::AppResult; use crate::yield_sources::Strategy; pub const CONFIG: Item = Item::new("config"); -pub const STRATEGY_CONFIG: Item = Item::new("strategy_config"); +/// Don't make this config public to avoid saving the cache inside the struct directly +const STRATEGY_CONFIG: Item = Item::new("strategy_config"); pub const AUTOCOMPOUND_STATE: Item = Item::new("position"); pub const CURRENT_EXECUTOR: Item = Item::new("executor"); @@ -25,3 +27,18 @@ pub struct ConfigBase { pub autocompound_config: AutocompoundConfigBase, pub dex: String, } + +pub fn load_strategy(deps: Deps) -> StdResult { + load_strategy(deps) +} + +pub fn save_strategy(deps: DepsMut, strategy: &mut Strategy) -> AppResult<()> { + // We need to correct positions for which the cache is not empty + // This is a security measure + strategy + .0 + .iter_mut() + .for_each(|s| s.yield_source.params.clear_cache()); + STRATEGY_CONFIG.save(deps.storage, &strategy)?; + Ok(()) +} diff --git a/contracts/carrot-app/src/yield_sources/mars.rs b/contracts/carrot-app/src/yield_sources/mars.rs index 48ccd22c..68bada38 100644 --- a/contracts/carrot-app/src/yield_sources/mars.rs +++ b/contracts/carrot-app/src/yield_sources/mars.rs @@ -20,7 +20,7 @@ pub struct MarsDepositParams { } impl YieldTypeImplementation for MarsDepositParams { - fn deposit(&self, deps: Deps, funds: Vec, app: &App) -> AppResult> { + fn deposit(&mut self, deps: Deps, funds: Vec, app: &App) -> AppResult> { let ans = app.name_service(deps); let ans_fund = ans.query(&AssetInfo::native(self.denom.clone()))?; @@ -31,7 +31,7 @@ impl YieldTypeImplementation for MarsDepositParams { } fn withdraw( - &self, + &mut self, deps: Deps, amount: Option, app: &App, @@ -51,12 +51,16 @@ impl YieldTypeImplementation for MarsDepositParams { .withdraw(AnsAsset::new(ans_fund, amount))?]) } - fn withdraw_rewards(&self, _deps: Deps, _app: &App) -> AppResult<(Vec, Vec)> { + fn withdraw_rewards( + &mut self, + _deps: Deps, + _app: &App, + ) -> AppResult<(Vec, Vec)> { // Mars doesn't have rewards, it's automatically auto-compounded Ok((vec![], vec![])) } - fn user_deposit(&self, deps: Deps, app: &App) -> AppResult> { + fn user_deposit(&mut self, deps: Deps, app: &App) -> AppResult> { let ans = app.name_service(deps); let asset = ans.query(&AssetInfo::native(self.denom.clone()))?; let user = app.account_base(deps)?.proxy; @@ -74,17 +78,20 @@ impl YieldTypeImplementation for MarsDepositParams { Ok(coins(deposit.u128(), self.denom.clone())) } - fn user_rewards(&self, _deps: Deps, _app: &App) -> AppResult> { + fn user_rewards(&mut self, _deps: Deps, _app: &App) -> AppResult> { // No rewards, because mars is already auto-compounding Ok(vec![]) } - fn user_liquidity(&self, deps: Deps, app: &App) -> AppResult { + fn user_liquidity(&mut self, deps: Deps, app: &App) -> AppResult { Ok(self.user_deposit(deps, app)?[0].amount) } - fn share_type(&self) -> super::ShareType { + fn share_type(&mut self) -> super::ShareType { ShareType::Fixed } + + // No cache for mars + fn clear_cache(&mut self) {} } 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 caa9421a..5d65f9e6 100644 --- a/contracts/carrot-app/src/yield_sources/osmosis_cl_pool.rs +++ b/contracts/carrot-app/src/yield_sources/osmosis_cl_pool.rs @@ -35,6 +35,8 @@ pub struct ConcentratedPoolParamsBase { // This is not actually a parameter but rather state // This can be used as a parameter for existing positions pub position_id: Option, + // This is a cache to avoid querying the position information as this query is expensive + pub position_cache: Option, pub _phantom: PhantomData, } @@ -42,7 +44,7 @@ pub type ConcentratedPoolParamsUnchecked = ConcentratedPoolParamsBase pub type ConcentratedPoolParams = ConcentratedPoolParamsBase; impl YieldTypeImplementation for ConcentratedPoolParams { - fn deposit(&self, deps: Deps, funds: Vec, app: &App) -> AppResult> { + fn deposit(&mut self, deps: Deps, funds: Vec, app: &App) -> AppResult> { // We verify there is a position stored if let Ok(position) = self.position(deps) { self.raw_deposit(deps, funds, app, position) @@ -53,7 +55,7 @@ impl YieldTypeImplementation for ConcentratedPoolParams { } fn withdraw( - &self, + &mut self, deps: Deps, amount: Option, app: &App, @@ -80,7 +82,11 @@ impl YieldTypeImplementation for ConcentratedPoolParams { .into()]) } - fn withdraw_rewards(&self, deps: Deps, app: &App) -> AppResult<(Vec, Vec)> { + fn withdraw_rewards( + &mut self, + deps: Deps, + app: &App, + ) -> AppResult<(Vec, Vec)> { let position = self.position(deps)?; let position_details = position.position.unwrap(); @@ -119,7 +125,7 @@ impl YieldTypeImplementation for ConcentratedPoolParams { } /// 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(&mut self, deps: Deps, _app: &App) -> AppResult> { let position = self.position(deps)?; Ok([ @@ -136,7 +142,7 @@ impl YieldTypeImplementation for ConcentratedPoolParams { .collect()) } - fn user_rewards(&self, deps: Deps, _app: &App) -> AppResult> { + fn user_rewards(&mut self, deps: Deps, _app: &App) -> AppResult> { let position = self.position(deps)?; let mut rewards = cosmwasm_std::Coins::default(); @@ -151,16 +157,20 @@ impl YieldTypeImplementation for ConcentratedPoolParams { Ok(rewards.into()) } - fn user_liquidity(&self, deps: Deps, _app: &App) -> AppResult { + fn user_liquidity(&mut self, deps: Deps, _app: &App) -> AppResult { let position = self.position(deps)?; let total_liquidity = position.position.unwrap().liquidity.replace('.', ""); Ok(Uint128::from_str(&total_liquidity)?) } - fn share_type(&self) -> super::ShareType { + fn share_type(&mut self) -> super::ShareType { ShareType::Dynamic } + + fn clear_cache(&mut self) { + self.position_cache = None; + } } impl ConcentratedPoolParams { @@ -250,13 +260,22 @@ impl ConcentratedPoolParams { Ok(vec![deposit_msg]) } - fn position(&self, deps: Deps) -> AppResult { - let position_id = self.position_id.ok_or(AppError::NoPosition {})?; - ConcentratedliquidityQuerier::new(&deps.querier) - .position_by_id(position_id) - .map_err(|e| AppError::UnableToQueryPosition(position_id, e))? - .position - .ok_or(AppError::NoPosition {}) + fn position(&mut self, deps: Deps) -> AppResult { + deps.api.debug("Getting osmosis position"); + if let Some(position) = &self.position_cache { + deps.api.debug("Getting osmosis position from cache"); + Ok(position.clone()) + } else { + let position_id = self.position_id.ok_or(AppError::NoPosition {})?; + let position = ConcentratedliquidityQuerier::new(&deps.querier) + .position_by_id(position_id) + .map_err(|e| AppError::UnableToQueryPosition(position_id, e))? + .position + .ok_or(AppError::NoPosition {})?; + + self.position_cache = Some(position.clone()); + Ok(position) + } } } diff --git a/contracts/carrot-app/src/yield_sources/yield_type.rs b/contracts/carrot-app/src/yield_sources/yield_type.rs index eca0bcec..6182952d 100644 --- a/contracts/carrot-app/src/yield_sources/yield_type.rs +++ b/contracts/carrot-app/src/yield_sources/yield_type.rs @@ -21,73 +21,99 @@ pub type YieldTypeUnchecked = YieldParamsBase; pub type YieldType = YieldParamsBase; impl YieldTypeImplementation for YieldType { - fn deposit(&self, deps: Deps, funds: Vec, app: &App) -> AppResult> { + fn deposit(&mut self, deps: Deps, funds: Vec, app: &App) -> AppResult> { if funds.is_empty() { return Ok(vec![]); } - self.inner().deposit(deps, funds, app) + self.inner_mut().deposit(deps, funds, app) } fn withdraw( - &self, + &mut self, deps: Deps, amount: Option, app: &App, ) -> AppResult> { - self.inner().withdraw(deps, amount, app) + self.inner_mut().withdraw(deps, amount, app) } - fn withdraw_rewards(&self, deps: Deps, app: &App) -> AppResult<(Vec, Vec)> { - self.inner().withdraw_rewards(deps, app) + fn withdraw_rewards( + &mut self, + deps: Deps, + app: &App, + ) -> AppResult<(Vec, Vec)> { + self.inner_mut().withdraw_rewards(deps, app) } - fn user_deposit(&self, deps: Deps, app: &App) -> AppResult> { - Ok(self.inner().user_deposit(deps, app).unwrap_or_default()) + fn user_deposit(&mut self, deps: Deps, app: &App) -> AppResult> { + Ok(self.inner_mut().user_deposit(deps, app).unwrap_or_default()) } - fn user_rewards(&self, deps: Deps, app: &App) -> AppResult> { - Ok(self.inner().user_rewards(deps, app).unwrap_or_default()) + fn user_rewards(&mut self, deps: Deps, app: &App) -> AppResult> { + Ok(self.inner_mut().user_rewards(deps, app).unwrap_or_default()) } - fn user_liquidity(&self, deps: Deps, app: &App) -> AppResult { - Ok(self.inner().user_liquidity(deps, app).unwrap_or_default()) + fn user_liquidity(&mut self, deps: Deps, app: &App) -> AppResult { + Ok(self + .inner_mut() + .user_liquidity(deps, app) + .unwrap_or_default()) } /// Indicate the default funds allocation /// This is specifically useful for auto-compound as we're not able to input target amounts /// CL pools use that to know the best funds deposit ratio /// Mars doesn't use that, because the share is fixed to 1 - fn share_type(&self) -> ShareType { - self.inner().share_type() + + fn share_type(&mut self) -> ShareType { + self.inner_mut().share_type() + } + + fn clear_cache(&mut self) { + self.inner_mut().clear_cache() } } impl YieldType { - fn inner(&self) -> &dyn YieldTypeImplementation { + fn inner_mut(&mut self) -> &mut dyn YieldTypeImplementation { match self { YieldType::ConcentratedLiquidityPool(params) => params, YieldType::Mars(params) => params, } } + + pub fn clear_cache(&mut self) { + match self { + YieldType::ConcentratedLiquidityPool(params) => params.clear_cache(), + YieldType::Mars(params) => params.clear_cache(), + } + } } pub trait YieldTypeImplementation { - fn deposit(&self, deps: Deps, funds: Vec, app: &App) -> AppResult>; + fn deposit(&mut self, deps: Deps, funds: Vec, app: &App) -> AppResult>; - fn withdraw(&self, deps: Deps, amount: Option, app: &App) - -> AppResult>; + fn withdraw( + &mut self, + deps: Deps, + amount: Option, + app: &App, + ) -> AppResult>; - fn withdraw_rewards(&self, deps: Deps, app: &App) -> AppResult<(Vec, Vec)>; + fn withdraw_rewards(&mut self, deps: Deps, app: &App) + -> AppResult<(Vec, Vec)>; - fn user_deposit(&self, deps: Deps, app: &App) -> AppResult>; + fn user_deposit(&mut self, deps: Deps, app: &App) -> AppResult>; - fn user_rewards(&self, deps: Deps, app: &App) -> AppResult>; + fn user_rewards(&mut self, deps: Deps, app: &App) -> AppResult>; - fn user_liquidity(&self, deps: Deps, app: &App) -> AppResult; + fn user_liquidity(&mut self, deps: Deps, app: &App) -> AppResult; /// Indicate the default funds allocation /// This is specifically useful for auto-compound as we're not able to input target amounts /// CL pools use that to know the best funds deposit ratio /// Mars doesn't use that, because the share is fixed to 1 - fn share_type(&self) -> ShareType; + fn share_type(&mut self) -> ShareType; + + fn clear_cache(&mut self); } diff --git a/contracts/carrot-app/tests/common.rs b/contracts/carrot-app/tests/common.rs index 5bedc582..619df7af 100644 --- a/contracts/carrot-app/tests/common.rs +++ b/contracts/carrot-app/tests/common.rs @@ -154,6 +154,7 @@ pub fn deploy( upper_tick: INITIAL_UPPER_TICK, position_id: None, _phantom: std::marker::PhantomData, + position_cache: None, }), }, share: Decimal::one(), diff --git a/contracts/carrot-app/tests/config.rs b/contracts/carrot-app/tests/config.rs index ce9f54c0..3ac218f3 100644 --- a/contracts/carrot-app/tests/config.rs +++ b/contracts/carrot-app/tests/config.rs @@ -40,6 +40,7 @@ fn rebalance_fails() -> anyhow::Result<()> { lower_tick: INITIAL_LOWER_TICK, upper_tick: INITIAL_UPPER_TICK, position_id: None, + position_cache: None, _phantom: std::marker::PhantomData, }, ), @@ -64,6 +65,7 @@ fn rebalance_fails() -> anyhow::Result<()> { lower_tick: INITIAL_LOWER_TICK, upper_tick: INITIAL_UPPER_TICK, position_id: None, + position_cache: None, _phantom: std::marker::PhantomData, }, ), @@ -103,6 +105,7 @@ fn rebalance_success() -> anyhow::Result<()> { upper_tick: INITIAL_UPPER_TICK, position_id: None, _phantom: std::marker::PhantomData, + position_cache: None, }), }, share: Decimal::percent(50), @@ -124,6 +127,7 @@ fn rebalance_success() -> anyhow::Result<()> { lower_tick: INITIAL_LOWER_TICK, upper_tick: INITIAL_UPPER_TICK, position_id: None, + position_cache: None, _phantom: std::marker::PhantomData, }), }, @@ -179,6 +183,7 @@ fn rebalance_with_new_pool_success() -> anyhow::Result<()> { lower_tick: INITIAL_LOWER_TICK, upper_tick: INITIAL_UPPER_TICK, position_id: None, + position_cache: None, _phantom: std::marker::PhantomData, }), }, @@ -201,6 +206,7 @@ fn rebalance_with_new_pool_success() -> anyhow::Result<()> { lower_tick: INITIAL_LOWER_TICK, upper_tick: INITIAL_UPPER_TICK, position_id: None, + position_cache: None, _phantom: std::marker::PhantomData, }), }, @@ -257,6 +263,7 @@ fn rebalance_with_stale_strategy_success() -> anyhow::Result<()> { lower_tick: INITIAL_LOWER_TICK, upper_tick: INITIAL_UPPER_TICK, position_id: None, + position_cache: None, _phantom: std::marker::PhantomData, }), }; @@ -283,6 +290,7 @@ fn rebalance_with_stale_strategy_success() -> anyhow::Result<()> { lower_tick: INITIAL_LOWER_TICK, upper_tick: INITIAL_UPPER_TICK, position_id: None, + position_cache: None, _phantom: std::marker::PhantomData, }), }, @@ -352,6 +360,7 @@ fn rebalance_with_current_and_stale_strategy_success() -> anyhow::Result<()> { lower_tick: INITIAL_LOWER_TICK, upper_tick: INITIAL_UPPER_TICK, position_id: None, + position_cache: None, _phantom: std::marker::PhantomData, }), }; @@ -374,6 +383,7 @@ fn rebalance_with_current_and_stale_strategy_success() -> anyhow::Result<()> { lower_tick: INITIAL_LOWER_TICK, upper_tick: INITIAL_UPPER_TICK, position_id: None, + position_cache: None, _phantom: std::marker::PhantomData, }), }, diff --git a/contracts/carrot-app/tests/deposit_withdraw.rs b/contracts/carrot-app/tests/deposit_withdraw.rs index 09a8a378..3c6b18c1 100644 --- a/contracts/carrot-app/tests/deposit_withdraw.rs +++ b/contracts/carrot-app/tests/deposit_withdraw.rs @@ -180,6 +180,7 @@ fn deposit_multiple_positions() -> anyhow::Result<()> { lower_tick: INITIAL_LOWER_TICK, upper_tick: INITIAL_UPPER_TICK, position_id: None, + position_cache: None, _phantom: std::marker::PhantomData, }), }, @@ -202,6 +203,7 @@ fn deposit_multiple_positions() -> anyhow::Result<()> { lower_tick: 2 * INITIAL_LOWER_TICK, upper_tick: 2 * INITIAL_UPPER_TICK, position_id: None, + position_cache: None, _phantom: std::marker::PhantomData, }), }, @@ -251,6 +253,7 @@ fn deposit_multiple_positions_with_empty() -> anyhow::Result<()> { lower_tick: INITIAL_LOWER_TICK, upper_tick: INITIAL_UPPER_TICK, position_id: None, + position_cache: None, _phantom: std::marker::PhantomData, }), }, @@ -273,6 +276,7 @@ fn deposit_multiple_positions_with_empty() -> anyhow::Result<()> { lower_tick: 2 * INITIAL_LOWER_TICK, upper_tick: 2 * INITIAL_UPPER_TICK, position_id: None, + position_cache: None, _phantom: std::marker::PhantomData, }), },