Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V2 Withdraw To Endpoint #71

Open
wants to merge 3 commits into
base: v2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contracts/carrot-app/examples/localnet_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fn main() -> anyhow::Result<()> {
carrot.deposit(coins(10_000, "uosmo"), None)?;

carrot.update_strategy(coins(10_000, "uosmo"), five_strategy())?;
carrot.withdraw(None)?;
carrot.withdraw(None, None)?;
carrot.deposit(coins(10_000, "uosmo"), None)?;

Ok(())
Expand Down
58 changes: 48 additions & 10 deletions contracts/carrot-app/src/handlers/execute.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
use super::internal::execute_internal_action;
use super::{internal::execute_internal_action, swap_helpers::MAX_SPREAD};
use crate::{
autocompound::AutocompoundState,
check::Checkable,
contract::{App, AppResult},
distribution::deposit::generate_deposit_strategy,
error::AppError,
exchange_rate::query_exchange_rate,
handlers::query::query_balance,
helpers::assert_contract,
msg::{AppExecuteMsg, ExecuteMsg},
state::{AUTOCOMPOUND_STATE, CONFIG, STRATEGY_CONFIG},
yield_sources::{AssetShare, Strategy, StrategyUnchecked},
};
use abstract_app::abstract_sdk::features::AbstractResponse;
use abstract_app::traits::AbstractNameService;
use abstract_app::{abstract_sdk::features::AbstractResponse, objects::AssetEntry};
use abstract_dex_adapter::DexInterface;
use abstract_sdk::ExecutorMsg;
use cosmwasm_std::{
to_json_binary, Coin, Coins, CosmosMsg, Decimal, Deps, DepsMut, Env, MessageInfo, Uint128,
WasmMsg,
};
use cw_asset::Asset;

pub fn execute_handler(
deps: DepsMut,
Expand All @@ -30,7 +34,9 @@ pub fn execute_handler(
funds,
yield_sources_params,
} => deposit(deps, env, info, funds, yield_sources_params, app),
AppExecuteMsg::Withdraw { amount } => withdraw(deps, env, info, amount, app),
AppExecuteMsg::Withdraw { value, swap_to } => {
withdraw(deps, env, info, value, swap_to, app)
}
AppExecuteMsg::Autocompound {} => autocompound(deps, env, info, app),
AppExecuteMsg::UpdateStrategy { strategy, funds } => {
update_strategy(deps, env, info, strategy, funds, app)
Expand Down Expand Up @@ -81,12 +87,13 @@ fn withdraw(
env: Env,
info: MessageInfo,
amount: Option<Uint128>,
swap_to: Option<AssetEntry>,
app: App,
) -> AppResult {
// Only the authorized addresses (admin ?) can withdraw
app.admin.assert_admin(deps.as_ref(), &info.sender)?;

let msgs = _inner_withdraw(deps, &env, amount, &app)?;
let msgs = _inner_withdraw(deps, &env, amount, swap_to, &app)?;

Ok(app.response("withdraw").add_messages(msgs))
}
Expand Down Expand Up @@ -225,8 +232,9 @@ fn _inner_withdraw(
deps: DepsMut,
_env: &Env,
value: Option<Uint128>,
swap_to: Option<AssetEntry>,
app: &App,
) -> AppResult<Vec<ExecutorMsg>> {
) -> AppResult<Vec<CosmosMsg>> {
// We need to select the share of each investment that needs to be withdrawn
let withdraw_share = value
.map(|value| {
Expand All @@ -240,10 +248,40 @@ fn _inner_withdraw(
.transpose()?;

// We withdraw the necessary share from all registered investments
let withdraw_msgs =
STRATEGY_CONFIG
.load(deps.storage)?
.withdraw(deps.as_ref(), withdraw_share, app)?;
let mut withdraw_msgs: Vec<CosmosMsg> = STRATEGY_CONFIG
.load(deps.storage)?
.withdraw(deps.as_ref(), withdraw_share, app)?
.into_iter()
.map(Into::into)
.collect();

if let Some(swap_to) = swap_to {
let withdraw_preview = STRATEGY_CONFIG.load(deps.storage)?.withdraw_preview(
deps.as_ref(),
withdraw_share,
app,
)?;

// We swap all withdraw_preview coins to the swap asset
let config = CONFIG.load(deps.storage)?;
let ans = app.name_service(deps.as_ref());
let dex = app.ans_dex(deps.as_ref(), config.dex);
withdraw_preview.into_iter().try_for_each(|fund| {
let asset = ans.query(&Asset::from(fund.clone()))?;
if asset.name != swap_to {
let swap_msg = dex.swap(
asset,
swap_to.clone(),
Some(MAX_SPREAD),
Some(query_exchange_rate(deps.as_ref(), fund.denom, app)?),
)?;
withdraw_msgs.push(swap_msg);
}
Ok::<_, AppError>(())
})?;
}

deps.api.debug(&format!("{:?}", withdraw_msgs));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

about gas situation, noticed you have a lot of debugs. Want to mention that it will still eat gas for converting variable to debug string, and to allocate formatted string even if it does not get printed. Not really sure how much it is actually, but just to keep in mind before next gas measurements

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes you are right ! This is very low though compared to what the app is actually eating !


Ok(withdraw_msgs.into_iter().collect())
Ok(withdraw_msgs)
}
9 changes: 2 additions & 7 deletions contracts/carrot-app/src/handlers/swap_helpers.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use abstract_app::objects::{AnsAsset, AssetEntry};
use abstract_dex_adapter::DexInterface;
use cosmwasm_std::{CosmosMsg, Decimal, Deps, Env};
const MAX_SPREAD_PERCENT: u64 = 20;
pub const MAX_SPREAD: Decimal = Decimal::percent(20);
pub const DEFAULT_SLIPPAGE: Decimal = Decimal::permille(5);

use crate::contract::{App, AppResult, OSMOSIS};
Expand All @@ -19,12 +19,7 @@ pub(crate) fn swap_msg(
}

let dex = app.ans_dex(deps, OSMOSIS.to_string());
let swap_msg = dex.swap(
offer_asset,
ask_asset,
Some(Decimal::percent(MAX_SPREAD_PERCENT)),
None,
)?;
let swap_msg = dex.swap(offer_asset, ask_asset, Some(MAX_SPREAD), None)?;

Ok(Some(swap_msg))
}
6 changes: 5 additions & 1 deletion contracts/carrot-app/src/msg.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use abstract_app::objects::AssetEntry;
use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::{wasm_execute, Coin, CosmosMsg, Decimal, Env, Uint128, Uint64};
use cw_asset::AssetBase;
Expand Down Expand Up @@ -47,7 +48,10 @@ pub enum AppExecuteMsg {
},
/// Partial withdraw of the funds available on the app
/// If amount is omitted, withdraws everything that is on the app
Withdraw { amount: Option<Uint128> },
Withdraw {
value: Option<Uint128>,
swap_to: Option<AssetEntry>,
},
/// Auto-compounds the pool rewards into the pool
Autocompound {},
/// Rebalances all investments according to a new balance strategy
Expand Down
5 changes: 2 additions & 3 deletions contracts/carrot-app/tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ pub fn deploy<Chain: MutCwEnv + Stargate>(
gas_pool_id: u64,
initial_deposit: Option<Vec<Coin>>,
) -> anyhow::Result<Application<Chain, carrot_app::AppInterface<Chain>>> {
let asset0 = USDT.to_owned();
let asset1 = USDC.to_owned();
let asset0 = USDC.to_owned();
let asset1 = USDT.to_owned();
// We register the pool inside the Abstract ANS
let client = AbstractClient::builder(chain.clone())
.dex(DEX_NAME)
Expand Down Expand Up @@ -89,7 +89,6 @@ pub fn deploy<Chain: MutCwEnv + Stargate>(
// We deploy the carrot_app
let publisher = client
.publisher_builder(Namespace::new("abstract")?)
.install_on_sub_account(false)
.build()?;
// The dex adapter
let dex_adapter = publisher
Expand Down
47 changes: 44 additions & 3 deletions contracts/carrot-app/tests/deposit_withdraw.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand All @@ -12,7 +13,7 @@ use carrot_app::{
AppInterface,
};
use common::{INITIAL_LOWER_TICK, INITIAL_UPPER_TICK};
use cosmwasm_std::{coin, coins, Decimal, Uint128};
use cosmwasm_std::{coin, coins, Coins, Decimal, Uint128};
use cw_orch::{anyhow, prelude::*};

fn query_balances<Chain: CwEnv>(
Expand Down Expand Up @@ -96,7 +97,7 @@ fn withdraw_position() -> anyhow::Result<()> {
// Withdraw some of the value
let liquidity_amount: Uint128 = balance.balances[0].amount;
let half_of_liquidity = liquidity_amount / Uint128::new(2);
carrot_app.withdraw(Some(half_of_liquidity))?;
carrot_app.withdraw(None, Some(half_of_liquidity))?;

let balance_usdc_after_half_withdraw = chain
.bank_querier()
Expand All @@ -113,7 +114,7 @@ fn withdraw_position() -> anyhow::Result<()> {
assert!(balance_usdt_after_half_withdraw.amount > balance_usdt_before_withdraw.amount);

// Withdraw rest of liquidity
carrot_app.withdraw(None)?;
carrot_app.withdraw(None, None)?;
let balance_usdc_after_full_withdraw = chain
.bank_querier()
.balance(chain.sender(), Some(USDT.to_owned()))?
Expand Down Expand Up @@ -312,6 +313,46 @@ fn create_position_on_instantiation() -> anyhow::Result<()> {
Ok(())
}

#[test]
fn withdraw_to() -> 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 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)?;

// Check almost everything landed and there is 2 types of coins
let balances_after = query_balances(&carrot_app)?;
assert!(balances_before < balances_after);

let balances_before = chain.query_all_balances(carrot_app.account().proxy()?.as_str())?;
// Withdraw everything
carrot_app.withdraw(Some(AssetEntry::new(USDT)), None)?;

// Check that the proxy only has one asset type back
let mut balances_after: Coins = chain
.query_all_balances(carrot_app.account().proxy()?.as_str())?
.try_into()?;

for f in balances_before {
balances_after.sub(f)?;
}
// In balances before remains only the withdrawn funds
assert_eq!(balances_after.len(), 1);

Ok(())
}

// #[test]
// fn withdraw_after_user_withdraw_liquidity_manually() -> anyhow::Result<()> {
// let (_, carrot_app) = setup_test_tube(true)?;
Expand Down