dexter_multi_staking/execute/
unbond.rs

1use crate::{
2    contract::{update_staking_rewards, ContractResult, CONTRACT_NAME},
3    state::USER_LP_TOKEN_LOCKS,
4};
5use const_format::concatcp;
6use cosmwasm_std::{Addr, DepsMut, Env, Event, MessageInfo, Response, Uint128};
7
8use dexter::{
9    asset::AssetInfo,
10    helper::build_transfer_token_to_user_msg,
11    multi_staking::{Config, TokenLock, MAX_USER_LP_TOKEN_LOCKS},
12};
13
14use dexter::helper::EventExt;
15
16use crate::{
17    error::ContractError,
18    state::{CONFIG, LP_GLOBAL_STATE, USER_BONDED_LP_TOKENS},
19};
20
21/// Allows to instantly unbond LP tokens without waiting for the unlock period
22/// This is a special case and should only be used in emergencies like a black swan event or a hack.
23/// The user will pay a penalty fee in the form of a percentage of the unbonded amount which will be
24/// sent to the keeper i.e. protocol treasury.
25pub fn instant_unbond(
26    mut deps: DepsMut,
27    env: Env,
28    info: MessageInfo,
29    lp_token: Addr,
30    amount: Uint128,
31) -> ContractResult<Response> {
32    let mut response = Response::new();
33
34    if amount.is_zero() {
35        return Err(ContractError::ZeroAmount);
36    }
37
38    let current_bond_amount = USER_BONDED_LP_TOKENS
39        .may_load(deps.storage, (&lp_token, &info.sender))?
40        .unwrap_or_default();
41
42    let config: Config = CONFIG.load(deps.storage)?;
43    let mut lp_global_state = LP_GLOBAL_STATE.load(deps.storage, &lp_token)?;
44
45    let user_updated_bond_amount = current_bond_amount.checked_sub(amount).map_err(|_| {
46        ContractError::CantUnbondMoreThanBonded {
47            amount_to_unbond: amount,
48            current_bond_amount,
49        }
50    })?;
51
52    for asset in &lp_global_state.active_reward_assets {
53        update_staking_rewards(
54            asset,
55            &lp_token,
56            &info.sender,
57            lp_global_state.total_bond_amount,
58            current_bond_amount,
59            env.block.time.seconds(),
60            &mut deps,
61            &mut response,
62            None,
63        )?;
64    }
65
66    // Decrease bond amount
67    lp_global_state.total_bond_amount = lp_global_state.total_bond_amount.checked_sub(amount)?;
68    LP_GLOBAL_STATE.save(deps.storage, &lp_token, &lp_global_state)?;
69
70    USER_BONDED_LP_TOKENS.save(
71        deps.storage,
72        (&lp_token, &info.sender),
73        &user_updated_bond_amount,
74    )?;
75
76    // whole instant unbond fee is sent to the keeper as protocol treasury
77    let instant_unbond_fee = amount.multiply_ratio(config.instant_unbond_fee_bp, Uint128::from(10000u128));
78
79    // Check if the keeper is available, if not, send the fee to the contract owner
80    let fee_receiver = config.keeper;
81
82    // Send the instant unbond fee to the keeper as protocol treasury
83    let fee_msg = build_transfer_token_to_user_msg(
84        AssetInfo::token(lp_token.clone()),
85        fee_receiver,
86        instant_unbond_fee,
87    )?;
88    response = response.add_message(fee_msg);
89
90    // Send the unbonded amount to the user
91    let unbond_msg = build_transfer_token_to_user_msg(
92        AssetInfo::token(lp_token.clone()),
93        info.sender.clone(),
94        amount.checked_sub(instant_unbond_fee)?,
95    )?;
96    response = response.add_message(unbond_msg);
97
98    let event = Event::from_info(concatcp!(CONTRACT_NAME, "::instant_unbond"), &info)
99        .add_attribute("lp_token", lp_token)
100        .add_attribute("amount", amount)
101        .add_attribute("total_bond_amount", lp_global_state.total_bond_amount)
102        .add_attribute("user_updated_bond_amount", user_updated_bond_amount)
103        .add_attribute("instant_unbond_fee", instant_unbond_fee)
104        .add_attribute("user_withdrawn_amount", amount.checked_sub(instant_unbond_fee)?);
105
106    response = response.add_event(event);
107    Ok(response)
108}
109
110/// Unbond LP tokens
111pub fn unbond(
112    mut deps: DepsMut,
113    env: Env,
114    info: MessageInfo,
115    lp_token: Addr,
116    amount: Option<Uint128>,
117) -> ContractResult<Response> {
118    // We don't have to check for LP token allowed here, because there's a scenario that we allowed bonding
119    // for an asset earlier and then we remove the LP token from the list of allowed LP tokens. In this case
120    // we still want to allow unbonding.
121    let mut response = Response::new();
122
123    let current_bond_amount = USER_BONDED_LP_TOKENS
124        .may_load(deps.storage, (&lp_token, &info.sender))?
125        .unwrap_or_default();
126
127    // if user didn't explicitly mention any amount, unbond everything.
128    let amount = amount.unwrap_or(current_bond_amount);
129    if amount.is_zero() {
130        return Err(ContractError::ZeroAmount);
131    }
132
133    let mut lp_global_state = LP_GLOBAL_STATE.load(deps.storage, &lp_token)?;
134    for asset in &lp_global_state.active_reward_assets {
135        update_staking_rewards(
136            asset,
137            &lp_token,
138            &info.sender,
139            lp_global_state.total_bond_amount,
140            current_bond_amount,
141            env.block.time.seconds(),
142            &mut deps,
143            &mut response,
144            None,
145        )?;
146    }
147
148    // Decrease bond amount
149    lp_global_state.total_bond_amount = lp_global_state.total_bond_amount.checked_sub(amount)?;
150    LP_GLOBAL_STATE.save(deps.storage, &lp_token, &lp_global_state)?;
151
152    let user_updated_bond_amount = current_bond_amount.checked_sub(amount).map_err(|_| {
153        ContractError::CantUnbondMoreThanBonded {
154            amount_to_unbond: amount,
155            current_bond_amount,
156        }
157    })?;
158
159    USER_BONDED_LP_TOKENS.save(
160        deps.storage,
161        (&lp_token, &info.sender),
162        &user_updated_bond_amount,
163    )?;
164
165    // Start unlocking clock for the user's LP Tokens
166    let mut unlocks = USER_LP_TOKEN_LOCKS
167        .may_load(deps.storage, (&lp_token, &info.sender))?
168        .unwrap_or_default();
169
170    if unlocks.len() == MAX_USER_LP_TOKEN_LOCKS {
171        return Err(ContractError::CantAllowAnyMoreLpTokenUnbonds);
172    }
173
174    let config = CONFIG.load(deps.storage)?;
175
176    let unlock_time = env.block.time.seconds() + config.unlock_period;
177    unlocks.push(TokenLock {
178        unlock_time,
179        amount,
180    });
181
182    USER_LP_TOKEN_LOCKS.save(deps.storage, (&lp_token, &info.sender), &unlocks)?;
183
184    let event = Event::from_info(concatcp!(CONTRACT_NAME, "::unbond"), &info)
185        .add_attribute("lp_token", lp_token)
186        .add_attribute("amount", amount)
187        .add_attribute("total_bond_amount", lp_global_state.total_bond_amount)
188        .add_attribute("user_updated_bond_amount", user_updated_bond_amount)
189        .add_attribute("unlock_time", unlock_time.to_string());
190
191    response = response.add_event(event);
192    Ok(response)
193}