added partial liquidation

This commit is contained in:
dd 2021-04-03 09:27:57 -04:00
parent d6234ea4ee
commit ccbe33d907
8 changed files with 532 additions and 70 deletions

64
cli/Cargo.lock generated
View File

@ -104,6 +104,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "az"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "822d7d63e0c0260a050f6b1f0d316f5c79b9eab830aca526ed904e1011bd64ca"
[[package]]
name = "backtrace"
version = "0.3.56"
@ -1068,14 +1074,51 @@ dependencies = [
[[package]]
name = "fixed"
version = "0.5.7"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f32ca1abdbb21d63a3e02a658a9e3001b172f13c8b46724299e21190c5ee5041"
checksum = "b3cd35dc49a38c999fb8bc36a8c8680ad97bf7b9b499d852b1d82e5dd09f73f9"
dependencies = [
"az",
"half",
"serde",
"typenum",
]
[[package]]
name = "fixed-macro"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6b2cae66e4989f93364a38a59a12e1830afe3201c7c11e5ea8727534831fbe"
dependencies = [
"fixed",
"fixed-macro-impl",
"fixed-macro-types",
]
[[package]]
name = "fixed-macro-impl"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "787fa8e0bf88449e84799f4b440a15bd1958c4552a80abc568d5ba9e20a4283e"
dependencies = [
"fixed",
"paste 1.0.5",
"proc-macro-error",
"proc-macro2 1.0.24",
"quote 1.0.9",
"syn 1.0.60",
]
[[package]]
name = "fixed-macro-types"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c7241f3a037641b460153db21d5611c789e7e0504bf52e20a9b4bbe1d7cc00"
dependencies = [
"fixed",
"fixed-macro-impl",
]
[[package]]
name = "flate2"
version = "1.0.20"
@ -1337,6 +1380,12 @@ dependencies = [
"tracing-futures",
]
[[package]]
name = "half"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
[[package]]
name = "hash32"
version = "0.1.1"
@ -1611,7 +1660,7 @@ checksum = "c502a5ff9dd2924f1ed32ba96e3b65735d837b4bfd978d3161b1702e66aca4b7"
dependencies = [
"jemalloc-sys",
"libc",
"paste",
"paste 0.1.18",
]
[[package]]
@ -1772,7 +1821,7 @@ dependencies = [
[[package]]
name = "mango"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"arrayref",
"bincode",
@ -1780,6 +1829,7 @@ dependencies = [
"byteorder",
"enumflags2",
"fixed",
"fixed-macro",
"flux-aggregator",
"num-derive",
"num_enum",
@ -2243,6 +2293,12 @@ dependencies = [
"proc-macro-hack",
]
[[package]]
name = "paste"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
[[package]]
name = "paste-impl"
version = "0.1.18"

View File

@ -13,11 +13,11 @@ path = "src/main.rs"
[dependencies]
anyhow = "1.0.36"
clap = "3.0.0-beta.2"
solana-client = "1.5.14"
solana-cli = "1.5.16"
solana-sdk = "1.5.16"
solana-client = "~1.5"
solana-cli = "~1.5"
solana-sdk = "~1.5"
mango = { version = "*", path = "../program", features=["no-entrypoint"] }
spl-token = { version = "*", features=["no-entrypoint"] }
spl-token = { version = "^3.1.0", features=["no-entrypoint"] }
serde_json = "1.0.60"
chrono = "*"
common = { version = "*", path = "../common" }
@ -26,4 +26,4 @@ serum_dex = { version = "^0.2", git = "https://github.com/project-serum/serum-de
flux-aggregator = { version = "^0.1", git = "https://github.com/blockworks-foundation/solana-flux-aggregator.git", features=["program", "no-entrypoint"] }
arrayref = "^0.3.6"
fixed = { version = "=0.5.7" }
fixed = { version = "1.7.0" }

View File

@ -8,10 +8,13 @@ fi
# deploy mango program and new mango group
source ~/mango/cli/devnet.env $KEYPAIR
solana config set --url $DEVNET_URL
cd ~/mango
pushd program
# build bpf for devnet (just do cargo build-bpf for the mainnet version
mkdir target/devnet
cargo build-bpf --features devnet --bpf-out-dir target/devnet
# this will give a separate program id for devnet

114
program/Cargo.lock generated
View File

@ -1,5 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "ahash"
version = "0.4.7"
@ -44,6 +46,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "az"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "822d7d63e0c0260a050f6b1f0d316f5c79b9eab830aca526ed904e1011bd64ca"
[[package]]
name = "bincode"
version = "1.3.2"
@ -290,14 +298,51 @@ dependencies = [
[[package]]
name = "fixed"
version = "0.5.7"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f32ca1abdbb21d63a3e02a658a9e3001b172f13c8b46724299e21190c5ee5041"
checksum = "b3cd35dc49a38c999fb8bc36a8c8680ad97bf7b9b499d852b1d82e5dd09f73f9"
dependencies = [
"az",
"half",
"serde",
"typenum",
]
[[package]]
name = "fixed-macro"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6b2cae66e4989f93364a38a59a12e1830afe3201c7c11e5ea8727534831fbe"
dependencies = [
"fixed",
"fixed-macro-impl",
"fixed-macro-types",
]
[[package]]
name = "fixed-macro-impl"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "787fa8e0bf88449e84799f4b440a15bd1958c4552a80abc568d5ba9e20a4283e"
dependencies = [
"fixed",
"paste",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "fixed-macro-types"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c7241f3a037641b460153db21d5611c789e7e0504bf52e20a9b4bbe1d7cc00"
dependencies = [
"fixed",
"fixed-macro-impl",
]
[[package]]
name = "flux-aggregator"
version = "0.1.0"
@ -345,6 +390,12 @@ dependencies = [
"wasi",
]
[[package]]
name = "half"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
[[package]]
name = "hashbrown"
version = "0.9.1"
@ -392,9 +443,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.91"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7"
checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714"
[[package]]
name = "log"
@ -415,6 +466,7 @@ dependencies = [
"byteorder",
"enumflags2",
"fixed",
"fixed-macro",
"flux-aggregator",
"num-derive",
"num_enum",
@ -499,6 +551,12 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "paste"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
[[package]]
name = "pest"
version = "2.1.3"
@ -523,6 +581,30 @@ dependencies = [
"toml",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.24"
@ -729,9 +811,9 @@ dependencies = [
[[package]]
name = "solana-frozen-abi"
version = "1.5.16"
version = "1.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0acf010e4089c7027a65822c92a0c8c928f4ff9f8fa6fb9035b26bdb45c2730c"
checksum = "c3f571559d39de6ff0bcad239541bce1272628ad6ce7a453b785dd769a9e4a30"
dependencies = [
"bs58",
"bv",
@ -749,9 +831,9 @@ dependencies = [
[[package]]
name = "solana-frozen-abi-macro"
version = "1.5.16"
version = "1.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51e857abb4f59a4aacf2fb79b04edd295a801bccb46912de854f33d7d32e4f8c"
checksum = "980db1fdc868336e4e00bc410e10cf4376060f2ea20e220cef75049b9d179450"
dependencies = [
"lazy_static",
"proc-macro2",
@ -762,9 +844,9 @@ dependencies = [
[[package]]
name = "solana-logger"
version = "1.5.16"
version = "1.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a6b92c7d2abb55b5e4f66eef851ac877b5505d8f3198d872832b1d153562d04"
checksum = "d2a89cdddcf35ef945aba5d4bb62e761c150fedecddb49e44b16ee673000e6aa"
dependencies = [
"env_logger",
"lazy_static",
@ -773,9 +855,9 @@ dependencies = [
[[package]]
name = "solana-program"
version = "1.5.16"
version = "1.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7247d5d6d4037064ee4aa65ee3abe7a1818ea6663710db3d2a65ffe72d830e3"
checksum = "fd14a879d877f2712487109c1d2949a266837a250791ccf676ab606796715c61"
dependencies = [
"bincode",
"borsh 0.8.2",
@ -805,9 +887,9 @@ dependencies = [
[[package]]
name = "solana-sdk-macro"
version = "1.5.16"
version = "1.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9b3785480f2f1953b1d2962c00f4fa8a6b08461bbbb09aab9830030f90c9e7"
checksum = "6d763cfa55a605a2e07326b9eafbb9a66227b441cb00db7c482b806e2b9768b4"
dependencies = [
"bs58",
"proc-macro2",
@ -844,9 +926,9 @@ checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
[[package]]
name = "syn"
version = "1.0.65"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663"
checksum = "6498a9efc342871f91cc2d0d694c674368b4ceb40f62b65a7a08c3792935e702"
dependencies = [
"proc-macro2",
"quote",

View File

@ -9,7 +9,7 @@ no-entrypoint = []
devnet = []
[dependencies]
solana-program = "=1.5.16"
solana-program = "~1.5"
spl-token = { version = "^3.0.0", features=["no-entrypoint"] }
byteorder = "^1.3.4"
arrayref = "^0.3.6"
@ -25,7 +25,8 @@ serum_dex = { version = "^0.2", git = "https://github.com/project-serum/serum-de
num-derive = "^0.3.3"
flux-aggregator = { version = "^0.1", git = "https://github.com/blockworks-foundation/solana-flux-aggregator.git", features=["program", "no-entrypoint"] }
fixed = { version = "=0.5.7", features=["serde"] }
fixed = { version = "1.7.0", features=["serde"] }
fixed-macro = "1.1.1"
[profile.release]
lto = true

View File

@ -16,7 +16,6 @@ pub enum SourceFileId {
Processor = 0,
#[error("src/state.rs")]
State = 1,
}
#[derive(Debug, Clone, Eq, PartialEq)]
@ -47,7 +46,7 @@ pub enum MangoError {
#[error(transparent)]
ProgramError(#[from] ProgramError),
#[error("{mango_error_code}; source: {source_file_id}:{line}")]
MangoErrorCode { mango_error_code: MangoErrorCode, line: u32, source_file_id: SourceFileId}, // Just wraps MangoErrorCode into MangoError::MangoErrorCode
MangoErrorCode { mango_error_code: MangoErrorCode, line: u32, source_file_id: SourceFileId},
#[error(transparent)]
AssertionError(#[from] AssertionError)
}
@ -55,8 +54,8 @@ pub enum MangoError {
#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, IntoPrimitive)]
#[repr(u32)]
pub enum MangoErrorCode {
#[error("MangoErrorCode::AboveBorrowLimit This transaction would exceed the borrow limit")]
AboveBorrowLimit,
#[error("MangoErrorCode::BorrowLimitExceeded This instruction would exceed the borrow limit")]
BorrowLimitExceeded,
#[error("MangoErrorCode::CollateralRatioLimit Your collateral ratio is below the minimum initial collateral ratio")]
CollateralRatioLimit,
#[error("MangoErrorCode::InsufficientFunds Quantity requested is above the available balance")]
@ -73,8 +72,18 @@ pub enum MangoErrorCode {
GroupNotRentExempt,
#[error("MangoErrorCode::InvalidSignerKey")]
InvalidSignerKey,
#[error("MangoErrorCode::InvalidProgramId")]
InvalidProgramId,
#[error("MangoErrorCode::NotLiquidatable")]
NotLiquidatable,
#[error("MangoErrorCode::InvalidOpenOrdersAccount")]
InvalidOpenOrdersAccount,
#[error("MangoErrorCode::SignerNecessary")]
SignerNecessary,
#[error("MangoErrorCode::InvalidMangoVault")]
InvalidMangoVault,
#[error("MangoErrorCode::Default")]
#[error("MangoErrorCode::Default Check the source code for more info")]
Default = u32::MAX_VALUE,
}
@ -82,7 +91,11 @@ impl From<MangoError> for ProgramError {
fn from(e: MangoError) -> ProgramError {
match e {
MangoError::ProgramError(pe) => pe,
MangoError::MangoErrorCode { mango_error_code, line: _, source_file_id: _ } => ProgramError::Custom(mango_error_code.into()),
MangoError::MangoErrorCode {
mango_error_code,
line: _,
source_file_id: _
} => ProgramError::Custom(mango_error_code.into()),
MangoError::AssertionError(ae) => ProgramError::Custom(ae.into())
}
}

View File

@ -3,25 +3,26 @@ use std::mem::size_of;
use arrayref::{array_ref, array_refs};
use fixed::types::U64F64;
use fixed_macro::types::U64F64;
use flux_aggregator::borsh_state::InitBorshState;
use serum_dex::matching::Side;
use serum_dex::state::ToAlignedBytes;
use solana_program::account_info::AccountInfo;
use solana_program::clock::Clock;
use solana_program::entrypoint::ProgramResult;
use solana_program::instruction::{AccountMeta, Instruction};
use solana_program::msg;
use solana_program::program_error::ProgramError;
use solana_program::program_pack::{IsInitialized, Pack};
use solana_program::pubkey::Pubkey;
use solana_program::rent::Rent;
use solana_program::sysvar::{Sysvar};
use solana_program::sysvar::Sysvar;
use spl_token::state::{Account, Mint};
use crate::error::{check_assert, MangoResult, SourceFileId, MangoErrorCode, check_assert2};
use crate::instruction::{MangoInstruction};
use crate::state::{AccountFlag, check_open_orders, load_market_state, load_open_orders, Loadable, MangoGroup, MangoIndex, MarginAccount, NUM_MARKETS, NUM_TOKENS, MangoSrmAccount};
use crate::error::{check_assert, check_assert2, MangoErrorCode, MangoResult, SourceFileId};
use crate::instruction::MangoInstruction;
use crate::state::{AccountFlag, check_open_orders, load_market_state, load_open_orders, Loadable, MangoGroup, MangoIndex, MangoSrmAccount, MarginAccount, NUM_MARKETS, NUM_TOKENS, ONE_U64F64, ZERO_U64F64, PARTIAL_LIQ_INCENTIVE};
use crate::utils::{gen_signer_key, gen_signer_seeds};
use solana_program::entrypoint::ProgramResult;
macro_rules! prog_assert {
($cond:expr) => {
@ -51,12 +52,14 @@ macro_rules! prog_assert_eq2 {
mod srm_token {
use solana_program::declare_id;
#[cfg(feature = "devnet")]
declare_id!("9FbAMDvXqNjPqZSYt4EWTguJuDrGkfvwr3gSFpiSbX9S");
#[cfg(not(feature = "devnet"))]
declare_id!("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt");
}
pub const LIQ_MIN_COLL_RATIO: U64F64 = U64F64!(1.01);
pub struct Processor {}
@ -138,8 +141,8 @@ impl Processor {
mango_group.vaults[i] = *vault_acc.key;
mango_group.indexes[i] = MangoIndex {
last_update: curr_ts,
borrow: U64F64::from_num(1),
deposit: U64F64::from_num(1) // Smallest unit of interest is 0.0001% or 0.000001
borrow: ONE_U64F64,
deposit: ONE_U64F64 // Smallest unit of interest is 0.0001% or 0.000001
};
mango_group.mint_decimals[i] = mint.decimals;
}
@ -440,8 +443,6 @@ impl Processor {
clock_acc
] = fixed_accs;
// margin ratio = equity / val(borrowed)
// equity = val(positions) - val(borrowed) + val(collateral)
prog_assert!(liqor_acc.is_signer)?;
let mut mango_group = MangoGroup::load_mut_checked(
mango_group_acc, program_id
@ -459,10 +460,11 @@ impl Processor {
let prices = get_prices(&mango_group, oracle_accs)?;
let coll_ratio = liqee_margin_account.get_collateral_ratio(
&mango_group, &prices, open_orders_accs)?;
&mango_group, &prices, open_orders_accs
)?;
// No liquidations if account above maint collateral ratio
prog_assert!(coll_ratio < mango_group.maint_coll_ratio)?;
prog_assert2!(coll_ratio < mango_group.maint_coll_ratio, MangoErrorCode::NotLiquidatable)?;
// Settle borrows to see if it gets us above maint
for i in 0..NUM_TOKENS {
@ -470,21 +472,22 @@ impl Processor {
settle_borrow_unchecked(&mut mango_group, &mut liqee_margin_account, i, native_borrow)?;
}
let coll_ratio = liqee_margin_account.get_collateral_ratio(
&mango_group, &prices, open_orders_accs)?;
&mango_group, &prices, open_orders_accs
)?;
if coll_ratio >= mango_group.maint_coll_ratio { // if account not liquidatable after settle borrow, then return
return Ok(())
}
// TODO liquidator may forcefully SettleFunds and SettleBorrow on account with less than maint
if coll_ratio < U64F64::from_num(1) {
if coll_ratio < ONE_U64F64 {
let liabs = liqee_margin_account.get_total_liabs(&mango_group)?;
let liabs_val = liqee_margin_account.get_liabs_val(&mango_group, &prices)?;
let assets_val = liqee_margin_account.get_assets_val(&mango_group, &prices, open_orders_accs)?;
// reduction_val = amount of quote currency value to reduce liabilities by to get coll_ratio = 1.01
let reduction_val = liabs_val
.checked_sub(assets_val / U64F64::from_num(1.01)).unwrap();
.checked_sub(assets_val / LIQ_MIN_COLL_RATIO).unwrap();
for i in 0..NUM_TOKENS {
let proportion = U64F64::from_num(liabs[i])
@ -1141,7 +1144,7 @@ impl Processor {
prog_assert!(!reduce_only)?; // Cannot borrow more in reduce only mode
checked_add_borrow(&mut mango_group, &mut margin_account, out_token_i, rem_spend / out_index.borrow)?;
prog_assert2!(margin_account.get_native_borrow(&out_index, out_token_i) <= mango_group.borrow_limits[out_token_i], MangoErrorCode::AboveBorrowLimit)?;
prog_assert2!(margin_account.get_native_borrow(&out_index, out_token_i) <= mango_group.borrow_limits[out_token_i], MangoErrorCode::BorrowLimitExceeded)?;
} else { // just spend user deposits
let mango_spent = U64F64::from_num(total_out) / out_index.deposit;
checked_sub_deposit(&mut mango_group, &mut margin_account, out_token_i, mango_spent)?;
@ -1166,6 +1169,209 @@ impl Processor {
Ok(())
}
#[inline(never)]
fn partial_liquidate(
program_id: &Pubkey,
accounts: &[AccountInfo],
deposit_quantities: [u64; NUM_TOKENS],
max_deposit: u64
) -> MangoResult<()> {
// Cancel as many orders as possible for the selected market
// put account in liquidation mode
// if account is_liquidating, then no new orders can be created until account gets above init_coll_ratio
//
// cancel orders,
// settle funds
// settle borrows
// allow liquidations up until the account gets above init collateral ratio
// if account hits 0 deposits, socialize losses
// offset borrows
const NUM_FIXED: usize = 14;
// TODO make it so canceling orders feature is optional if no orders outstanding to cancel
let accounts = array_ref![accounts, 0, NUM_FIXED + 2 * NUM_MARKETS + NUM_TOKENS];
let (
fixed_accs,
open_orders_accs,
oracle_accs,
vault_accs,
) = array_refs![accounts, NUM_FIXED, NUM_MARKETS, NUM_MARKETS, NUM_TOKENS];
let [
mango_group_acc,
liqor_acc,
liqor_in_token_acc,
liqor_out_token_acc,
liqee_margin_account_acc,
spot_market_acc,
bids_acc,
asks_acc,
signer_acc,
dex_event_queue_acc,
dex_base_acc,
dex_quote_acc,
token_prog_acc,
dex_prog_acc,
] = fixed_accs;
prog_assert_eq2!(token_prog_acc.key, &spl_token::id(), MangoErrorCode::InvalidProgramId)?;
prog_assert2!(liqor_acc.is_signer, MangoErrorCode::SignerNecessary)?;
let mut mango_group = MangoGroup::load_mut_checked(
mango_group_acc, program_id
)?;
prog_assert_eq2!(dex_prog_acc.key, &mango_group.dex_program_id, MangoErrorCode::InvalidProgramId)?;
prog_assert_eq2!(signer_acc.key, &mango_group.signer_key, MangoErrorCode::InvalidSignerKey)?;
for i in 0..NUM_TOKENS {
prog_assert_eq2!(&mango_group.vaults[i], vault_accs[i].key, MangoErrorCode::InvalidMangoVault)?;
}
let mut liqee_margin_account = MarginAccount::load_mut_checked(
program_id, liqee_margin_account_acc, mango_group_acc.key
)?;
for i in 0..NUM_MARKETS {
prog_assert_eq2!(open_orders_accs[i].key, &liqee_margin_account.open_orders[i],
MangoErrorCode::InvalidOpenOrdersAccount)?;
check_open_orders(&open_orders_accs[i], &mango_group.signer_key)?;
}
let prices = get_prices(&mango_group, oracle_accs)?;
let coll_ratio = liqee_margin_account.get_collateral_ratio(
&mango_group, &prices, open_orders_accs
)?;
// Only allow liquidations on accounts already being liquidated and below init or accounts below maint
if liqee_margin_account.being_liquidated {
if coll_ratio >= mango_group.init_coll_ratio {
// TODO do same check in place_and_settle
liqee_margin_account.being_liquidated = false;
return Ok(());
}
} else {
prog_assert2!(coll_ratio < mango_group.maint_coll_ratio, MangoErrorCode::NotLiquidatable)?;
}
// Force cancel some orders to recoup funds for liquidator to withdraw
let market_index = mango_group.get_market_index(spot_market_acc.key).unwrap();
let open_orders_acc = &open_orders_acc[market_index];
let signers_seeds = gen_signer_seeds(&mango_group.signer_nonce, mango_group_acc.key);
cancel_all(open_orders_acc, dex_prog_acc, spot_market_acc, bids_acc, asks_acc, signer_acc,
dex_event_queue_acc, dex_base_acc, dex_quote_acc,
&vault_accs[market_index], &vault_accs[NUM_MARKETS],
dex_signer_acc, token_prog_acc, &[&signers_seeds], 5)?;
for i in 0..NUM_TOKENS {
settle_borrow_full_unchecked(&mut mango_group, &mut liqee_margin_account, i)?;
}
// Check again to see if account still liquidatable
let coll_ratio = liqee_margin_account.get_collateral_ratio(
&mango_group, &prices, open_orders_accs
)?;
if liqee_margin_account.being_liquidated {
if coll_ratio >= mango_group.init_coll_ratio {
// TODO make sure liquidator knows why tx was success but he didn't receive any funds
liqee_margin_account.being_liquidated = false;
return Ok(());
}
} else if coll_ratio >= mango_group.maint_coll_ratio {
return Ok(());
} else {
liqee_margin_account.being_liquidated = true;
// TODO make it so you can't place orders when account is being liquidated
// TODO set max orders allowed
}
// Now try to liquidate
let liqor_in_token_account = Account::unpack(&liqor_in_token_acc.try_borrow_data()?)?;
let in_token_index = mango_group.get_token_index(&liqor_in_token_account.mint).unwrap();
let liqor_out_token_account = Account::unpack(&liqor_out_token_acc.try_borrow_data()?)?;
let out_token_index = mango_group.get_token_index(&liqor_out_token_account.mint).unwrap();
prog_assert2!(in_token_index != out_token_index, MangoErrorCode::Default)?;
// Calculate maximum possible deposit amount
let deficit_val = liqee_margin_account.get_partial_liq_deficit(
&mango_group, &prices, open_orders_accs)?;
let out_withdrawable = liqee_margin_account.get_native_deposit(
&mango_group.indexes[out_token_index], out_token_index);
let val_out_withdrawable = U64F64::from_num(out_withdrawable)
.checked_mul(prices[out_token_index]).unwrap();
// Can only deposit as much as it is possible to withdraw out_token
let max_in_val = val_out_withdrawable / PARTIAL_LIQ_INCENTIVE;
let max_in_val = if deficit_val < max_in_val {
deficit_val
} else {
max_in_val
};
// we know prices are not 0; if they are this will error;
// TODO price could be very small
// TODO verify all the rounding errors
let max_in: U64F64 = max_in_val / prices[in_token_index];
let max_in_token: u64 = max_in.checked_ceil().unwrap().to_num();
let native_borrow = liqee_margin_account.get_native_borrow(
&mango_group.indexes[in_token_index], in_token_index);
// Can only deposit as much there is borrows to offset in in_token
let in_quantity = std::cmp::min(std::cmp::min(max_in_token, native_borrow), max_deposit);
let deposit_instruction = spl_token::instruction::transfer(
&spl_token::id(),
liqor_in_token_acc.key,
vault_accs[in_token_index].key,
&liqor_acc.key, &[], in_quantity
)?;
let deposit_accs = [
liqor_in_token_acc.clone(),
vault_accs[in_token_index].clone(),
liqor_acc.clone(),
token_prog_acc.clone()
];
solana_program::program::invoke_signed(&deposit_instruction, &deposit_accs, &[])?;
let deposit: U64F64 = U64F64::from_num(in_quantity) / mango_group.indexes[in_token_index].borrow;
checked_sub_borrow(&mut mango_group, &mut liqee_margin_account, in_token_index, deposit)?;
// Withdraw
let in_val: U64F64 = U64F64::from_num(in_quantity) * prices[in_token_index];
let out_val: U64F64 = in_val * PARTIAL_LIQ_INCENTIVE;
let out_quantity: u64 = (out_val / prices[out_token_index]).checked_floor().unwrap().to_num();
let withdraw_instruction = spl_token::instruction::transfer(
&spl_token::ID,
vault_accs[out_token_index].key,
liqor_out_token_acc.key,
signer_acc.key,
&[],
out_quantity
)?;
let withdraw_accs = [
vault_accs[out_token_index].clone(),
liqor_out_token_acc.clone(),
signer_acc.clone(),
token_prog_acc.clone()
];
let signer_seeds = gen_signer_seeds(&mango_group.signer_nonce, mango_group_acc.key);
solana_program::program::invoke_signed(&withdraw_instruction, &withdraw_accs, &[&signer_seeds])?;
let withdraw = U64F64::from_num(out_quantity) / mango_group.indexes[out_token_index].deposit;
checked_sub_deposit(&mut mango_group, &mut liqee_margin_account, out_token_index, withdraw)?;
let coll_ratio = liqee_margin_account.get_collateral_ratio(&mango_group, &prices, open_orders_accs)?;
if coll_ratio >= mango_group.init_coll_ratio {
// set margin account to no longer being liquidated
liqee_margin_account.being_liquidated = false;
}
Ok(())
}
pub fn process(
program_id: &Pubkey,
accounts: &[AccountInfo],
@ -1387,8 +1593,8 @@ pub fn get_prices(
mango_group: &MangoGroup,
oracle_accs: &[AccountInfo]
) -> MangoResult<[U64F64; NUM_TOKENS]> {
let mut prices = [U64F64::from_num(0); NUM_TOKENS];
prices[NUM_MARKETS] = U64F64::from_num(1); // quote currency is 1
let mut prices = [ZERO_U64F64; NUM_TOKENS];
prices[NUM_MARKETS] = ONE_U64F64; // quote currency is 1
let quote_decimals: u8 = mango_group.mint_decimals[NUM_MARKETS];
for i in 0..NUM_MARKETS {
@ -1411,7 +1617,7 @@ pub fn get_prices(
Ok(prices)
}
pub fn invoke_settle_funds<'a>(
fn invoke_settle_funds<'a>(
dex_prog_acc: &AccountInfo<'a>,
spot_market_acc: &AccountInfo<'a>,
open_orders_acc: &AccountInfo<'a>,
@ -1456,7 +1662,7 @@ pub fn invoke_settle_funds<'a>(
solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds)
}
pub fn invoke_cancel_order<'a>(
fn invoke_cancel_order<'a>(
dex_prog_acc: &AccountInfo<'a>,
spot_market_acc: &AccountInfo<'a>,
bids_acc: &AccountInfo<'a>,
@ -1492,3 +1698,78 @@ pub fn invoke_cancel_order<'a>(
];
solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds)
}
fn cancel_all<'a>(
open_orders_acc: &AccountInfo<'a>,
dex_prog_acc: &AccountInfo<'a>,
spot_market_acc: &AccountInfo<'a>,
bids_acc: &AccountInfo<'a>,
asks_acc: &AccountInfo<'a>,
signer_acc: &AccountInfo<'a>,
dex_event_queue_acc: &AccountInfo<'a>,
dex_base_acc: &AccountInfo<'a>,
dex_quote_acc: &AccountInfo<'a>,
base_vault_acc: &AccountInfo<'a>,
quote_vault_acc: &AccountInfo<'a>,
dex_signer_acc: &AccountInfo<'a>,
token_prog_acc: &AccountInfo<'a>,
signers_seeds: &[&[&[u8]]],
mut limit: u32
) -> MangoResult<()> {
let open_orders = load_open_orders(open_orders_acc)?;
let mut free_slot_bits = open_orders.free_slot_bits;
limit = std::cmp::min(limit, free_slot_bits.count_zeros());
if limit == 0 {
return Ok(());
}
for j in 0..128 {
let slot_is_free = (free_slot_bits & 1u128) != 0;
free_slot_bits = free_slot_bits >> 1;
if slot_is_free {
continue
}
let oid = open_orders.orders[j];
let cancel_instruction = serum_dex::instruction::MarketInstruction::CancelOrderV2(
serum_dex::instruction::CancelOrderInstructionV2 {
side: open_orders.slot_side(j as u8).unwrap(),
order_id: oid
}
);
invoke_cancel_order(
dex_prog_acc,
spot_market_acc,
bids_acc,
asks_acc,
open_orders_acc,
signer_acc,
dex_event_queue_acc,
cancel_instruction.pack(),
signers_seeds
)?;
limit -= 1;
if limit == 0 {
break;
}
}
invoke_settle_funds(
dex_prog_acc,
spot_market_acc,
open_orders_acc,
signer_acc,
dex_base_acc,
dex_quote_acc,
base_vault_acc,
quote_vault_acc,
dex_signer_acc,
token_prog_acc,
signers_seeds
)?;
Ok(())
}

View File

@ -11,6 +11,8 @@ use solana_program::clock::Clock;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use fixed_macro::types::U64F64;
use crate::error::{AssertionError, check_assert, MangoResult, SourceFileId};
/// Initially launching with BTC/USDT, ETH/USDT
@ -20,7 +22,14 @@ pub const MANGO_GROUP_PADDING: usize = 8 - (NUM_TOKENS + NUM_MARKETS) % 8;
pub const MINUTE: u64 = 60;
pub const HOUR: u64 = 3600;
pub const DAY: u64 = 86400;
pub const YEAR: u64 = 31536000;
pub const YEAR: U64F64 = U64F64!(31536000);
// pub const YEAR: u64 = 31536000;
const OPTIMAL_UTIL: U64F64 = U64F64!(0.7);
const OPTIMAL_R: U64F64 = U64F64!(3.17097919837645865e-09); // 10% APY -> 0.1 / YEAR
const MAX_R: U64F64 = U64F64!(3.17097919837645865e-08); // max 100% APY -> 1 / YEAR
pub const ONE_U64F64: U64F64 = U64F64!(1);
pub const ZERO_U64F64: U64F64 = U64F64!(0);
pub const PARTIAL_LIQ_INCENTIVE: U64F64 = U64F64!(1.05);
macro_rules! prog_assert {
($cond:expr) => {
@ -124,6 +133,8 @@ pub struct MangoGroup {
}
impl_loadable!(MangoGroup);
impl MangoGroup {
pub fn load_mut_checked<'a>(
account: &'a AccountInfo,
@ -158,27 +169,23 @@ impl MangoGroup {
}
/// interest is in units per second (e.g. 0.01 => 1% interest per second)
pub fn get_interest_rate(&self, token_index: usize) -> U64F64 {
let optimal_util = U64F64::from_num(0.7);
let optimal_r = U64F64::from_num(0.10) / U64F64::from_num(YEAR); // opt 10%
let max_r = U64F64::from_num(1) / U64F64::from_num(YEAR); // max 100%
let index: &MangoIndex = &self.indexes[token_index];
let native_deposits = index.deposit * self.total_deposits[token_index];
let native_borrows = index.borrow * self.total_borrows[token_index];
if native_deposits <= native_borrows { // if deps == 0, this is always true
return max_r; // kind of an error state
return MAX_R; // kind of an error state
}
let utilization = native_borrows / native_deposits;
if utilization > optimal_util {
let extra_util = utilization - optimal_util;
let slope = (max_r - optimal_r) / (U64F64::from_num(1) - optimal_util);
optimal_r + slope * extra_util
if utilization > OPTIMAL_UTIL {
let extra_util = utilization - OPTIMAL_UTIL;
let slope = (MAX_R - OPTIMAL_R) / (ONE_U64F64 - OPTIMAL_UTIL);
OPTIMAL_R + slope * extra_util
} else {
let slope = optimal_r / optimal_util;
let slope = OPTIMAL_R / OPTIMAL_UTIL;
slope * utilization
}
}
pub fn update_indexes(&mut self, clock: &Clock) -> MangoResult<()> {
// TODO verify what happens if total_deposits < total_borrows
// TODO verify what happens if total_deposits == 0 && total_borrows > 0
@ -190,7 +197,7 @@ impl MangoGroup {
for i in 0..NUM_TOKENS {
let interest_rate = self.get_interest_rate(i);
let index: &mut MangoIndex = &mut self.indexes[i];
if index.last_update == curr_ts || self.total_deposits[i] == U64F64::from_num(0) {
if index.last_update == curr_ts || self.total_deposits[i] == ZERO_U64F64 {
// TODO is skipping when total_deposits == 0 correct move?
continue;
}
@ -262,7 +269,8 @@ pub struct MarginAccount {
pub open_orders: [Pubkey; NUM_MARKETS], // owned by Mango
pub padding: [u8; 8] // padding to make compatible with previous MarginAccount size
pub being_liquidated: bool,
pub padding: [u8; 7] // padding to make compatible with previous MarginAccount size
// TODO add has_borrows field for easy memcmp fetching
}
impl_loadable!(MarginAccount);
@ -309,7 +317,7 @@ impl MarginAccount {
let assets = self.get_assets_val(mango_group, prices, open_orders_accs)?;
let liabs = self.get_liabs_val(mango_group, prices)?;
if liabs > assets {
Ok(U64F64::from_num(0))
Ok(ZERO_U64F64)
} else {
Ok(assets - liabs)
}
@ -324,7 +332,7 @@ impl MarginAccount {
// assets / liabs
let assets = self.get_assets_val(mango_group, prices, open_orders_accs)?;
let liabs = self.get_liabs_val(mango_group, prices)?;
if liabs == U64F64::from_num(0) {
if liabs == ZERO_U64F64 {
Ok(U64F64::MAX)
} else {
Ok(assets / liabs)
@ -372,7 +380,7 @@ impl MarginAccount {
) -> MangoResult<U64F64> {
// TODO weight collateral differently
// equity = val(deposits) + val(positions) + val(open_orders) - val(borrows)
let mut assets: U64F64 = U64F64::from_num(0);
let mut assets: U64F64 = ZERO_U64F64;
for i in 0..NUM_MARKETS { // Add up all the value in open orders
// TODO check open orders details
if *open_orders_accs[i].key == Pubkey::default() {
@ -401,7 +409,7 @@ impl MarginAccount {
mango_group: &MangoGroup,
prices: &[U64F64; NUM_TOKENS],
) -> MangoResult<U64F64> {
let mut liabs: U64F64 = U64F64::from_num(0);
let mut liabs: U64F64 = ZERO_U64F64;
for i in 0..NUM_TOKENS {
let index: &MangoIndex = &mango_group.indexes[i];
let native_borrows = index.borrow * self.borrows[i];
@ -419,12 +427,30 @@ impl MarginAccount {
let assets = self.get_assets_val(mango_group, prices, open_orders_accs)?;
let liabs = self.get_liabs_val(mango_group, prices)?;
if liabs == U64F64::from_num(0) || assets >= liabs * mango_group.init_coll_ratio {
if liabs == ZERO_U64F64 || assets >= liabs * mango_group.init_coll_ratio {
Ok(0)
} else {
Ok((liabs * mango_group.init_coll_ratio - assets).to_num())
}
}
pub fn get_partial_liq_deficit(
&self,
mango_group: &MangoGroup,
prices: &[U64F64; NUM_TOKENS],
open_orders_accs: &[AccountInfo; NUM_MARKETS]
) -> MangoResult<U64F64> {
let assets = self.get_assets_val(mango_group, prices, open_orders_accs)?;
let liabs = self.get_liabs_val(mango_group, prices)?;
if liabs == ZERO_U64F64 || assets >= liabs * mango_group.init_coll_ratio {
Ok(ZERO_U64F64)
} else {
// TODO make this checked
Ok((liabs * mango_group.init_coll_ratio - assets) / (mango_group.init_coll_ratio - PARTIAL_LIQ_INCENTIVE))
}
}
pub fn get_native_borrow(&self, index: &MangoIndex, token_i: usize) -> u64 {
(self.borrows[token_i] * index.borrow).to_num()
}