Liquidation Queue
Fork of Anchor Protocol's implementation with slight modifications
The Liquidation contract enables users to submit CDP token bids for a Cw20 or native sdk token. Bidders can submit a bid to one of the bid pools; each of the pools deposited funds are used to buy the liquidated collateral at different discount rates. There are 21 slots per collateral, from 0% to 20%; users can bid on one or more slots.
Upon execution of a bid, collateral tokens are allocated to the bidder, while the bidder's bid tokens are sent to repay the liquidated position.
Bids are consumed from the bid pools in increasing order of premium rate (e.g 2% bids are only consumed after 0% and 1% pools are emptied). The liquidated collateral is then allocated to the bidders in the affected pools in proportion to their bid amount. The respective collateral has to be claimed manually by the bidders.
To prevent bots from sniping loans, submitted bids are only activated after wait_period
has expired, unless the total bid amount falls under the bid_threshold
, in which case bids will be directly activated upon submission.
Warning: The cumulative threshold of the frequented slots should be larger than the largest single liquidation amount to prevent waiting bids from causing InsufficientBids errors.
Source
https://docs.anchorprotocol.com/smart-contracts/liquidations/liquidation-queue-contract https://github.com/Anchor-Protocol/money-market-contracts/tree/main/contracts/liquidation_queue
Modifications
Automatic activation after
wait_period
elaspes. This increases computation time in return for less reliance on external contract calls.Liquidations send the RepayMsg for the position in the Positions contract
Prices are taken from input by the Positions contract, the messages are guaranteed the same block so the price can be up to block_time + Position's config oracle_time_limit second's old.
The position is assumed insolvent since called by the Positions contract, ie there is no additional solvency check in this contract.
ExecuteMsg::Liquidate doesn't take any assets up front, instead receiving assets in the Reply fn of the Positions contract
Removed bid_with, instead saving the bid_asset from the Positions contract
Added minimum_bid amount & maximum_waiting_bids to config
Created a separate Vector for PremiumSlot waiting bids
Submitted bids on the (bid) threshold get split into 1 active bid & 1 waiting bid
InstantiateMsg
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
pub owner: Option<String>,
pub positions_contract: String,
pub osmosis_proxy_contract: String,
pub waiting_period: u64, //seconds
pub minimum_bid: Uint128,
pub maximum_waiting_bids: u64,
}
*owner
String
Owner of the contract, defaults to info.sender
positions_contract
String
CDP contract
osmosis_proxy_contract
String
Osmosis proxy contract
waiting_period
u64
Waiting period for bids (secs)
*minimum_bid
Uint128
Minimum bid amount
*maximum_waiting_bid
u64
Maximum waiting bids
* = optional
ExecuteMsg
SubmitBid
SubmitBid
Submit a Bid alongside an accepted native SDK asset to a Queue and corresponding PremiumSlot
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
SubmitBid { //Deposit a list of accepted assets
bid_input: BidInput,
bid_owner: Option<String>,
}
}
pub struct BidInput{
pub bid_for: AssetInfo,
pub liq_premium: u8, //Premium within range of Queue
}
pub enum AssetInfo {
Token{
address: Addr,
},
NativeToken{
denom: String,
},
}
bid_input
BidInput
Information on what asset to bid for and at what premium
*bid_owner
String
Owner of the bid, defaults to info.sender
* = optional
RetractBid
RetractBid
Withdraw bid
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
RetractBid { //Withdraw a list of accepted assets
bid_id: Uint128,
bid_for: AssetInfo,
amount: Option<Uint256>, //If none, retracts full bid
}
}
bid_id
Uint128
ID of bid to withdraw
bid_for
AssetInfo
Asset queue to withdraw from
*amount
Uint256
Amount to withdraw
* = optional
Liquidate
Liquidate
Repays for a Position and earns discounted collateral for the bidders. Only used by the owner (ie Positions contract)
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
Liquidate {
//From Positions Contract
credit_price: PriceResponse,
collateral_price: PriceResponse,
collateral_amount: Uint256,
bid_for: AssetInfo,
}
}
credit_price
PriceResponse
Credit repayment price
collateral_price
PriceResponse
Collateral TWAP price
collateral_amount
Uint256
Collateral amount to liquidate
bid_for
AssetInfo
Collateral asset info
ClaimLiquidations
ClaimLiquidations
Claim liquidations for a list of bids
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
ClaimLiquidations {
bid_for: AssetInfo,
bid_ids: Option<Vec<Uint128>>, //None = All bids in the queue
}
}
bid_for
AssetInfo
Info of asset queue to claim from
*bid_ids
Vec<Uint128>
List of bids to claim from, if None
it claims all available user bids
* = optional
AddQueue
AddQueue
Add Queue to the contract
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
AddQueue{
bid_for: AssetInfo,
max_premium: Uint128, //A slot for each premium is created when queue is created
bid_threshold: Uint256, //Minimum bid amount. Unlocks waiting bids if total_bids is less than.
}
}
bid_for
AssetInfo
Asset to bid for
max_premium
Uint128
Max premium for the Queue's range
bid_threshold
Uint256
Minimum total bids before waiting bids unlock immediately
UpdateQueue
UpdateQueue
Update existing Queue
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
UpdateQueue{
bid_for: AssetInfo, //To signla which queue to edit. You can't edit the bid_for asset.
max_premium: Option<Uint128>,
bid_threshold: Option<Uint256>,
},
bid_for
AssetInfo
Asset to bid for
*max_premium
Uint128
Max premium for the Queue's range
*bid_threshold
Uint256
Minimum total bids before waiting bids unlock immediately
* = optional
UpdateConfig
Update Config
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
UpdateConfig{
owner: Option<String>,
positions_contract: Option<String>,
osmosis_proxy_contract: Option<String>,
waiting_period: Option<u64>,
minimum_bid: Option<Uint128>,
maximum_waiting_bids: Option<u64>,
}
}
*owner
String
Owner of the contract
*positions_contract
String
Positions contract
*osmosis_proxy_contract
String
Osmosis Proxy contract
*waiting_period
u64
Bid waiting period in seconds
*minimum_bid
Uint128
Minimum Bid amount
*maximum_waiting_bids
u64
Maximum waiting bids
* = optional
QueryMsg
Config
Config
Returns Config
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
Config {}
}
pub struct Config {
pub owner: String,
pub positions_contract: Addr,
pub osmosis_proxy_contract: Addr,
pub added_assets: Option<Vec<AssetInfo>>,
pub waiting_period: u64,
pub bid_asset: AssetInfo,
pub minimum_bid: Uint128,
pub maximum_waiting_bids: u64,
}
Bid
Bid
Returns a Bid
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
Bid {
bid_for: AssetInfo,
bid_id: Uint128,
}
}
pub struct BidResponse {
pub user: String,
pub id: Uint128,
pub amount: Uint256,
pub liq_premium: u8,
pub product_snapshot: Decimal256,
pub sum_snapshot: Decimal256,
pub pending_liquidated_collateral: Uint256,
pub wait_end: Option<u64>,
pub epoch_snapshot: Uint128,
pub scale_snapshot: Uint128,
}
bid_for
AssetInfo
Asset the bid is for
bid_id
Uint128
Bid ID
BidsByUser
BidsByUser
Returns Bids by user in a Queue
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
BidsByUser{
bid_for: AssetInfo,
user: String,
limit: Option<u8>,
start_after: Option<Uint128>,
}
}
pub struct BidResponse {
pub user: String,
pub id: Uint128,
pub amount: Uint256,
pub liq_premium: u8,
pub product_snapshot: Decimal256,
pub sum_snapshot: Decimal256,
pub pending_liquidated_collateral: Uint256,
pub wait_end: Option<u64>,
pub epoch_snapshot: Uint128,
pub scale_snapshot: Uint128,
}
bid_for
AssetInfo
Asset the Queue is bidding for
user
String
Bid owner
*limit
u32
Limit to returned bids
*start_after
Uint128
Start after bid id
* = optional
Queue
Queue
Returns Queue
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
Queue {
bid_for: AssetInfo,
}
}
pub struct QueueResponse {
pub bid_asset: String,
pub max_premium: String,
pub slots: Vec<PremiumSlot>,
pub current_bid_id: String,
pub bid_threshold: String,
}
pub struct PremiumSlot {
pub bids: Vec<Bid>,
pub liq_premium: Decimal256, //
pub sum_snapshot: Decimal256,
pub product_snapshot: Decimal256,
pub total_bid_amount: Uint256,
pub last_total: u64, //last time the bids have been totaled
pub current_epoch: Uint128,
pub current_scale: Uint128,
pub residue_collateral: Decimal256,
pub residue_bid: Decimal256,
}
bid_for
AssetInfo
Asset the Queue is bidding for
Queues
Returns Queues
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
Queues{
start_after: Option<AssetInfo>,
limit: Option<u8>,
}
}
pub struct QueueResponse {
pub bid_asset: String,
pub max_premium: String,
pub slots: Vec<PremiumSlot>,
pub current_bid_id: String,
pub bid_threshold: String,
}
*start_after
AssetInfo
Asset Queue to start after
*limit
u8
Limit to # of users parsed through
* = optional
CheckLiquidatible
CheckLiquidatible
Check if collateral amount is liquidatible
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
//Check if the amount of said asset is liquidatible
//Position's contract is sending its basket.credit_price
CheckLiquidatible {
bid_for: AssetInfo,
collateral_price: PriceResponse,
collateral_amount: Uint256,
credit_info: AssetInfo,
credit_price: PriceResponse,
}
}
pub struct LiquidatibleResponse {
pub leftover_collateral: String,
pub total_credit_repaid: String,
}
bid_for
AssetInfo
Asset the Queue is bidding for
collateral_price
PriceResponse
Price of collateral being bid for
collateral_amount
Uint256
Collateral amount
credit_info
AssetInfo
Asset being bid with
credit_price
PriceResponse
Bid_with price
UserClaims
UserClaims
Returns a users claimable assets
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
//Check if user has any claimable assets
UserClaims { user: String }
}
pub struct ClaimsResponse {
pub bid_for: String,
pub pending_liquidated_collateral: Uint256
}
user
String
User's claims to check
PremiumSlot
PremiumSlot
Returns info for a Queue's PremiumSlot
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
PremiumSlot {
bid_for: AssetInfo,
premium: u64, //Taken as %. 50 = 50%
}
}
pub struct SlotResponse {
pub bids: Vec<Bid>,
pub liq_premium: String,
pub sum_snapshot: String,
pub product_snapshot: String,
pub total_bid_amount: String,
pub current_epoch: Uint128,
pub current_scale: Uint128,
pub residue_collateral: String,
pub residue_bid: String,
}
pub struct Bid {
pub user: Addr,
pub id: Uint128,
pub amount: Uint256,
pub liq_premium: u8,
pub product_snapshot: Decimal256,
pub sum_snapshot: Decimal256,
pub pending_liquidated_collateral: Uint256,
pub wait_end: Option<u64>,
pub epoch_snapshot: Uint128,
pub scale_snapshot: Uint128,
}
bid_for
AssetInfo
Asset the Queue is bidding for
premium
u64
Which premium slot to query
PremiumSlots
Returns all of a Queue's PremiumSlots
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
PremiumSlots {
bid_for: AssetInfo,
start_after: Option<u64>, //Start after a premium value taken as a %.( 50 = 50%)
limit: Option<u8>,
}
}
pub struct SlotResponse {
pub bids: Vec<Bid>,
pub waiting_bids: Vec<Bid>,
pub liq_premium: String,
pub sum_snapshot: String,
pub product_snapshot: String,
pub total_bid_amount: String,
pub current_epoch: Uint128,
pub current_scale: Uint128,
pub residue_collateral: String,
pub residue_bid: String,
}
bid_fpr
AssetInfo
Asset the Queue is bidding for
*start_after
AssetInfo
Asset Queue to start after
*limit
u8
Limit to # of slots returned
Last updated