use crate::asserts::{assert_activate_status, assert_withdraw_amount};
use crate::querier::query_collateral_whitelist_info;
use crate::state::{
pop_bid_idx, read_bid, read_bid_pool, read_bids_by_user, read_collateral_info, read_config,
read_epoch_scale_sum, read_or_create_bid_pool, read_total_bids, remove_bid, store_bid,
store_bid_pool, store_epoch_scale_sum, store_total_bids, Bid, BidPool, CollateralInfo, Config,
};
use cosmwasm_std::{
attr, to_json_binary, CanonicalAddr, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdError,
StdResult, Storage, Uint128, WasmMsg, Uint256, Decimal256,
};
use cw20::Cw20ExecuteMsg;
use moneymarket::oracle::PriceResponse;
use moneymarket::querier::{query_price, TimeConstraints};
pub fn submit_bid(
deps: DepsMut,
env: Env,
bidder: String,
collateral_token: String,
premium_slot: u8,
amount: Uint256,
) -> StdResult<Response> {
let config: Config = read_config(deps.storage)?;
let collateral_token_validated: CanonicalAddr = deps.api.addr_canonicalize(&collateral_token)?;
let collateral_info: CollateralInfo =
read_collateral_info(deps.storage, &collateral_token_validated)?;
let bidder_validated = deps.api.addr_canonicalize(&bidder)?;
let mut bid_pool: BidPool =
read_or_create_bid_pool(deps.storage, &collateral_info, premium_slot)?;
let bid_idx: Uint128 = pop_bid_idx(deps.storage)?;
let mut bid = Bid {
idx: bid_idx,
bidder: bidder_validated,
collateral_token: collateral_token_validated.clone(),
product_snapshot: Decimal256::one(),
amount,
premium_slot,
sum_snapshot: Decimal256::zero(),
pending_liquidated_collateral: Uint256::zero(),
wait_end: None,
epoch_snapshot: Uint128::zero(),
scale_snapshot: Uint128::zero(),
};
let available_bids: Uint256 =
read_total_bids(deps.storage, &collateral_token_validated).unwrap_or_default();
if available_bids < collateral_info.bid_threshold {
process_bid_activation(&mut bid, &mut bid_pool, amount);
store_bid_pool(deps.storage, &collateral_token_validated, premium_slot, &bid_pool)?;
store_total_bids(deps.storage, &collateral_token_validated, available_bids + amount)?;
} else {
bid.wait_end = Some(env.block.time.plus_seconds(config.waiting_period).seconds());
};
store_bid(deps.storage, bid_idx, &bid)?;
Ok(Response::new().add_attributes(vec![
attr("action", "submit_bid"),
attr("bid_idx", bid_idx),
attr("amount", amount),
]))
}
pub fn activate_bids(
deps: DepsMut,
env: Env,
info: MessageInfo,
collateral_token: String,
bids_idx: Option<Vec<Uint128>>,
) -> StdResult<Response> {
let sender_validated: CanonicalAddr = deps.api.addr_canonicalize(info.sender.as_str())?;
let collateral_token_validated: CanonicalAddr = deps.api.addr_canonicalize(&collateral_token)?;
let collateral_info: CollateralInfo =
read_collateral_info(deps.storage, &collateral_token_validated)?;
let mut available_bids: Uint256 =
read_total_bids(deps.storage, &collateral_token_validated).unwrap_or_default();
let bids: Vec<Bid> = if let Some(bids_idx) = &bids_idx {
bids_idx
.iter()
.map(|idx| read_bid(deps.storage, *idx))
.collect::<StdResult<Vec<Bid>>>()?
} else {
read_bids_by_user(deps.storage, &collateral_token_validated, &sender_validated, None, None)?
.into_iter()
.filter(|b| b.wait_end.is_some())
.collect::<Vec<Bid>>()
};
let mut total_activated_amount = Uint256::zero();
for mut bid in bids.into_iter() {
if bid.bidder != sender_validated {
return Err(StdError::generic_err("unauthorized"));
}
if bid.collateral_token != collateral_token_validated {
return Err(StdError::generic_err("Bid collateral token doesn't match"));
}
let mut bid_pool: BidPool =
read_bid_pool(deps.storage, &bid.collateral_token, bid.premium_slot)?;
let amount_to_activate = bid.amount;
if let Err(err) =
assert_activate_status(&bid, &env, available_bids, collateral_info.bid_threshold)
{
if bids_idx.is_some() {
return Err(err);
} else {
continue;
}
}
process_bid_activation(&mut bid, &mut bid_pool, amount_to_activate);
store_bid(deps.storage, bid.idx, &bid)?;
store_bid_pool(
deps.storage,
&bid.collateral_token,
bid.premium_slot,
&bid_pool,
)?;
total_activated_amount += amount_to_activate;
available_bids += amount_to_activate;
}
store_total_bids(deps.storage, &collateral_token_validated, available_bids)?;
Ok(Response::new().add_attributes(vec![
attr("action", "activate_bids"),
attr("amount", total_activated_amount),
]))
}
pub fn retract_bid(
deps: DepsMut,
_env: Env,
info: MessageInfo,
bid_idx: Uint128,
amount: Option<Uint256>,
) -> StdResult<Response> {
let config: Config = read_config(deps.storage)?;
let sender_validated: CanonicalAddr = deps.api.addr_canonicalize(info.sender.as_str())?;
let mut bid: Bid = read_bid(deps.storage, bid_idx)?;
let collateral_token_validated: CanonicalAddr = bid.collateral_token.clone();
if bid.bidder != sender_validated {
return Err(StdError::generic_err("unauthorized"));
}
let withdraw_amount: Uint256 = if bid.wait_end.is_some() {
let waiting_withdraw_amount = assert_withdraw_amount(amount, bid.amount)?;
if waiting_withdraw_amount == bid.amount {
remove_bid(deps.storage, bid.idx)?;
} else {
bid.amount = bid.amount - waiting_withdraw_amount;
store_bid(deps.storage, bid.idx, &bid)?;
}
waiting_withdraw_amount
} else {
let available_bids: Uint256 = read_total_bids(deps.storage, &collateral_token_validated)?;
let mut bid_pool: BidPool =
read_bid_pool(deps.storage, &collateral_token_validated, bid.premium_slot)?;
let (withdrawable_amount, residue_bid) = calculate_remaining_bid(&bid, &bid_pool)?;
let (liquidated_collateral, residue_collateral) =
calculate_liquidated_collateral(deps.storage, &bid)?;
bid.pending_liquidated_collateral += liquidated_collateral;
bid_pool.residue_collateral += residue_collateral;
bid_pool.residue_bid += residue_bid;
let withdraw_amount: Uint256 = assert_withdraw_amount(amount, withdrawable_amount)?;
if withdraw_amount == withdrawable_amount && bid.pending_liquidated_collateral.is_zero() {
remove_bid(deps.storage, bid_idx)?;
} else {
store_bid(
deps.storage,
bid.idx,
&Bid {
amount: withdrawable_amount - withdraw_amount,
product_snapshot: bid_pool.product_snapshot,
sum_snapshot: bid_pool.sum_snapshot,
scale_snapshot: bid_pool.current_scale,
..bid
},
)?;
}
bid_pool.total_bid_amount = bid_pool.total_bid_amount - withdraw_amount;
let refund_amount = withdraw_amount + claim_bid_residue(&mut bid_pool);
store_bid_pool(
deps.storage,
&collateral_token_validated,
bid.premium_slot,
&bid_pool,
)?;
store_total_bids(
deps.storage,
&collateral_token_validated,
available_bids - withdraw_amount,
)?;
refund_amount
};
let mut messages: Vec<CosmosMsg> = vec![];
if !withdraw_amount.is_zero() {
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: deps
.api
.addr_humanize(&config.stable_contract.into())?
.to_string(),
funds: vec![],
msg: to_json_binary(&Cw20ExecuteMsg::Transfer {
recipient: info.sender.to_string(),
amount: withdraw_amount.into(),
})?,
}));
}
Ok(Response::new().add_messages(messages).add_attributes(vec![
attr("action", "retract_bid"),
attr("bid_idx", bid_idx),
attr("amount", withdraw_amount),
]))
}
#[allow(clippy::too_many_arguments)]
pub fn execute_liquidation(
deps: DepsMut,
env: Env,
sender: String,
liquidator: String,
repay_address: String,
fee_address: String,
collateral_token: String,
amount: Uint256,
) -> StdResult<Response> {
let config: Config = read_config(deps.storage)?;
let collateral_token_validated = deps.api.addr_canonicalize(&collateral_token)?;
let collateral_info: CollateralInfo =
read_collateral_info(deps.storage, &collateral_token_validated)?;
let available_bids: Uint256 = read_total_bids(deps.storage, &collateral_token_validated)?;
let overseer = deps.api.addr_humanize(&config.overseer)?;
let custody_contract = query_collateral_whitelist_info(
&deps.querier,
overseer.to_string(),
collateral_token.to_string(),
)?
.custody_contract;
if sender != custody_contract {
return Err(StdError::generic_err(
"Unauthorized: only custody contract can execute liquidations",
));
}
let oracle_contract = deps.api.addr_humanize(&config.oracle_contract)?;
let price: PriceResponse = query_price(
deps.as_ref(),
oracle_contract,
collateral_token.to_string(),
"stable0000".to_string(),
Some(TimeConstraints {
block_time: env.block.time.seconds(),
valid_timeframe: config.price_timeframe,
}),
)?;
let mut remaining_collateral_to_liquidate = amount;
let mut repay_amount = Uint256::zero();
let mut filled: bool = false;
for slot in 0..collateral_info.max_slot + 1 {
let mut bid_pool: BidPool = match read_bid_pool(deps.storage, &collateral_token_validated, slot) {
Ok(bid_pool) => bid_pool,
Err(_) => continue,
};
if bid_pool.total_bid_amount.is_zero() {
continue;
};
let (pool_repay_amount, pool_liquidated_collateral) = execute_pool_liquidation(
deps.storage,
&mut bid_pool,
&collateral_token_validated,
slot,
remaining_collateral_to_liquidate,
price.rate,
&mut filled,
)?;
store_bid_pool(deps.storage, &collateral_token_validated, slot, &bid_pool)?;
repay_amount += pool_repay_amount;
if filled {
remaining_collateral_to_liquidate = Uint256::zero();
break;
} else {
remaining_collateral_to_liquidate =
remaining_collateral_to_liquidate - pool_liquidated_collateral;
}
}
if !remaining_collateral_to_liquidate.is_zero() {
return Err(StdError::generic_err(
"Not enough bids to execute this liquidation",
));
}
store_total_bids(
deps.storage,
&collateral_token_validated,
available_bids - repay_amount,
)?;
let bid_fee = repay_amount * config.bid_fee;
let liquidator_fee = repay_amount * config.liquidator_fee;
let repay_amount = repay_amount - bid_fee - liquidator_fee;
let mut messages: Vec<CosmosMsg> = vec![CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: deps.api.addr_humanize(&config.stable_contract)?.to_string(),
funds: vec![],
msg: to_json_binary(&Cw20ExecuteMsg::Transfer {
recipient: repay_address,
amount: repay_amount.into(),
})?,
})];
if !bid_fee.is_zero() {
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: deps.api.addr_humanize(&config.stable_contract)?.to_string(),
funds: vec![],
msg: to_json_binary(&Cw20ExecuteMsg::Transfer {
recipient: fee_address,
amount: bid_fee.into(),
})?,
}));
}
if !liquidator_fee.is_zero() {
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: deps.api.addr_humanize(&config.stable_contract)?.to_string(),
funds: vec![],
msg: to_json_binary(&Cw20ExecuteMsg::Transfer {
recipient: liquidator,
amount: liquidator_fee.into(),
})?,
}));
}
Ok(Response::new().add_messages(messages).add_attributes(vec![
attr("action", "execute_bid"),
attr("stable_contract", config.stable_contract.to_string()),
attr("repay_amount", repay_amount),
attr("bid_fee", bid_fee),
attr("liquidator_fee", liquidator_fee),
attr("collateral_token", collateral_token),
attr("collateral_amount", amount),
]))
}
pub fn claim_liquidations(
deps: DepsMut,
_env: Env,
info: MessageInfo,
collateral_token: String,
bids_idx: Option<Vec<Uint128>>,
) -> StdResult<Response> {
let sender_validated = deps.api.addr_canonicalize(info.sender.as_str())?;
let collateral_token_validated = deps.api.addr_canonicalize(&collateral_token)?;
let bids: Vec<Bid> = if let Some(bids_idx) = bids_idx {
bids_idx
.iter()
.map(|idx| read_bid(deps.storage, *idx))
.collect::<StdResult<Vec<Bid>>>()?
} else {
read_bids_by_user(deps.storage, &collateral_token_validated, &sender_validated, None, None)?
};
let mut claim_amount = Uint256::zero();
for bid in bids.into_iter() {
if bid.bidder != sender_validated {
return Err(StdError::generic_err("unauthorized"));
}
if bid.collateral_token != collateral_token_validated {
return Err(StdError::generic_err("Bid collateral token doesn't match"));
}
if bid.wait_end.is_some() {
continue;
}
let mut bid_pool: BidPool =
read_bid_pool(deps.storage, &bid.collateral_token, bid.premium_slot)?;
let (remaining_bid, residue_bid) = calculate_remaining_bid(&bid, &bid_pool)?;
let (liquidated_collateral, residue_collateral) =
calculate_liquidated_collateral(deps.storage, &bid)?;
bid_pool.residue_collateral += residue_collateral;
bid_pool.residue_bid += residue_bid;
claim_amount += bid.pending_liquidated_collateral
+ liquidated_collateral
+ claim_col_residue(&mut bid_pool);
store_bid_pool(
deps.storage,
&collateral_token_validated,
bid.premium_slot,
&bid_pool,
)?;
if remaining_bid <= Uint256::one() {
remove_bid(deps.storage, bid.idx)?;
} else {
store_bid(
deps.storage,
bid.idx,
&Bid {
amount: remaining_bid,
product_snapshot: bid_pool.product_snapshot,
sum_snapshot: bid_pool.sum_snapshot,
scale_snapshot: bid_pool.current_scale,
pending_liquidated_collateral: Uint256::zero(),
..bid
},
)?;
}
}
let mut messages: Vec<CosmosMsg> = vec![];
if !claim_amount.is_zero() {
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: collateral_token.clone(),
funds: vec![],
msg: to_json_binary(&Cw20ExecuteMsg::Transfer {
recipient: info.sender.to_string(),
amount: claim_amount.into(),
})?,
}));
}
Ok(Response::new().add_messages(messages).add_attributes(vec![
attr("action", "claim_liquidations"),
attr("collateral_token", collateral_token),
attr("collateral_amount", claim_amount),
]))
}
fn process_bid_activation(bid: &mut Bid, bid_pool: &mut BidPool, amount: Uint256) {
bid.product_snapshot = bid_pool.product_snapshot;
bid.sum_snapshot = bid_pool.sum_snapshot;
bid.wait_end = None;
bid.scale_snapshot = bid_pool.current_scale;
bid.epoch_snapshot = bid_pool.current_epoch;
bid_pool.total_bid_amount += amount;
}
#[allow(clippy::too_many_arguments)]
fn execute_pool_liquidation(
storage: &mut dyn Storage,
bid_pool: &mut BidPool,
collateral_token: &CanonicalAddr,
premium_slot: u8,
collateral_to_liquidate: Uint256,
price: Decimal256,
filled: &mut bool,
) -> StdResult<(Uint256, Uint256)> {
let premium_price = price * (Decimal256::one() - bid_pool.premium_rate);
let mut pool_collateral_to_liquidate = collateral_to_liquidate;
let mut pool_required_stable = pool_collateral_to_liquidate * premium_price;
if pool_required_stable > bid_pool.total_bid_amount {
pool_required_stable = bid_pool.total_bid_amount;
pool_collateral_to_liquidate = pool_required_stable / premium_price;
} else {
*filled = true;
}
let col_per_bid: Decimal256 = Decimal256::from_uint256(pool_collateral_to_liquidate)
/ Decimal256::from_uint256(bid_pool.total_bid_amount);
let expense_per_bid: Decimal256 = Decimal256::from_uint256(pool_required_stable)
/ Decimal256::from_uint256(bid_pool.total_bid_amount);
let sum = bid_pool.product_snapshot * col_per_bid;
bid_pool.sum_snapshot += sum;
bid_pool.total_bid_amount = bid_pool.total_bid_amount - pool_required_stable;
store_epoch_scale_sum(
storage,
collateral_token,
premium_slot,
bid_pool.current_epoch,
bid_pool.current_scale,
bid_pool.sum_snapshot,
)?;
if expense_per_bid == Decimal256::one() {
bid_pool.sum_snapshot = Decimal256::zero();
bid_pool.product_snapshot = Decimal256::one();
bid_pool.current_scale = Uint128::zero();
bid_pool.current_epoch += Uint128::from(1u128);
} else {
let product = Decimal256::one() - expense_per_bid;
let new_product = bid_pool.product_snapshot * product;
bid_pool.product_snapshot = if new_product < Decimal256(U256::from(1_000_000_000u64)) {
bid_pool.current_scale += Uint128::from(1u128);
Decimal256(bid_pool.product_snapshot.0 * U256::from(1_000_000_000u64)) * product
} else {
new_product
};
}
Ok((pool_required_stable, pool_collateral_to_liquidate))
}
pub(crate) fn calculate_remaining_bid(
bid: &Bid,
bid_pool: &BidPool,
) -> StdResult<(Uint256, Decimal256)> {
let scale_diff: Uint128 = bid_pool.current_scale.checked_sub(bid.scale_snapshot)?;
let epoch_diff: Uint128 = bid_pool.current_epoch.checked_sub(bid.epoch_snapshot)?;
let remaining_bid_dec: Decimal256 = if !epoch_diff.is_zero() {
Decimal256::zero()
} else if scale_diff.is_zero() {
Decimal256::from_uint256(bid.amount) * bid_pool.product_snapshot / bid.product_snapshot
} else if scale_diff == Uint128::from(1u128) {
let scaled_remaining_bid =
Decimal256::from_uint256(bid.amount) * bid_pool.product_snapshot / bid.product_snapshot;
Decimal256(scaled_remaining_bid.0 / U256::from(1_000_000_000u64))
} else {
Decimal256::zero()
};
let remaining_bid = remaining_bid_dec * Uint256::one();
let bid_residue = remaining_bid_dec - Decimal256::from_uint256(remaining_bid);
Ok((remaining_bid, bid_residue))
}
pub(crate) fn calculate_liquidated_collateral(
storage: &dyn Storage,
bid: &Bid,
) -> StdResult<(Uint256, Decimal256)> {
let reference_sum_snapshot = read_epoch_scale_sum(
storage,
&bid.collateral_token,
bid.premium_slot,
bid.epoch_snapshot,
bid.scale_snapshot,
)
.unwrap_or_default();
let first_portion = reference_sum_snapshot - bid.sum_snapshot;
let second_portion = if let Ok(second_scale_sum_snapshot) = read_epoch_scale_sum(
storage,
&bid.collateral_token,
bid.premium_slot,
bid.epoch_snapshot,
bid.scale_snapshot + Uint128::from(1u128),
) {
Decimal256(
(second_scale_sum_snapshot.0 - reference_sum_snapshot.0) / U256::from(1_000_000_000u64),
)
} else {
Decimal256::zero()
};
let liquidated_collateral_dec = Decimal256::from_uint256(bid.amount)
* (first_portion + second_portion)
/ bid.product_snapshot;
let liquidated_collateral = liquidated_collateral_dec * Uint256::one();
let residue_collateral =
liquidated_collateral_dec - Decimal256::from_uint256(liquidated_collateral);
Ok((liquidated_collateral, residue_collateral))
}
fn claim_col_residue(bid_pool: &mut BidPool) -> Uint256 {
let claimable = bid_pool.residue_collateral * Uint256::one();
if !claimable.is_zero() {
bid_pool.residue_collateral =
bid_pool.residue_collateral - Decimal256::from_uint256(claimable);
}
claimable
}
fn claim_bid_residue(bid_pool: &mut BidPool) -> Uint256 {
let claimable = bid_pool.residue_bid * Uint256::one();
if !claimable.is_zero() {
bid_pool.residue_bid = bid_pool.residue_bid - Decimal256::from_uint256(claimable);
}
claimable
}