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,
}

* = optional

ExecuteMsg

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,
    },
}

* = optional

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
    }
}

* = optional

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,
    }
}

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
    }
}

* = optional

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.
    }
}

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>, 
    },

* = 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>,
    }
}

* = optional

QueryMsg

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

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,
}

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,
}

* = optional

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,
}

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,
}

* = optional

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,
}

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
}

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,
}

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,
}

Last updated