Use the overflow-checks=true equivalent with the fixed crate (#476)
* Vendor `fixed` crate to have checked math in release mode * remove all cm!() * drop superfluous parens * drop use of checked_math crate * manual removal of redundant checked_* functions
This commit is contained in:
parent
330739364f
commit
5c7a2e3e10
|
@ -5,3 +5,6 @@
|
|||
[submodule "switchboard-v2"]
|
||||
path = 3rdparty/switchboard-v2
|
||||
url = https://github.com/blockworks-foundation/sbv2-solana.git
|
||||
[submodule "3rdparty/fixed"]
|
||||
path = 3rdparty/fixed
|
||||
url = https://gitlab.com/ckamm/fixed.git
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit d0f6e6b64e9a434ed0a529538c6fd056b79507df
|
|
@ -325,6 +325,12 @@ version = "1.0.66"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e90af4de65aa7b293ef2d09daff88501eb254f58edde2e1ac02c82d873eadad"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.5.1"
|
||||
|
@ -1830,53 +1836,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fixed"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80a9a8cb2e34880a498f09367089339bda5e12d6f871640f947850f7113058c0"
|
||||
version = "1.23.0"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"az",
|
||||
"borsh",
|
||||
"bytemuck",
|
||||
"half",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed-macro"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0c48af8cb14e02868f449f8a2187bd78af7a08da201fdc78d518ecb1675bc"
|
||||
dependencies = [
|
||||
"fixed",
|
||||
"fixed-macro-impl",
|
||||
"fixed-macro-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed-macro-impl"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c93086f471c0a1b9c5e300ea92f5cd990ac6d3f8edf27616ef624b8fa6402d4b"
|
||||
dependencies = [
|
||||
"fixed",
|
||||
"paste",
|
||||
"proc-macro-error",
|
||||
"proc-macro2 1.0.47",
|
||||
"quote 1.0.21",
|
||||
"syn 1.0.105",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed-macro-types"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "044a61b034a2264a7f65aa0c3cd112a01b4d4ee58baace51fead3f21b993c7e4"
|
||||
dependencies = [
|
||||
"fixed",
|
||||
"fixed-macro-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.4.2"
|
||||
|
@ -2193,9 +2164,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "1.8.2"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
||||
checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hash32"
|
||||
|
@ -3003,12 +2977,10 @@ dependencies = [
|
|||
"bincode",
|
||||
"borsh",
|
||||
"bytemuck",
|
||||
"checked_math",
|
||||
"default-env",
|
||||
"derivative",
|
||||
"env_logger 0.9.3",
|
||||
"fixed",
|
||||
"fixed-macro",
|
||||
"itertools 0.10.5",
|
||||
"lazy_static",
|
||||
"log 0.4.17",
|
||||
|
@ -3043,7 +3015,6 @@ dependencies = [
|
|||
"dotenv",
|
||||
"env_logger 0.8.4",
|
||||
"fixed",
|
||||
"fixed-macro",
|
||||
"futures 0.3.25",
|
||||
"log 0.4.17",
|
||||
"mango-v4",
|
||||
|
@ -3069,7 +3040,6 @@ dependencies = [
|
|||
"base64 0.13.1",
|
||||
"bincode",
|
||||
"fixed",
|
||||
"fixed-macro",
|
||||
"futures 0.3.25",
|
||||
"itertools 0.10.5",
|
||||
"jsonrpc-core 18.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -3105,7 +3075,6 @@ dependencies = [
|
|||
"dotenv",
|
||||
"env_logger 0.8.4",
|
||||
"fixed",
|
||||
"fixed-macro",
|
||||
"futures 0.3.25",
|
||||
"itertools 0.10.5",
|
||||
"log 0.4.17",
|
||||
|
@ -3132,7 +3101,6 @@ dependencies = [
|
|||
"bs58 0.3.1",
|
||||
"bytemuck",
|
||||
"bytes 1.3.0",
|
||||
"checked_math",
|
||||
"clap 3.2.23",
|
||||
"dotenv",
|
||||
"fixed",
|
||||
|
@ -3179,7 +3147,6 @@ dependencies = [
|
|||
"bs58 0.3.1",
|
||||
"bytemuck",
|
||||
"bytes 1.3.0",
|
||||
"checked_math",
|
||||
"clap 3.2.23",
|
||||
"dotenv",
|
||||
"fixed",
|
||||
|
@ -3872,12 +3839,6 @@ dependencies = [
|
|||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1"
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.2.1"
|
||||
|
|
|
@ -15,8 +15,7 @@ anyhow = "1.0"
|
|||
clap = { version = "3.1.8", features = ["derive", "env"] }
|
||||
dotenv = "0.15.0"
|
||||
env_logger = "0.8.4"
|
||||
fixed = { version = "=1.11.0", features = ["serde", "borsh"] }
|
||||
fixed-macro = "^1.1.1"
|
||||
fixed = { path = "../../3rdparty/fixed", version = "=1.23.0", features = ["serde", "borsh"] }
|
||||
futures = "0.3.21"
|
||||
log = "0.4.0"
|
||||
mango-v4 = { path = "../../programs/mango-v4", features = ["client"] }
|
||||
|
|
|
@ -15,8 +15,7 @@ anyhow = "1.0"
|
|||
clap = { version = "3.1.8", features = ["derive", "env"] }
|
||||
dotenv = "0.15.0"
|
||||
env_logger = "0.8.4"
|
||||
fixed = { version = "=1.11.0", features = ["serde", "borsh"] }
|
||||
fixed-macro = "^1.1.1"
|
||||
fixed = { path = "../../3rdparty/fixed", version = "=1.23.0", features = ["serde", "borsh"] }
|
||||
futures = "0.3.21"
|
||||
itertools = "0.10.3"
|
||||
log = "0.4.0"
|
||||
|
|
|
@ -18,10 +18,9 @@ async-trait = "0.1"
|
|||
bs58 = "0.3.1"
|
||||
bytemuck = "^1.7.2"
|
||||
bytes = "1.0"
|
||||
checked_math = { path = "../../lib/checked_math" }
|
||||
clap = { version = "3.1.8", features = ["derive", "env"] }
|
||||
dotenv = "0.15.0"
|
||||
fixed = { version = "=1.11.0", features = ["serde"] }
|
||||
fixed = { path = "../../3rdparty/fixed", version = "=1.23.0", features = ["serde"] }
|
||||
futures = "0.3.17"
|
||||
futures-core = "0.3"
|
||||
futures-util = "0.3"
|
||||
|
|
|
@ -19,10 +19,9 @@ bincode = "1.3.3"
|
|||
bs58 = "0.3.1"
|
||||
bytemuck = "^1.7.2"
|
||||
bytes = "1.0"
|
||||
checked_math = { path = "../../lib/checked_math" }
|
||||
clap = { version = "3.1.8", features = ["derive", "env"] }
|
||||
dotenv = "0.15.0"
|
||||
fixed = { version = "=1.11.0", features = ["serde"] }
|
||||
fixed = { path = "../../3rdparty/fixed", version = "=1.23.0", features = ["serde"] }
|
||||
futures = "0.3.17"
|
||||
futures-core = "0.3"
|
||||
futures-util = "0.3"
|
||||
|
|
|
@ -14,8 +14,7 @@ anyhow = "1.0"
|
|||
async-channel = "1.6"
|
||||
async-once-cell = { version = "0.4.2", features = ["unpin"] }
|
||||
async-trait = "0.1.52"
|
||||
fixed = { version = "=1.11.0", features = ["serde", "borsh"] }
|
||||
fixed-macro = "^1.1.1"
|
||||
fixed = { path = "../../3rdparty/fixed", version = "=1.23.0", features = ["serde", "borsh"] }
|
||||
futures = "0.3.25"
|
||||
itertools = "0.10.3"
|
||||
jsonrpc-core = "18.0.0"
|
||||
|
|
|
@ -29,11 +29,9 @@ arrayref = "0.3.6"
|
|||
bincode = "1.3.3"
|
||||
borsh = { version = "0.9.3", features = ["const-generics"] }
|
||||
bytemuck = { version = "^1.7.2", features = ["min_const_generics"] }
|
||||
checked_math = { path = "../../lib/checked_math" }
|
||||
default-env = "0.1.1"
|
||||
derivative = "2.2.0"
|
||||
fixed = { version = "=1.11.0", features = ["serde", "borsh"] } # todo: higher versions don't work
|
||||
fixed-macro = "^1.1.1"
|
||||
fixed = { path = "../../3rdparty/fixed", version = "=1.23.0", features = ["serde", "borsh", "disable-cache", "debug-assert-in-release"] }
|
||||
num_enum = "0.5.1"
|
||||
pyth-sdk-solana = "0.1.0"
|
||||
serde = "^1.0"
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token::{Mint, Token, TokenAccount};
|
||||
use fixed::types::I80F48;
|
||||
use fixed_macro::types::I80F48;
|
||||
|
||||
use crate::error::*;
|
||||
use crate::state::*;
|
||||
|
||||
pub const INDEX_START: I80F48 = I80F48!(1_000_000);
|
||||
pub const INDEX_START: I80F48 = I80F48::lit("1_000_000");
|
||||
|
||||
const FIRST_BANK_NUM: u32 = 0;
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ use crate::accounts_zerocopy::*;
|
|||
use crate::error::*;
|
||||
use crate::serum3_cpi;
|
||||
use crate::state::{Bank, MangoAccountRef, PerpMarket, PerpMarketIndex, TokenIndex};
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
/// This trait abstracts how to find accounts needed for the health computation.
|
||||
///
|
||||
|
@ -64,17 +63,17 @@ pub fn new_fixed_order_account_retriever<'a, 'info>(
|
|||
let active_token_len = account.active_token_positions().count();
|
||||
let active_serum3_len = account.active_serum3_orders().count();
|
||||
let active_perp_len = account.active_perp_positions().count();
|
||||
let expected_ais = cm!(active_token_len * 2 // banks + oracles
|
||||
let expected_ais = active_token_len * 2 // banks + oracles
|
||||
+ active_perp_len * 2 // PerpMarkets + Oracles
|
||||
+ active_serum3_len); // open_orders
|
||||
+ active_serum3_len; // open_orders
|
||||
require_eq!(ais.len(), expected_ais);
|
||||
|
||||
Ok(FixedOrderAccountRetriever {
|
||||
ais: AccountInfoRef::borrow_slice(ais)?,
|
||||
n_banks: active_token_len,
|
||||
n_perps: active_perp_len,
|
||||
begin_perp: cm!(active_token_len * 2),
|
||||
begin_serum3: cm!(active_token_len * 2 + active_perp_len * 2),
|
||||
begin_perp: active_token_len * 2,
|
||||
begin_serum3: active_token_len * 2 + active_perp_len * 2,
|
||||
staleness_slot: Some(Clock::get()?.slot),
|
||||
})
|
||||
}
|
||||
|
@ -130,7 +129,7 @@ impl<T: KeyedAccountReader> AccountRetriever for FixedOrderAccountRetriever<T> {
|
|||
)
|
||||
})?;
|
||||
|
||||
let oracle_index = cm!(self.n_banks + active_token_position_index);
|
||||
let oracle_index = self.n_banks + active_token_position_index;
|
||||
let oracle_price = self.oracle_price_bank(oracle_index, bank).with_context(|| {
|
||||
format!(
|
||||
"getting oracle for bank with health account index {} and token index {}, passed account {}",
|
||||
|
@ -149,7 +148,7 @@ impl<T: KeyedAccountReader> AccountRetriever for FixedOrderAccountRetriever<T> {
|
|||
active_perp_position_index: usize,
|
||||
perp_market_index: PerpMarketIndex,
|
||||
) -> Result<(&PerpMarket, I80F48)> {
|
||||
let perp_index = cm!(self.begin_perp + active_perp_position_index);
|
||||
let perp_index = self.begin_perp + active_perp_position_index;
|
||||
let perp_market = self
|
||||
.perp_market(group, perp_index, perp_market_index)
|
||||
.with_context(|| {
|
||||
|
@ -161,7 +160,7 @@ impl<T: KeyedAccountReader> AccountRetriever for FixedOrderAccountRetriever<T> {
|
|||
)
|
||||
})?;
|
||||
|
||||
let oracle_index = cm!(perp_index + self.n_perps);
|
||||
let oracle_index = perp_index + self.n_perps;
|
||||
let oracle_price = self.oracle_price_perp(oracle_index, perp_market).with_context(|| {
|
||||
format!(
|
||||
"getting oracle for perp market with health account index {} and perp market index {}, passed account {}",
|
||||
|
@ -174,7 +173,7 @@ impl<T: KeyedAccountReader> AccountRetriever for FixedOrderAccountRetriever<T> {
|
|||
}
|
||||
|
||||
fn serum_oo(&self, active_serum_oo_index: usize, key: &Pubkey) -> Result<&OpenOrders> {
|
||||
let serum_oo_index = cm!(self.begin_serum3 + active_serum_oo_index);
|
||||
let serum_oo_index = self.begin_serum3 + active_serum_oo_index;
|
||||
let ai = &self.ais[serum_oo_index];
|
||||
(|| {
|
||||
require_keys_eq!(*key, *ai.key());
|
||||
|
|
|
@ -6,7 +6,6 @@ use crate::error::*;
|
|||
use crate::state::{
|
||||
Bank, MangoAccountRef, PerpMarket, PerpMarketIndex, PerpPosition, Serum3MarketIndex, TokenIndex,
|
||||
};
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -132,7 +131,7 @@ impl TokenInfo {
|
|||
self.prices.asset(health_type),
|
||||
)
|
||||
};
|
||||
cm!(self.balance_native * price * weight)
|
||||
self.balance_native * price * weight
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,7 +174,7 @@ impl Serum3Info {
|
|||
|token_info: &TokenInfo, token_max_reserved: I80F48, market_reserved: I80F48| {
|
||||
// This balance includes all possible reserved funds from markets that relate to the
|
||||
// token, including this market itself: `market_reserved` is already included in `token_max_reserved`.
|
||||
let max_balance = cm!(token_info.balance_native + token_max_reserved);
|
||||
let max_balance = token_info.balance_native + token_max_reserved;
|
||||
|
||||
// For simplicity, we assume that `market_reserved` was added to `max_balance` last
|
||||
// (it underestimates health because that gives the smallest effects): how much did
|
||||
|
@ -185,14 +184,14 @@ impl Serum3Info {
|
|||
} else if max_balance.is_negative() {
|
||||
(I80F48::ZERO, market_reserved)
|
||||
} else {
|
||||
(max_balance, cm!(market_reserved - max_balance))
|
||||
(max_balance, market_reserved - max_balance)
|
||||
};
|
||||
|
||||
let asset_weight = token_info.asset_weight(health_type);
|
||||
let liab_weight = token_info.liab_weight(health_type);
|
||||
let asset_price = token_info.prices.asset(health_type);
|
||||
let liab_price = token_info.prices.liab(health_type);
|
||||
cm!(asset_part * asset_weight * asset_price + liab_part * liab_weight * liab_price)
|
||||
asset_part * asset_weight * asset_price + liab_part * liab_weight * liab_price
|
||||
};
|
||||
|
||||
let health_base = compute_health_effect(
|
||||
|
@ -239,14 +238,11 @@ pub struct PerpInfo {
|
|||
|
||||
impl PerpInfo {
|
||||
fn new(perp_position: &PerpPosition, perp_market: &PerpMarket, prices: Prices) -> Result<Self> {
|
||||
let base_lots = cm!(perp_position.base_position_lots() + perp_position.taker_base_lots);
|
||||
let base_lots = perp_position.base_position_lots() + perp_position.taker_base_lots;
|
||||
|
||||
let unsettled_funding = perp_position.unsettled_funding(perp_market);
|
||||
let taker_quote = I80F48::from(cm!(
|
||||
perp_position.taker_quote_lots * perp_market.quote_lot_size
|
||||
));
|
||||
let quote_current =
|
||||
cm!(perp_position.quote_position_native() - unsettled_funding + taker_quote);
|
||||
let taker_quote = I80F48::from(perp_position.taker_quote_lots * perp_market.quote_lot_size);
|
||||
let quote_current = perp_position.quote_position_native() - unsettled_funding + taker_quote;
|
||||
|
||||
Ok(Self {
|
||||
perp_market_index: perp_market.perp_market_index,
|
||||
|
@ -299,7 +295,7 @@ impl PerpInfo {
|
|||
HealthType::Maint => self.maint_overall_asset_weight,
|
||||
};
|
||||
|
||||
cm!(asset_weight * unweighted)
|
||||
asset_weight * unweighted
|
||||
} else {
|
||||
unweighted
|
||||
}
|
||||
|
@ -309,7 +305,7 @@ impl PerpInfo {
|
|||
pub fn unweighted_health_contribution(&self, health_type: HealthType) -> I80F48 {
|
||||
let order_execution_case = |orders_base_lots: i64, order_price: I80F48| {
|
||||
let net_base_native =
|
||||
I80F48::from(cm!((self.base_lots + orders_base_lots) * self.base_lot_size));
|
||||
I80F48::from((self.base_lots + orders_base_lots) * self.base_lot_size);
|
||||
let weight = match (health_type, net_base_native.is_negative()) {
|
||||
(HealthType::Init, true) | (HealthType::LiquidationEnd, true) => {
|
||||
self.init_base_liab_weight
|
||||
|
@ -327,13 +323,13 @@ impl PerpInfo {
|
|||
};
|
||||
|
||||
// Total value of the order-execution adjusted base position
|
||||
let base_health = cm!(net_base_native * weight * base_price);
|
||||
let base_health = net_base_native * weight * base_price;
|
||||
|
||||
let orders_base_native = I80F48::from(cm!(orders_base_lots * self.base_lot_size));
|
||||
let orders_base_native = I80F48::from(orders_base_lots * self.base_lot_size);
|
||||
// The quote change from executing the bids/asks
|
||||
let order_quote = cm!(-orders_base_native * order_price);
|
||||
let order_quote = -orders_base_native * order_price;
|
||||
|
||||
cm!(base_health + order_quote)
|
||||
base_health + order_quote
|
||||
};
|
||||
|
||||
// What is worse: Executing all bids at oracle_price.liab, or executing all asks at oracle_price.asset?
|
||||
|
@ -341,7 +337,7 @@ impl PerpInfo {
|
|||
let asks_case = order_execution_case(-self.asks_base_lots, self.prices.asset(health_type));
|
||||
let worst_case = bids_case.min(asks_case);
|
||||
|
||||
cm!(self.quote + worst_case)
|
||||
self.quote + worst_case
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -357,7 +353,7 @@ impl HealthCache {
|
|||
pub fn health(&self, health_type: HealthType) -> I80F48 {
|
||||
let mut health = I80F48::ZERO;
|
||||
let sum = |contrib| {
|
||||
cm!(health += contrib);
|
||||
health += contrib;
|
||||
};
|
||||
self.health_sum(health_type, sum);
|
||||
health
|
||||
|
@ -370,9 +366,9 @@ impl HealthCache {
|
|||
let mut liabs = I80F48::ZERO;
|
||||
let sum = |contrib| {
|
||||
if contrib > 0 {
|
||||
cm!(assets += contrib);
|
||||
assets += contrib;
|
||||
} else {
|
||||
cm!(liabs -= contrib);
|
||||
liabs -= contrib;
|
||||
}
|
||||
};
|
||||
self.health_sum(health_type, sum);
|
||||
|
@ -429,7 +425,7 @@ impl HealthCache {
|
|||
// We need to make sure that if balance is before * price, then change = -before
|
||||
// brings it to exactly zero.
|
||||
let removed_contribution = -change;
|
||||
cm!(entry.balance_native -= removed_contribution);
|
||||
entry.balance_native -= removed_contribution;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -454,11 +450,11 @@ impl HealthCache {
|
|||
// Apply it to the tokens
|
||||
{
|
||||
let base_entry = &mut self.token_infos[base_entry_index];
|
||||
cm!(base_entry.balance_native += free_base_change);
|
||||
base_entry.balance_native += free_base_change;
|
||||
}
|
||||
{
|
||||
let quote_entry = &mut self.token_infos[quote_entry_index];
|
||||
cm!(quote_entry.balance_native += free_quote_change);
|
||||
quote_entry.balance_native += free_quote_change;
|
||||
}
|
||||
|
||||
// Apply it to the serum3 info
|
||||
|
@ -467,8 +463,8 @@ impl HealthCache {
|
|||
.iter_mut()
|
||||
.find(|m| m.market_index == market_index)
|
||||
.ok_or_else(|| error_msg!("serum3 market {} not found", market_index))?;
|
||||
cm!(market_entry.reserved_base += reserved_base_change);
|
||||
cm!(market_entry.reserved_quote += reserved_quote_change);
|
||||
market_entry.reserved_base += reserved_base_change;
|
||||
market_entry.reserved_quote += reserved_quote_change;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -613,21 +609,17 @@ impl HealthCache {
|
|||
let quote_asset = quote.prices.asset(health_type);
|
||||
let base_liab = base.prices.liab(health_type);
|
||||
// OPTIMIZATION: These divisions can be extremely expensive (up to 5k CU each)
|
||||
let all_reserved_as_base =
|
||||
cm!(reserved_base + reserved_quote * quote_asset / base_liab);
|
||||
let all_reserved_as_base = reserved_base + reserved_quote * quote_asset / base_liab;
|
||||
|
||||
let base_asset = base.prices.asset(health_type);
|
||||
let quote_liab = quote.prices.liab(health_type);
|
||||
let all_reserved_as_quote =
|
||||
cm!(reserved_quote + reserved_base * base_asset / quote_liab);
|
||||
let all_reserved_as_quote = reserved_quote + reserved_base * base_asset / quote_liab;
|
||||
|
||||
let base_max_reserved = &mut token_max_reserved[info.base_index];
|
||||
// note: cm!() does not work with mutable references
|
||||
*base_max_reserved = base_max_reserved.checked_add(all_reserved_as_base).unwrap();
|
||||
// note: () does not work with mutable references
|
||||
*base_max_reserved += all_reserved_as_base;
|
||||
let quote_max_reserved = &mut token_max_reserved[info.quote_index];
|
||||
*quote_max_reserved = quote_max_reserved
|
||||
.checked_add(all_reserved_as_quote)
|
||||
.unwrap();
|
||||
*quote_max_reserved += all_reserved_as_quote;
|
||||
|
||||
serum3_reserved.push(Serum3Reserved {
|
||||
all_reserved_as_base,
|
||||
|
@ -676,7 +668,7 @@ impl HealthCache {
|
|||
let mut health = I80F48::ZERO;
|
||||
for token_info in self.token_infos.iter() {
|
||||
let contrib = token_info.health_contribution(health_type);
|
||||
cm!(health += contrib);
|
||||
health += contrib;
|
||||
}
|
||||
|
||||
let (token_max_reserved, serum3_reserved) = self.compute_serum3_reservations(health_type);
|
||||
|
@ -687,12 +679,12 @@ impl HealthCache {
|
|||
&token_max_reserved,
|
||||
reserved,
|
||||
);
|
||||
cm!(health += contrib);
|
||||
health += contrib;
|
||||
}
|
||||
|
||||
for perp_info in self.perp_infos.iter() {
|
||||
let positive_contrib = perp_info.health_contribution(health_type).max(I80F48::ZERO);
|
||||
cm!(health += positive_contrib);
|
||||
health += positive_contrib;
|
||||
}
|
||||
health
|
||||
}
|
||||
|
@ -755,15 +747,15 @@ pub fn new_health_cache(
|
|||
|
||||
// add the amounts that are freely settleable immediately to token balances
|
||||
let base_free = I80F48::from(oo.native_coin_free);
|
||||
let quote_free = I80F48::from(cm!(oo.native_pc_free + oo.referrer_rebates_accrued));
|
||||
let quote_free = I80F48::from(oo.native_pc_free + oo.referrer_rebates_accrued);
|
||||
let base_info = &mut token_infos[base_index];
|
||||
cm!(base_info.balance_native += base_free);
|
||||
base_info.balance_native += base_free;
|
||||
let quote_info = &mut token_infos[quote_index];
|
||||
cm!(quote_info.balance_native += quote_free);
|
||||
quote_info.balance_native += quote_free;
|
||||
|
||||
// track the reserved amounts
|
||||
let reserved_base = I80F48::from(cm!(oo.native_coin_total - oo.native_coin_free));
|
||||
let reserved_quote = I80F48::from(cm!(oo.native_pc_total - oo.native_pc_free));
|
||||
let reserved_base = I80F48::from(oo.native_coin_total - oo.native_coin_free);
|
||||
let reserved_quote = I80F48::from(oo.native_pc_total - oo.native_pc_free);
|
||||
|
||||
serum3_infos.push(Serum3Info {
|
||||
reserved_base,
|
||||
|
@ -809,7 +801,6 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::state::*;
|
||||
use serum_dex::state::OpenOrders;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_precision() {
|
||||
|
|
|
@ -3,12 +3,10 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
use fixed::types::I80F48;
|
||||
use fixed_macro::types::I80F48;
|
||||
|
||||
use crate::error::*;
|
||||
use crate::state::Side as PerpOrderSide;
|
||||
use crate::state::{Bank, MangoAccountValue, PerpMarketIndex};
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -33,7 +31,7 @@ impl HealthCache {
|
|||
let hundred = I80F48::from(100);
|
||||
if liabs > 0 {
|
||||
// feel free to saturate to MAX for tiny liabs
|
||||
cm!(hundred * (assets - liabs)).saturating_div(liabs)
|
||||
(hundred * (assets - liabs)).saturating_div(liabs)
|
||||
} else {
|
||||
I80F48::MAX
|
||||
}
|
||||
|
@ -58,7 +56,7 @@ impl HealthCache {
|
|||
let mut source_position = account.token_position(source_bank.token_index)?.clone();
|
||||
let mut target_position = account.token_position(target_bank.token_index)?.clone();
|
||||
|
||||
let target_amount = cm!(amount * price);
|
||||
let target_amount = amount * price;
|
||||
|
||||
let mut source_bank = source_bank.clone();
|
||||
source_bank.withdraw_with_fee(&mut source_position, amount, now_ts, source_oracle_price)?;
|
||||
|
@ -477,7 +475,7 @@ fn binary_search(
|
|||
fun: impl Fn(I80F48) -> Result<I80F48>,
|
||||
) -> Result<I80F48> {
|
||||
let max_iterations = 50;
|
||||
let target_error = I80F48!(0.1);
|
||||
let target_error = I80F48::lit("0.1");
|
||||
let right_value = fun(right)?;
|
||||
require_msg!(
|
||||
(left_value <= target_value && right_value >= target_value)
|
||||
|
|
|
@ -2,7 +2,6 @@ use anchor_lang::prelude::*;
|
|||
|
||||
use crate::accounts_ix::*;
|
||||
use crate::state::*;
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
pub fn account_expand(
|
||||
ctx: Context<AccountExpand>,
|
||||
|
@ -29,7 +28,7 @@ pub fn account_expand(
|
|||
to: realloc_account.clone(),
|
||||
},
|
||||
),
|
||||
cm!(new_rent_minimum - old_lamports),
|
||||
new_rent_minimum - old_lamports,
|
||||
)?;
|
||||
|
||||
// realloc: it's safe to not re-zero-init since we never shrink accounts
|
||||
|
|
|
@ -185,23 +185,23 @@ pub fn benchmark(_ctx: Context<Benchmark>) -> Result<()> {
|
|||
let max = I80F48::MAX;
|
||||
sol_log_compute_units(); // 92610
|
||||
|
||||
let _ = checked_math!(half + half); // 0
|
||||
let _ = half + half; // 0
|
||||
sol_log_compute_units(); // 92509
|
||||
|
||||
let _ = checked_math!(max - max); // 0
|
||||
let _ = max - max; // 0
|
||||
sol_log_compute_units(); // 92408
|
||||
|
||||
let _ = checked_math!(large_number * large_number); // 77
|
||||
let _ = large_number * large_number; // 77
|
||||
sol_log_compute_units(); // 92230
|
||||
|
||||
// /
|
||||
let _ = checked_math!(I80F48::ZERO / max); // 839
|
||||
let _ = I80F48::ZERO / max; // 839
|
||||
sol_log_compute_units(); // 91290
|
||||
|
||||
let _ = checked_math!(half / max); // 3438
|
||||
let _ = half / max; // 3438
|
||||
sol_log_compute_units(); // 87751
|
||||
|
||||
let _ = checked_math!(max / max); // 3457
|
||||
let _ = max / max; // 3457
|
||||
sol_log_compute_units(); // 84193
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::group_seeds;
|
|||
use crate::health::{new_fixed_order_account_retriever, new_health_cache, AccountRetriever};
|
||||
use crate::logs::{FlashLoanLog, FlashLoanTokenDetail, TokenBalanceLog};
|
||||
use crate::state::*;
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_lang::solana_program::sysvar::instructions as tx_instructions;
|
||||
use anchor_lang::Discriminator;
|
||||
|
@ -271,7 +271,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
token::transfer(transfer_ctx, repay)?;
|
||||
|
||||
let repay = I80F48::from(repay);
|
||||
cm!(change += repay);
|
||||
change += repay;
|
||||
}
|
||||
|
||||
changes.push(TokenVaultChange {
|
||||
|
@ -333,16 +333,16 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
let approved_amount = I80F48::from(bank.flash_loan_approved_amount);
|
||||
|
||||
let loan = if native.is_positive() {
|
||||
cm!(approved_amount - native).max(I80F48::ZERO)
|
||||
(approved_amount - native).max(I80F48::ZERO)
|
||||
} else {
|
||||
approved_amount
|
||||
};
|
||||
|
||||
let loan_origination_fee = cm!(loan * bank.loan_origination_fee_rate);
|
||||
cm!(bank.collected_fees_native += loan_origination_fee);
|
||||
let loan_origination_fee = loan * bank.loan_origination_fee_rate;
|
||||
bank.collected_fees_native += loan_origination_fee;
|
||||
|
||||
let change_amount = cm!(change.amount - loan_origination_fee);
|
||||
let native_after_change = cm!(native + change_amount);
|
||||
let change_amount = change.amount - loan_origination_fee;
|
||||
let native_after_change = native + change_amount;
|
||||
if bank.is_reduce_only() {
|
||||
require!(
|
||||
(change_amount < 0 && native_after_change >= 0)
|
||||
|
@ -362,7 +362,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
|
||||
let is_active = bank.change_without_fee(
|
||||
position,
|
||||
cm!(change.amount - loan_origination_fee),
|
||||
change.amount - loan_origination_fee,
|
||||
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||
*oracle_price,
|
||||
)?;
|
||||
|
|
|
@ -59,8 +59,7 @@ pub fn health_region_begin<'key, 'accounts, 'remaining, 'info>(
|
|||
// Compute pre-health and store it on the account
|
||||
let health_cache = new_health_cache(&account.borrow(), &account_retriever)?;
|
||||
let pre_init_health = account.check_health_pre(&health_cache)?;
|
||||
account.fixed.health_region_begin_init_health =
|
||||
pre_init_health.ceil().checked_to_num().unwrap();
|
||||
account.fixed.health_region_begin_init_health = pre_init_health.ceil().to_num();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use checked_math as cm;
|
||||
|
||||
use fixed::types::I80F48;
|
||||
|
||||
use crate::accounts_zerocopy::*;
|
||||
|
@ -217,7 +217,7 @@ pub(crate) fn liquidation_action(
|
|||
let perp_info = liqee_health_cache.perp_info(perp_market_index)?;
|
||||
let oracle_price = perp_info.prices.oracle;
|
||||
let base_lot_size = I80F48::from(perp_market.base_lot_size);
|
||||
let oracle_price_per_lot = cm!(base_lot_size * oracle_price);
|
||||
let oracle_price_per_lot = base_lot_size * oracle_price;
|
||||
|
||||
let liqee_positive_settle_limit = liqee_perp_position.settle_limit(&perp_market).1;
|
||||
|
||||
|
@ -245,12 +245,11 @@ pub(crate) fn liquidation_action(
|
|||
// and increased by `base * price * (1 - liq_fee) * quote_init_asset_weight`
|
||||
let quote_init_asset_weight = I80F48::ONE;
|
||||
direction = -1;
|
||||
fee_factor = cm!(I80F48::ONE - perp_market.base_liquidation_fee);
|
||||
fee_factor = I80F48::ONE - perp_market.base_liquidation_fee;
|
||||
let asset_price = perp_info.prices.asset(HealthType::LiquidationEnd);
|
||||
unweighted_health_per_lot = cm!(-asset_price
|
||||
* base_lot_size
|
||||
* perp_market.init_base_asset_weight
|
||||
+ oracle_price_per_lot * quote_init_asset_weight * fee_factor);
|
||||
unweighted_health_per_lot =
|
||||
-asset_price * base_lot_size * perp_market.init_base_asset_weight
|
||||
+ oracle_price_per_lot * quote_init_asset_weight * fee_factor;
|
||||
} else {
|
||||
// liqee_base_lots <= 0
|
||||
require_msg!(
|
||||
|
@ -262,17 +261,15 @@ pub(crate) fn liquidation_action(
|
|||
// and reduced by `base * price * (1 + liq_fee) * quote_init_liab_weight`
|
||||
let quote_init_liab_weight = I80F48::ONE;
|
||||
direction = 1;
|
||||
fee_factor = cm!(I80F48::ONE + perp_market.base_liquidation_fee);
|
||||
fee_factor = I80F48::ONE + perp_market.base_liquidation_fee;
|
||||
let liab_price = perp_info.prices.liab(HealthType::LiquidationEnd);
|
||||
unweighted_health_per_lot = cm!(liab_price
|
||||
* base_lot_size
|
||||
* perp_market.init_base_liab_weight
|
||||
- oracle_price_per_lot * quote_init_liab_weight * fee_factor);
|
||||
unweighted_health_per_lot = liab_price * base_lot_size * perp_market.init_base_liab_weight
|
||||
- oracle_price_per_lot * quote_init_liab_weight * fee_factor;
|
||||
};
|
||||
assert!(unweighted_health_per_lot > 0);
|
||||
|
||||
// Amount of settle token received for each token that is settled
|
||||
let spot_gain_per_settled = cm!(I80F48::ONE - perp_market.positive_pnl_liquidation_fee);
|
||||
let spot_gain_per_settled = I80F48::ONE - perp_market.positive_pnl_liquidation_fee;
|
||||
|
||||
let init_overall_asset_weight = perp_market.init_overall_asset_weight;
|
||||
|
||||
|
@ -283,10 +280,10 @@ pub(crate) fn liquidation_action(
|
|||
if unweighted < 0 {
|
||||
unweighted
|
||||
} else if unweighted < max_pnl_transfer {
|
||||
cm!(unweighted * spot_gain_per_settled)
|
||||
unweighted * spot_gain_per_settled
|
||||
} else {
|
||||
let unsettled = cm!(unweighted - max_pnl_transfer);
|
||||
cm!(max_pnl_transfer * spot_gain_per_settled + unsettled * init_overall_asset_weight)
|
||||
let unsettled = unweighted - max_pnl_transfer;
|
||||
max_pnl_transfer * spot_gain_per_settled + unsettled * init_overall_asset_weight
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -301,7 +298,7 @@ pub(crate) fn liquidation_action(
|
|||
.weigh_health_contribution(current_unweighted_perp_health, HealthType::LiquidationEnd);
|
||||
let mut current_expected_perp_health = expected_perp_health(current_unweighted_perp_health);
|
||||
let mut current_expected_health =
|
||||
cm!(liqee_liq_end_health + current_expected_perp_health - initial_weighted_perp_health);
|
||||
liqee_liq_end_health + current_expected_perp_health - initial_weighted_perp_health;
|
||||
|
||||
let mut reduce_base = |step: &str,
|
||||
health_amount: I80F48,
|
||||
|
@ -312,20 +309,18 @@ pub(crate) fn liquidation_action(
|
|||
.min(-current_expected_health)
|
||||
.max(I80F48::ZERO);
|
||||
// How many lots to transfer?
|
||||
let base_lots = cm!(health_limit / health_per_lot)
|
||||
.checked_ceil() // overshoot to aim for init_health >= 0
|
||||
.unwrap()
|
||||
.checked_to_num::<i64>()
|
||||
.unwrap()
|
||||
let base_lots = (health_limit / health_per_lot)
|
||||
.ceil() // overshoot to aim for init_health >= 0
|
||||
.to_num::<i64>()
|
||||
.min(liqee_base_lots.abs() - base_reduction)
|
||||
.min(max_base_transfer.abs() - base_reduction)
|
||||
.max(0);
|
||||
let unweighted_change = cm!(I80F48::from(base_lots) * unweighted_health_per_lot);
|
||||
let unweighted_change = I80F48::from(base_lots) * unweighted_health_per_lot;
|
||||
let current_unweighted = *current_unweighted_perp_health;
|
||||
let new_unweighted_perp = cm!(current_unweighted + unweighted_change);
|
||||
let new_unweighted_perp = current_unweighted + unweighted_change;
|
||||
let new_expected_perp = expected_perp_health(new_unweighted_perp);
|
||||
let new_expected_health =
|
||||
cm!(current_expected_health + (new_expected_perp - current_expected_perp_health));
|
||||
current_expected_health + (new_expected_perp - current_expected_perp_health);
|
||||
msg!(
|
||||
"{}: {} lots, health {} -> {}, unweighted perp {} -> {}",
|
||||
step,
|
||||
|
@ -360,10 +355,10 @@ pub(crate) fn liquidation_action(
|
|||
// benefit account health slightly less because of the settlement liquidation fee.
|
||||
//
|
||||
if current_unweighted_perp_health >= 0 && current_unweighted_perp_health < max_pnl_transfer {
|
||||
let settled_health_per_lot = cm!(unweighted_health_per_lot * spot_gain_per_settled);
|
||||
let settled_health_per_lot = unweighted_health_per_lot * spot_gain_per_settled;
|
||||
reduce_base(
|
||||
"settleable",
|
||||
cm!(max_pnl_transfer - current_unweighted_perp_health),
|
||||
max_pnl_transfer - current_unweighted_perp_health,
|
||||
settled_health_per_lot,
|
||||
&mut current_unweighted_perp_health,
|
||||
);
|
||||
|
@ -373,7 +368,7 @@ pub(crate) fn liquidation_action(
|
|||
// Step 3: Above that, perp base positions only benefit account health if the pnl asset weight is positive
|
||||
//
|
||||
if current_unweighted_perp_health >= max_pnl_transfer && init_overall_asset_weight > 0 {
|
||||
let weighted_health_per_lot = cm!(unweighted_health_per_lot * init_overall_asset_weight);
|
||||
let weighted_health_per_lot = unweighted_health_per_lot * init_overall_asset_weight;
|
||||
reduce_base(
|
||||
"positive",
|
||||
I80F48::MAX,
|
||||
|
@ -386,8 +381,8 @@ pub(crate) fn liquidation_action(
|
|||
// Execute the base reduction. This is essentially a forced trade and updates the
|
||||
// liqee and liqors entry and break even prices.
|
||||
//
|
||||
let base_transfer = cm!(direction * base_reduction);
|
||||
let quote_transfer = cm!(-I80F48::from(base_transfer) * oracle_price_per_lot * fee_factor);
|
||||
let base_transfer = direction * base_reduction;
|
||||
let quote_transfer = -I80F48::from(base_transfer) * oracle_price_per_lot * fee_factor;
|
||||
if base_transfer != 0 {
|
||||
msg!(
|
||||
"transfering: {} base lots and {} quote",
|
||||
|
@ -405,14 +400,12 @@ pub(crate) fn liquidation_action(
|
|||
let final_weighted_perp_health = perp_info
|
||||
.weigh_health_contribution(current_unweighted_perp_health, HealthType::LiquidationEnd);
|
||||
let current_actual_health =
|
||||
cm!(liqee_liq_end_health - initial_weighted_perp_health + final_weighted_perp_health);
|
||||
liqee_liq_end_health - initial_weighted_perp_health + final_weighted_perp_health;
|
||||
let pnl_transfer_possible =
|
||||
current_actual_health < 0 && current_unweighted_perp_health > 0 && max_pnl_transfer > 0;
|
||||
let (pnl_transfer, limit_transfer) = if pnl_transfer_possible {
|
||||
let health_per_transfer = cm!(spot_gain_per_settled - init_overall_asset_weight);
|
||||
let transfer_for_zero = cm!(-current_actual_health / health_per_transfer)
|
||||
.checked_ceil()
|
||||
.unwrap();
|
||||
let health_per_transfer = spot_gain_per_settled - init_overall_asset_weight;
|
||||
let transfer_for_zero = (-current_actual_health / health_per_transfer).ceil();
|
||||
let liqee_pnl = liqee_perp_position.unsettled_pnl(&perp_market, oracle_price)?;
|
||||
|
||||
// Allow taking over *more* than the liqee_positive_settle_limit. In exchange, the liqor
|
||||
|
@ -430,28 +423,25 @@ pub(crate) fn liquidation_action(
|
|||
let limit_transfer = {
|
||||
// take care, liqee_limit may be i64::MAX
|
||||
let liqee_limit: i128 = liqee_positive_settle_limit.into();
|
||||
let settle = pnl_transfer.checked_floor().unwrap().to_num::<i128>();
|
||||
let total = liqee_pnl.checked_ceil().unwrap().to_num::<i128>();
|
||||
let liqor_limit: i64 = cm!(liqee_limit * settle / total).try_into().unwrap();
|
||||
let settle = pnl_transfer.floor().to_num::<i128>();
|
||||
let total = liqee_pnl.ceil().to_num::<i128>();
|
||||
let liqor_limit: i64 = (liqee_limit * settle / total).try_into().unwrap();
|
||||
I80F48::from(liqor_limit).min(pnl_transfer).max(I80F48::ONE)
|
||||
};
|
||||
|
||||
// The liqor pays less than the full amount to receive the positive pnl
|
||||
let token_transfer = cm!(pnl_transfer * spot_gain_per_settled);
|
||||
let token_transfer = pnl_transfer * spot_gain_per_settled;
|
||||
|
||||
if pnl_transfer > 0 {
|
||||
liqor_perp_position.record_liquidation_pnl_takeover(pnl_transfer, limit_transfer);
|
||||
liqee_perp_position.record_settle(pnl_transfer);
|
||||
|
||||
// Update the accounts' perp_spot_transfer statistics.
|
||||
let transfer_i64 = token_transfer
|
||||
.round_to_zero()
|
||||
.checked_to_num::<i64>()
|
||||
.unwrap();
|
||||
cm!(liqor_perp_position.perp_spot_transfers -= transfer_i64);
|
||||
cm!(liqee_perp_position.perp_spot_transfers += transfer_i64);
|
||||
cm!(liqor.fixed.perp_spot_transfers -= transfer_i64);
|
||||
cm!(liqee.fixed.perp_spot_transfers += transfer_i64);
|
||||
let transfer_i64 = token_transfer.round_to_zero().to_num::<i64>();
|
||||
liqor_perp_position.perp_spot_transfers -= transfer_i64;
|
||||
liqee_perp_position.perp_spot_transfers += transfer_i64;
|
||||
liqor.fixed.perp_spot_transfers -= transfer_i64;
|
||||
liqee.fixed.perp_spot_transfers += transfer_i64;
|
||||
|
||||
// Transfer token balance
|
||||
let liqor_token_position = liqor.token_position_mut(settle_token_index)?.0;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token;
|
||||
use checked_math as cm;
|
||||
|
||||
use fixed::types::I80F48;
|
||||
|
||||
use crate::accounts_ix::*;
|
||||
|
@ -112,11 +112,11 @@ pub fn perp_liq_negative_pnl_or_bankruptcy(
|
|||
liqee_perp_position.record_settle(-settlement);
|
||||
|
||||
// Update the accounts' perp_spot_transfer statistics.
|
||||
let settlement_i64 = settlement.round_to_zero().checked_to_num::<i64>().unwrap();
|
||||
cm!(liqor_perp_position.perp_spot_transfers += settlement_i64);
|
||||
cm!(liqee_perp_position.perp_spot_transfers -= settlement_i64);
|
||||
cm!(liqor.fixed.perp_spot_transfers += settlement_i64);
|
||||
cm!(liqee.fixed.perp_spot_transfers -= settlement_i64);
|
||||
let settlement_i64 = settlement.round_to_zero().to_num::<i64>();
|
||||
liqor_perp_position.perp_spot_transfers += settlement_i64;
|
||||
liqee_perp_position.perp_spot_transfers -= settlement_i64;
|
||||
liqor.fixed.perp_spot_transfers += settlement_i64;
|
||||
liqee.fixed.perp_spot_transfers -= settlement_i64;
|
||||
|
||||
// Transfer token balance
|
||||
let liqor_token_position = liqor.token_position_mut(settle_token_index)?.0;
|
||||
|
@ -141,7 +141,7 @@ pub fn perp_liq_negative_pnl_or_bankruptcy(
|
|||
msg!("liquidated pnl = {}", settlement);
|
||||
}
|
||||
};
|
||||
let max_liab_transfer = cm!(I80F48::from(max_liab_transfer) - settlement);
|
||||
let max_liab_transfer = I80F48::from(max_liab_transfer) - settlement;
|
||||
|
||||
//
|
||||
// Step 2: bankruptcy
|
||||
|
@ -167,14 +167,12 @@ pub fn perp_liq_negative_pnl_or_bankruptcy(
|
|||
0
|
||||
};
|
||||
|
||||
let liquidation_fee_factor = cm!(I80F48::ONE + perp_market.base_liquidation_fee);
|
||||
let liquidation_fee_factor = I80F48::ONE + perp_market.base_liquidation_fee;
|
||||
|
||||
// Amount given to the liqor from the insurance fund
|
||||
let insurance_transfer = cm!(liab_transfer * liquidation_fee_factor)
|
||||
.checked_ceil()
|
||||
.unwrap()
|
||||
.checked_to_num::<u64>()
|
||||
.unwrap()
|
||||
let insurance_transfer = (liab_transfer * liquidation_fee_factor)
|
||||
.ceil()
|
||||
.to_num::<u64>()
|
||||
.min(insurance_vault_amount);
|
||||
|
||||
let insurance_transfer_i80f48 = I80F48::from(insurance_transfer);
|
||||
|
@ -182,7 +180,7 @@ pub fn perp_liq_negative_pnl_or_bankruptcy(
|
|||
|
||||
// Amount of negative perp pnl transfered to the liqor
|
||||
let insurance_liab_transfer =
|
||||
cm!(insurance_transfer_i80f48 / liquidation_fee_factor).min(liab_transfer);
|
||||
(insurance_transfer_i80f48 / liquidation_fee_factor).min(liab_transfer);
|
||||
|
||||
// Try using the insurance fund if possible
|
||||
if insurance_transfer > 0 {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use checked_math as cm;
|
||||
|
||||
use fixed::types::I80F48;
|
||||
|
||||
use crate::accounts_zerocopy::*;
|
||||
|
@ -64,7 +64,7 @@ pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: u64) ->
|
|||
require!(settlement >= 0, MangoError::SettlementAmountMustBePositive);
|
||||
|
||||
perp_position.record_settle(-settlement); // settle the negative pnl on the user perp position
|
||||
perp_market.fees_accrued = cm!(perp_market.fees_accrued - settlement);
|
||||
perp_market.fees_accrued -= settlement;
|
||||
|
||||
emit_perp_balances(
|
||||
ctx.accounts.group.key(),
|
||||
|
@ -74,7 +74,7 @@ pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: u64) ->
|
|||
);
|
||||
|
||||
// Update the account's perp_spot_transfers with the new PnL
|
||||
let settlement_i64 = settlement.round().checked_to_num::<i64>().unwrap();
|
||||
let settlement_i64 = settlement.round().to_num::<i64>();
|
||||
|
||||
// Safety check to prevent any accidental negative transfer
|
||||
require!(
|
||||
|
@ -82,8 +82,8 @@ pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: u64) ->
|
|||
MangoError::SettlementAmountMustBePositive
|
||||
);
|
||||
|
||||
cm!(perp_position.perp_spot_transfers -= settlement_i64);
|
||||
cm!(account.fixed.perp_spot_transfers -= settlement_i64);
|
||||
perp_position.perp_spot_transfers -= settlement_i64;
|
||||
account.fixed.perp_spot_transfers -= settlement_i64;
|
||||
|
||||
// Transfer token balances
|
||||
let token_position = account
|
||||
|
@ -96,7 +96,7 @@ pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: u64) ->
|
|||
oracle_price,
|
||||
)?;
|
||||
// Update the settled balance on the market itself
|
||||
perp_market.fees_settled = cm!(perp_market.fees_settled + settlement);
|
||||
perp_market.fees_settled += settlement;
|
||||
|
||||
emit!(TokenBalanceLog {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use checked_math as cm;
|
||||
|
||||
use fixed::types::I80F48;
|
||||
|
||||
use crate::accounts_ix::*;
|
||||
|
@ -157,18 +157,18 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
|
|||
// Applying the fee here means that it decreases the displayed perp pnl.
|
||||
// Think about it like this: a's pnl reduces by `settlement` and spot increases by `settlement - fee`.
|
||||
// That means that it managed to extract `settlement - fee` from perp interactions.
|
||||
let settlement_i64 = settlement.round_to_zero().checked_to_num::<i64>().unwrap();
|
||||
let fee_i64 = fee.round_to_zero().checked_to_num::<i64>().unwrap();
|
||||
cm!(a_perp_position.perp_spot_transfers += settlement_i64 - fee_i64);
|
||||
cm!(b_perp_position.perp_spot_transfers -= settlement_i64);
|
||||
cm!(account_a.fixed.perp_spot_transfers += settlement_i64 - fee_i64);
|
||||
cm!(account_b.fixed.perp_spot_transfers -= settlement_i64);
|
||||
let settlement_i64 = settlement.round_to_zero().to_num::<i64>();
|
||||
let fee_i64 = fee.round_to_zero().to_num::<i64>();
|
||||
(a_perp_position.perp_spot_transfers += settlement_i64 - fee_i64);
|
||||
(b_perp_position.perp_spot_transfers -= settlement_i64);
|
||||
(account_a.fixed.perp_spot_transfers += settlement_i64 - fee_i64);
|
||||
(account_b.fixed.perp_spot_transfers -= settlement_i64);
|
||||
|
||||
// Transfer token balances
|
||||
// The fee is paid by the account with positive unsettled pnl
|
||||
let a_token_position = account_a.token_position_mut(settle_token_index)?.0;
|
||||
let b_token_position = account_b.token_position_mut(settle_token_index)?.0;
|
||||
settle_bank.deposit(a_token_position, cm!(settlement - fee), now_ts)?;
|
||||
settle_bank.deposit(a_token_position, settlement - fee, now_ts)?;
|
||||
// Don't charge loan origination fees on borrows created via settling:
|
||||
// Even small loan origination fees could accumulate if a perp position is
|
||||
// settled back and forth repeatedly.
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::accounts_ix::*;
|
|||
use crate::logs::{Serum3OpenOrdersBalanceLogV2, TokenBalanceLog};
|
||||
use crate::serum3_cpi::{load_market_state, load_open_orders_ref};
|
||||
use anchor_lang::prelude::*;
|
||||
use checked_math as cm;
|
||||
|
||||
use fixed::types::I80F48;
|
||||
use serum_dex::instruction::NewOrderInstructionV3;
|
||||
use serum_dex::state::OpenOrders;
|
||||
|
@ -45,10 +45,10 @@ pub trait OpenOrdersAmounts {
|
|||
|
||||
impl OpenOrdersAmounts for OpenOrdersSlim {
|
||||
fn native_base_reserved(&self) -> u64 {
|
||||
cm!(self.native_coin_total - self.native_coin_free)
|
||||
self.native_coin_total - self.native_coin_free
|
||||
}
|
||||
fn native_quote_reserved(&self) -> u64 {
|
||||
cm!(self.native_pc_total - self.native_pc_free)
|
||||
self.native_pc_total - self.native_pc_free
|
||||
}
|
||||
fn native_base_free(&self) -> u64 {
|
||||
self.native_coin_free
|
||||
|
@ -69,10 +69,10 @@ impl OpenOrdersAmounts for OpenOrdersSlim {
|
|||
|
||||
impl OpenOrdersAmounts for OpenOrders {
|
||||
fn native_base_reserved(&self) -> u64 {
|
||||
cm!(self.native_coin_total - self.native_coin_free)
|
||||
self.native_coin_total - self.native_coin_free
|
||||
}
|
||||
fn native_quote_reserved(&self) -> u64 {
|
||||
cm!(self.native_pc_total - self.native_pc_free)
|
||||
self.native_pc_total - self.native_pc_free
|
||||
}
|
||||
fn native_base_free(&self) -> u64 {
|
||||
self.native_coin_free
|
||||
|
@ -176,7 +176,7 @@ pub fn serum3_place_order(
|
|||
|
||||
let needed_amount = match side {
|
||||
Serum3Side::Ask => {
|
||||
cm!(max_base_qty * base_lot_size).saturating_sub(before_oo.native_base_free())
|
||||
(max_base_qty * base_lot_size).saturating_sub(before_oo.native_base_free())
|
||||
}
|
||||
Serum3Side::Bid => {
|
||||
max_native_quote_qty_including_fees.saturating_sub(before_oo.native_quote_free())
|
||||
|
@ -244,7 +244,7 @@ pub fn serum3_place_order(
|
|||
let mut payer_bank = ctx.accounts.payer_bank.load_mut()?;
|
||||
|
||||
// Enforce min vault to deposits ratio
|
||||
let withdrawn_from_vault = I80F48::from(cm!(before_vault - after_vault));
|
||||
let withdrawn_from_vault = I80F48::from(before_vault - after_vault);
|
||||
let position_native = account
|
||||
.token_position_mut(payer_bank.token_index)?
|
||||
.0
|
||||
|
@ -292,14 +292,14 @@ pub struct OODifference {
|
|||
impl OODifference {
|
||||
pub fn new(before_oo: &OpenOrdersSlim, after_oo: &OpenOrdersSlim) -> Self {
|
||||
Self {
|
||||
reserved_base_change: cm!(I80F48::from(after_oo.native_base_reserved())
|
||||
- I80F48::from(before_oo.native_base_reserved())),
|
||||
reserved_quote_change: cm!(I80F48::from(after_oo.native_quote_reserved())
|
||||
- I80F48::from(before_oo.native_quote_reserved())),
|
||||
free_base_change: cm!(I80F48::from(after_oo.native_base_free())
|
||||
- I80F48::from(before_oo.native_base_free())),
|
||||
free_quote_change: cm!(I80F48::from(after_oo.native_quote_free())
|
||||
- I80F48::from(before_oo.native_quote_free())),
|
||||
reserved_base_change: I80F48::from(after_oo.native_base_reserved())
|
||||
- I80F48::from(before_oo.native_base_reserved()),
|
||||
reserved_quote_change: I80F48::from(after_oo.native_quote_reserved())
|
||||
- I80F48::from(before_oo.native_quote_reserved()),
|
||||
free_base_change: I80F48::from(after_oo.native_base_free())
|
||||
- I80F48::from(before_oo.native_base_free()),
|
||||
free_quote_change: I80F48::from(after_oo.native_quote_free())
|
||||
- I80F48::from(before_oo.native_quote_free()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -349,7 +349,7 @@ fn apply_vault_difference(
|
|||
vault_before: u64,
|
||||
oracle_price: Option<I80F48>,
|
||||
) -> Result<VaultDifference> {
|
||||
let needed_change = cm!(I80F48::from(vault_after) - I80F48::from(vault_before));
|
||||
let needed_change = I80F48::from(vault_after) - I80F48::from(vault_before);
|
||||
|
||||
let (position, _) = account.token_position_mut(bank.token_index)?;
|
||||
let native_before = position.native(bank);
|
||||
|
@ -365,7 +365,7 @@ fn apply_vault_difference(
|
|||
)?;
|
||||
}
|
||||
let native_after = position.native(bank);
|
||||
let native_change = cm!(native_after - native_before);
|
||||
let native_change = native_after - native_before;
|
||||
let new_borrows = native_change
|
||||
.max(native_after)
|
||||
.min(I80F48::ZERO)
|
||||
|
@ -386,7 +386,7 @@ fn apply_vault_difference(
|
|||
|
||||
// Only for place: Add to potential borrow amount
|
||||
let old_value = *borrows_without_fee;
|
||||
*borrows_without_fee = cm!(old_value + new_borrows);
|
||||
*borrows_without_fee = old_value + new_borrows;
|
||||
|
||||
// Only for settle/liq_force_cancel: Reduce the potential borrow amounts
|
||||
if needed_change > 0 {
|
||||
|
@ -428,7 +428,7 @@ pub fn apply_settle_changes(
|
|||
let received_fees = before_oo
|
||||
.native_rebates()
|
||||
.saturating_sub(after_oo.native_rebates());
|
||||
cm!(quote_bank.collected_fees_native += I80F48::from(received_fees));
|
||||
quote_bank.collected_fees_native += I80F48::from(received_fees);
|
||||
|
||||
// Don't count the referrer rebate fees as part of the vault change that should be
|
||||
// credited to the user.
|
||||
|
|
|
@ -8,7 +8,6 @@ use crate::accounts_zerocopy::AccountInfoRef;
|
|||
use crate::error::*;
|
||||
use crate::health::*;
|
||||
use crate::state::*;
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
use crate::accounts_ix::*;
|
||||
use crate::logs::{DepositLog, TokenBalanceLog};
|
||||
|
@ -94,8 +93,8 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
|
|||
)?;
|
||||
|
||||
// Update the net deposits - adjust by price so different tokens are on the same basis (in USD terms)
|
||||
let amount_usd = cm!(amount_i80f48 * oracle_price).to_num::<i64>();
|
||||
cm!(account.fixed.net_deposits += amount_usd);
|
||||
let amount_usd = (amount_i80f48 * oracle_price).to_num::<i64>();
|
||||
account.fixed.net_deposits += amount_usd;
|
||||
|
||||
emit!(TokenBalanceLog {
|
||||
mango_group: self.group.key(),
|
||||
|
@ -135,8 +134,7 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
|
|||
.health_assets_and_liabs(HealthType::Init)
|
||||
.0
|
||||
.round_to_zero()
|
||||
.checked_to_num::<u64>()
|
||||
.unwrap();
|
||||
.to_num::<u64>();
|
||||
require_msg_typed!(
|
||||
assets <= group.deposit_limit_quote,
|
||||
MangoError::DepositLimit,
|
||||
|
|
|
@ -6,7 +6,6 @@ use crate::accounts_zerocopy::*;
|
|||
use crate::error::*;
|
||||
use crate::health::*;
|
||||
use crate::state::*;
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
use crate::accounts_ix::*;
|
||||
use crate::logs::{
|
||||
|
@ -64,9 +63,9 @@ pub fn token_liq_bankruptcy(
|
|||
let liab_fee_factor = if liab_token_index == QUOTE_TOKEN_INDEX {
|
||||
I80F48::ONE
|
||||
} else {
|
||||
cm!(I80F48::ONE + liab_bank.liquidation_fee)
|
||||
I80F48::ONE + liab_bank.liquidation_fee
|
||||
};
|
||||
let liab_price_adjusted = cm!(liab_oracle_price * liab_fee_factor);
|
||||
let liab_price_adjusted = liab_oracle_price * liab_fee_factor;
|
||||
|
||||
let liab_transfer_unrounded = remaining_liab_loss.min(max_liab_transfer);
|
||||
|
||||
|
@ -76,11 +75,9 @@ pub fn token_liq_bankruptcy(
|
|||
0
|
||||
};
|
||||
|
||||
let insurance_transfer = cm!(liab_transfer_unrounded * liab_price_adjusted)
|
||||
.checked_ceil()
|
||||
.unwrap()
|
||||
.checked_to_num::<u64>()
|
||||
.unwrap()
|
||||
let insurance_transfer = (liab_transfer_unrounded * liab_price_adjusted)
|
||||
.ceil()
|
||||
.to_num::<u64>()
|
||||
.min(insurance_vault_amount);
|
||||
|
||||
let insurance_fund_exhausted = insurance_transfer == insurance_vault_amount;
|
||||
|
@ -90,7 +87,7 @@ pub fn token_liq_bankruptcy(
|
|||
// AUDIT: v3 does this, but it seems bad, because it can make liab_transfer
|
||||
// exceed max_liab_transfer due to the ceil() above! Otoh, not doing it would allow
|
||||
// liquidators to exploit the insurance fund for 1 native token each call.
|
||||
let liab_transfer = cm!(insurance_transfer_i80f48 / liab_price_adjusted);
|
||||
let liab_transfer = insurance_transfer_i80f48 / liab_price_adjusted;
|
||||
|
||||
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
|
||||
|
||||
|
@ -203,15 +200,14 @@ pub fn token_liq_bankruptcy(
|
|||
let mut indexed_total_deposits = I80F48::ZERO;
|
||||
for bank_ai in bank_ais.iter() {
|
||||
let bank = bank_ai.load::<Bank>()?;
|
||||
cm!(indexed_total_deposits += bank.indexed_deposits);
|
||||
indexed_total_deposits += bank.indexed_deposits;
|
||||
}
|
||||
|
||||
// This is the solution to:
|
||||
// total_indexed_deposits * (deposit_index - new_deposit_index) = remaining_liab_loss
|
||||
// AUDIT: Could it happen that remaining_liab_loss > total_indexed_deposits * deposit_index?
|
||||
// Probably not.
|
||||
let new_deposit_index =
|
||||
cm!(liab_deposit_index - remaining_liab_loss / indexed_total_deposits);
|
||||
let new_deposit_index = liab_deposit_index - remaining_liab_loss / indexed_total_deposits;
|
||||
liab_deposit_index = new_deposit_index;
|
||||
socialized_loss = remaining_liab_loss;
|
||||
|
||||
|
@ -227,7 +223,7 @@ pub fn token_liq_bankruptcy(
|
|||
// could bring the total position slightly above zero otherwise
|
||||
liqee_liab_active =
|
||||
bank.deposit_with_dusting(liqee_liab, amount_for_bank, now_ts)?;
|
||||
cm!(amount_to_credit -= amount_for_bank);
|
||||
amount_to_credit -= amount_for_bank;
|
||||
if amount_to_credit <= 0 {
|
||||
break;
|
||||
}
|
||||
|
@ -250,8 +246,7 @@ pub fn token_liq_bankruptcy(
|
|||
|
||||
let liab_bank = bank_ais[0].load::<Bank>()?;
|
||||
let end_liab_native = liqee_liab.native(&liab_bank);
|
||||
liqee_health_cache
|
||||
.adjust_token_balance(&liab_bank, cm!(end_liab_native - initial_liab_native))?;
|
||||
liqee_health_cache.adjust_token_balance(&liab_bank, end_liab_native - initial_liab_native)?;
|
||||
|
||||
// Check liqee health again
|
||||
let liqee_liq_end_health = liqee_health_cache.health(HealthType::LiquidationEnd);
|
||||
|
|
|
@ -10,7 +10,6 @@ use crate::logs::{
|
|||
WithdrawLoanOriginationFeeLog,
|
||||
};
|
||||
use crate::state::*;
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
pub fn token_liq_with_token(
|
||||
ctx: Context<TokenLiqWithToken>,
|
||||
|
@ -125,8 +124,8 @@ pub(crate) fn liquidation_action(
|
|||
// assets = liabs * liab_oracle_price / asset_oracle_price * fee_factor
|
||||
// assets = liabs * liab_oracle_price_adjusted / asset_oracle_price
|
||||
// = liabs * lopa / aop
|
||||
let fee_factor = cm!(I80F48::ONE + asset_bank.liquidation_fee + liab_bank.liquidation_fee);
|
||||
let liab_oracle_price_adjusted = cm!(liab_oracle_price * fee_factor);
|
||||
let fee_factor = I80F48::ONE + asset_bank.liquidation_fee + liab_bank.liquidation_fee;
|
||||
let liab_oracle_price_adjusted = liab_oracle_price * fee_factor;
|
||||
|
||||
let init_asset_weight = asset_bank.init_asset_weight;
|
||||
let init_liab_weight = liab_bank.init_liab_weight;
|
||||
|
@ -163,14 +162,14 @@ pub(crate) fn liquidation_action(
|
|||
// y = x * lopa / aop (native asset tokens, see above)
|
||||
//
|
||||
// Result: x = -init_health / (ilw * llep - iaw * lopa * alep / aop)
|
||||
let liab_needed = cm!(-liqee_liq_end_health
|
||||
let liab_needed = -liqee_liq_end_health
|
||||
/ (liab_liq_end_price * init_liab_weight
|
||||
- liab_oracle_price_adjusted
|
||||
* init_asset_weight
|
||||
* (asset_liq_end_price / asset_oracle_price)));
|
||||
* (asset_liq_end_price / asset_oracle_price));
|
||||
|
||||
// How much liab can we get at most for the asset balance?
|
||||
let liab_possible = cm!(liqee_asset_native * asset_oracle_price / liab_oracle_price_adjusted);
|
||||
let liab_possible = liqee_asset_native * asset_oracle_price / liab_oracle_price_adjusted;
|
||||
|
||||
// The amount of liab native tokens we will transfer
|
||||
let liab_transfer = min(
|
||||
|
@ -179,7 +178,7 @@ pub(crate) fn liquidation_action(
|
|||
);
|
||||
|
||||
// The amount of asset native tokens we will give up for them
|
||||
let asset_transfer = cm!(liab_transfer * liab_oracle_price_adjusted / asset_oracle_price);
|
||||
let asset_transfer = liab_transfer * liab_oracle_price_adjusted / asset_oracle_price;
|
||||
|
||||
// During liquidation, we mustn't leave small positive balances in the liqee. Those
|
||||
// could break bankruptcy-detection. Thus we dust them even if the token position
|
||||
|
@ -219,11 +218,9 @@ pub(crate) fn liquidation_action(
|
|||
|
||||
// Update the health cache
|
||||
liqee_health_cache
|
||||
.adjust_token_balance(liab_bank, cm!(liqee_liab_native_after - liqee_liab_native))?;
|
||||
liqee_health_cache.adjust_token_balance(
|
||||
asset_bank,
|
||||
cm!(liqee_assets_native_after - liqee_asset_native),
|
||||
)?;
|
||||
.adjust_token_balance(liab_bank, liqee_liab_native_after - liqee_liab_native)?;
|
||||
liqee_health_cache
|
||||
.adjust_token_balance(asset_bank, liqee_assets_native_after - liqee_asset_native)?;
|
||||
|
||||
msg!(
|
||||
"liquidated {} liab for {} asset",
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use fixed::types::I80F48;
|
||||
use fixed_macro::types::I80F48;
|
||||
|
||||
use crate::accounts_zerocopy::AccountInfoRef;
|
||||
use crate::error::*;
|
||||
|
@ -9,7 +8,7 @@ use crate::util::fill_from_str;
|
|||
|
||||
use crate::logs::TokenMetaDataLog;
|
||||
|
||||
pub const INDEX_START: I80F48 = I80F48!(1_000_000);
|
||||
pub const INDEX_START: I80F48 = I80F48::lit("1_000_000");
|
||||
|
||||
use crate::accounts_ix::*;
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::{
|
|||
};
|
||||
use anchor_lang::solana_program::sysvar::instructions as tx_instructions;
|
||||
use anchor_lang::Discriminator;
|
||||
use checked_math as cm;
|
||||
|
||||
use fixed::types::I80F48;
|
||||
|
||||
pub mod compute_budget {
|
||||
|
@ -62,8 +62,8 @@ pub fn token_update_index_and_rate(ctx: Context<TokenUpdateIndexAndRate>) -> Res
|
|||
let mut indexed_total_borrows = I80F48::ZERO;
|
||||
for ai in ctx.remaining_accounts.iter() {
|
||||
let bank = ai.load::<Bank>()?;
|
||||
cm!(indexed_total_deposits += bank.indexed_deposits);
|
||||
cm!(indexed_total_borrows += bank.indexed_borrows);
|
||||
indexed_total_deposits += bank.indexed_deposits;
|
||||
indexed_total_borrows += bank.indexed_borrows;
|
||||
}
|
||||
|
||||
// compute and set latest index and average utilization on each bank
|
||||
|
@ -71,12 +71,12 @@ pub fn token_update_index_and_rate(ctx: Context<TokenUpdateIndexAndRate>) -> Res
|
|||
{
|
||||
let mut some_bank = ctx.remaining_accounts[0].load_mut::<Bank>()?;
|
||||
|
||||
let diff_ts = I80F48::from_num(cm!(now_ts - some_bank.index_last_updated));
|
||||
let diff_ts = I80F48::from_num(now_ts - some_bank.index_last_updated);
|
||||
|
||||
let (deposit_index, borrow_index, borrow_fees, borrow_rate, deposit_rate) =
|
||||
some_bank.compute_index(indexed_total_deposits, indexed_total_borrows, diff_ts)?;
|
||||
|
||||
cm!(some_bank.collected_fees_native += borrow_fees);
|
||||
some_bank.collected_fees_native += borrow_fees;
|
||||
|
||||
let new_avg_utilization = some_bank.compute_new_avg_utilization(
|
||||
indexed_total_deposits,
|
||||
|
@ -104,8 +104,8 @@ pub fn token_update_index_and_rate(ctx: Context<TokenUpdateIndexAndRate>) -> Res
|
|||
stable_price: some_bank.stable_price().to_bits(),
|
||||
collected_fees: some_bank.collected_fees_native.to_bits(),
|
||||
loan_fee_rate: some_bank.loan_fee_rate.to_bits(),
|
||||
total_deposits: cm!(deposit_index * indexed_total_deposits).to_bits(),
|
||||
total_borrows: cm!(borrow_index * indexed_total_borrows).to_bits(),
|
||||
total_deposits: (deposit_index * indexed_total_deposits).to_bits(),
|
||||
total_borrows: (borrow_index * indexed_total_borrows).to_bits(),
|
||||
borrow_rate: borrow_rate.to_bits(),
|
||||
deposit_rate: deposit_rate.to_bits(),
|
||||
});
|
||||
|
|
|
@ -10,7 +10,6 @@ use crate::accounts_ix::*;
|
|||
use crate::logs::{
|
||||
LoanOriginationFeeInstruction, TokenBalanceLog, WithdrawLoanOriginationFeeLog, WithdrawLog,
|
||||
};
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bool) -> Result<()> {
|
||||
require_msg!(amount > 0, "withdraw amount must be positive");
|
||||
|
@ -102,14 +101,14 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
|
|||
});
|
||||
|
||||
// Update the net deposits - adjust by price so different tokens are on the same basis (in USD terms)
|
||||
let amount_usd = cm!(amount_i80f48 * oracle_price).to_num::<i64>();
|
||||
cm!(account.fixed.net_deposits -= amount_usd);
|
||||
let amount_usd = (amount_i80f48 * oracle_price).to_num::<i64>();
|
||||
account.fixed.net_deposits -= amount_usd;
|
||||
|
||||
//
|
||||
// Health check
|
||||
//
|
||||
if let Some((mut health_cache, pre_init_health)) = pre_health_opt {
|
||||
health_cache.adjust_token_balance(&bank, cm!(native_position_after - native_position))?;
|
||||
health_cache.adjust_token_balance(&bank, native_position_after - native_position)?;
|
||||
account.check_health_post(&health_cache, pre_init_health)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,21 +3,20 @@ use crate::accounts_zerocopy::KeyedAccountReader;
|
|||
use crate::error::*;
|
||||
use crate::state::{oracle, StablePriceModel};
|
||||
use crate::util;
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token::TokenAccount;
|
||||
use derivative::Derivative;
|
||||
use fixed::types::I80F48;
|
||||
use fixed_macro::types::I80F48;
|
||||
use static_assertions::const_assert_eq;
|
||||
|
||||
use std::mem::size_of;
|
||||
|
||||
pub const HOUR: i64 = 3600;
|
||||
pub const DAY: i64 = 86400;
|
||||
pub const DAY_I80F48: I80F48 = I80F48!(86400);
|
||||
pub const YEAR_I80F48: I80F48 = I80F48!(31536000);
|
||||
pub const MINIMUM_MAX_RATE: I80F48 = I80F48!(0.5);
|
||||
pub const DAY_I80F48: I80F48 = I80F48::lit("86_400");
|
||||
pub const YEAR_I80F48: I80F48 = I80F48::lit("31_536_000");
|
||||
pub const MINIMUM_MAX_RATE: I80F48 = I80F48::lit("0.5");
|
||||
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug)]
|
||||
|
@ -235,12 +234,12 @@ impl Bank {
|
|||
|
||||
#[inline(always)]
|
||||
pub fn native_borrows(&self) -> I80F48 {
|
||||
cm!(self.borrow_index * self.indexed_borrows)
|
||||
self.borrow_index * self.indexed_borrows
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn native_deposits(&self) -> I80F48 {
|
||||
cm!(self.deposit_index * self.indexed_deposits)
|
||||
self.deposit_index * self.indexed_deposits
|
||||
}
|
||||
|
||||
/// Prevent borrowing away the full bank vault.
|
||||
|
@ -253,7 +252,7 @@ impl Bank {
|
|||
|
||||
let bank_native_deposits = self.native_deposits();
|
||||
if bank_native_deposits != I80F48::ZERO {
|
||||
let bank_native_deposits: f64 = bank_native_deposits.checked_to_num().unwrap();
|
||||
let bank_native_deposits: f64 = bank_native_deposits.to_num();
|
||||
if vault_amount < self.min_vault_to_deposits_ratio * bank_native_deposits {
|
||||
return err!(MangoError::BankBorrowLimitReached).with_context(|| {
|
||||
format!(
|
||||
|
@ -328,34 +327,34 @@ impl Bank {
|
|||
// (native / index) * index == native, because we sometimes call this function with
|
||||
// values that are products of index.
|
||||
let div_rounding_up = |native: I80F48, index: I80F48| {
|
||||
let indexed = cm!(native / index);
|
||||
if cm!(indexed * index) < native {
|
||||
cm!(indexed + I80F48::DELTA)
|
||||
let indexed = native / index;
|
||||
if (indexed * index) < native {
|
||||
indexed + I80F48::DELTA
|
||||
} else {
|
||||
indexed
|
||||
}
|
||||
};
|
||||
|
||||
if native_position.is_negative() {
|
||||
let new_native_position = cm!(native_position + native_amount);
|
||||
let new_native_position = native_position + native_amount;
|
||||
let indexed_change = div_rounding_up(native_amount, self.borrow_index);
|
||||
// this is only correct if it's not positive, because it scales the whole amount by borrow_index
|
||||
let new_indexed_value = cm!(position.indexed_position + indexed_change);
|
||||
let new_indexed_value = position.indexed_position + indexed_change;
|
||||
if new_indexed_value.is_negative() {
|
||||
// pay back borrows only, leaving a negative position
|
||||
cm!(self.indexed_borrows -= indexed_change);
|
||||
self.indexed_borrows -= indexed_change;
|
||||
position.indexed_position = new_indexed_value;
|
||||
return Ok(true);
|
||||
} else if new_native_position < I80F48::ONE && allow_dusting {
|
||||
// if there's less than one token deposited, zero the position
|
||||
cm!(self.dust += new_native_position);
|
||||
cm!(self.indexed_borrows += position.indexed_position);
|
||||
self.dust += new_native_position;
|
||||
self.indexed_borrows += position.indexed_position;
|
||||
position.indexed_position = I80F48::ZERO;
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// pay back all borrows
|
||||
cm!(self.indexed_borrows += position.indexed_position); // position.value is negative
|
||||
self.indexed_borrows += position.indexed_position; // position.value is negative
|
||||
position.indexed_position = I80F48::ZERO;
|
||||
// deposit the rest
|
||||
// note: .max(0) because there's a scenario where new_indexed_value == 0 and new_native_position < 0
|
||||
|
@ -364,8 +363,8 @@ impl Bank {
|
|||
|
||||
// add to deposits
|
||||
let indexed_change = div_rounding_up(native_amount, self.deposit_index);
|
||||
cm!(self.indexed_deposits += indexed_change);
|
||||
cm!(position.indexed_position += indexed_change);
|
||||
self.indexed_deposits += indexed_change;
|
||||
position.indexed_position += indexed_change;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
@ -480,26 +479,26 @@ impl Bank {
|
|||
let native_position = position.native(self);
|
||||
|
||||
if native_position.is_positive() {
|
||||
let new_native_position = cm!(native_position - native_amount);
|
||||
let new_native_position = native_position - native_amount;
|
||||
if !new_native_position.is_negative() {
|
||||
// withdraw deposits only
|
||||
if new_native_position < I80F48::ONE && allow_dusting {
|
||||
// zero the account collecting the leftovers in `dust`
|
||||
cm!(self.dust += new_native_position);
|
||||
cm!(self.indexed_deposits -= position.indexed_position);
|
||||
self.dust += new_native_position;
|
||||
self.indexed_deposits -= position.indexed_position;
|
||||
position.indexed_position = I80F48::ZERO;
|
||||
return Ok((false, I80F48::ZERO));
|
||||
} else {
|
||||
// withdraw some deposits leaving a positive balance
|
||||
let indexed_change = cm!(native_amount / self.deposit_index);
|
||||
cm!(self.indexed_deposits -= indexed_change);
|
||||
cm!(position.indexed_position -= indexed_change);
|
||||
let indexed_change = native_amount / self.deposit_index;
|
||||
self.indexed_deposits -= indexed_change;
|
||||
position.indexed_position -= indexed_change;
|
||||
return Ok((true, I80F48::ZERO));
|
||||
}
|
||||
}
|
||||
|
||||
// withdraw all deposits
|
||||
cm!(self.indexed_deposits -= position.indexed_position);
|
||||
self.indexed_deposits -= position.indexed_position;
|
||||
position.indexed_position = I80F48::ZERO;
|
||||
// borrow the rest
|
||||
native_amount = -new_native_position;
|
||||
|
@ -507,15 +506,15 @@ impl Bank {
|
|||
|
||||
let mut loan_origination_fee = I80F48::ZERO;
|
||||
if with_loan_origination_fee {
|
||||
loan_origination_fee = cm!(self.loan_origination_fee_rate * native_amount);
|
||||
cm!(self.collected_fees_native += loan_origination_fee);
|
||||
cm!(native_amount += loan_origination_fee);
|
||||
loan_origination_fee = self.loan_origination_fee_rate * native_amount;
|
||||
self.collected_fees_native += loan_origination_fee;
|
||||
native_amount += loan_origination_fee;
|
||||
}
|
||||
|
||||
// add to borrows
|
||||
let indexed_change = cm!(native_amount / self.borrow_index);
|
||||
cm!(self.indexed_borrows += indexed_change);
|
||||
cm!(position.indexed_position -= indexed_change);
|
||||
let indexed_change = native_amount / self.borrow_index;
|
||||
self.indexed_borrows += indexed_change;
|
||||
position.indexed_position -= indexed_change;
|
||||
|
||||
// net borrows requires updating in only this case, since other branches of the method deal with
|
||||
// withdraws and not borrows
|
||||
|
@ -534,9 +533,8 @@ impl Bank {
|
|||
already_borrowed_native_amount: I80F48,
|
||||
now_ts: u64,
|
||||
) -> Result<(bool, I80F48)> {
|
||||
let loan_origination_fee =
|
||||
cm!(self.loan_origination_fee_rate * already_borrowed_native_amount);
|
||||
cm!(self.collected_fees_native += loan_origination_fee);
|
||||
let loan_origination_fee = self.loan_origination_fee_rate * already_borrowed_native_amount;
|
||||
self.collected_fees_native += loan_origination_fee;
|
||||
|
||||
let (position_is_active, _) = self.withdraw_internal_wrapper(
|
||||
position,
|
||||
|
@ -591,9 +589,9 @@ impl Bank {
|
|||
// reset to latest window
|
||||
self.last_net_borrows_window_start_ts = now_ts / self.net_borrow_limit_window_size_ts
|
||||
* self.net_borrow_limit_window_size_ts;
|
||||
native_amount.checked_to_num::<i64>().unwrap()
|
||||
native_amount.to_num::<i64>()
|
||||
} else {
|
||||
cm!(self.net_borrows_in_window + native_amount.checked_to_num().unwrap())
|
||||
self.net_borrows_in_window + native_amount.to_num::<i64>()
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -623,14 +621,14 @@ impl Bank {
|
|||
opening_indexed_position: I80F48,
|
||||
) {
|
||||
if opening_indexed_position.is_positive() {
|
||||
let interest =
|
||||
cm!((self.deposit_index - position.previous_index) * opening_indexed_position)
|
||||
.to_num::<f64>();
|
||||
let interest = ((self.deposit_index - position.previous_index)
|
||||
* opening_indexed_position)
|
||||
.to_num::<f64>();
|
||||
position.cumulative_deposit_interest += interest;
|
||||
} else {
|
||||
let interest =
|
||||
cm!((self.borrow_index - position.previous_index) * opening_indexed_position)
|
||||
.to_num::<f64>();
|
||||
let interest = ((self.borrow_index - position.previous_index)
|
||||
* opening_indexed_position)
|
||||
.to_num::<f64>();
|
||||
position.cumulative_borrow_interest -= interest;
|
||||
}
|
||||
|
||||
|
@ -648,14 +646,14 @@ impl Bank {
|
|||
diff_ts: I80F48,
|
||||
) -> Result<(I80F48, I80F48, I80F48, I80F48, I80F48)> {
|
||||
// compute index based on utilization
|
||||
let native_total_deposits = cm!(self.deposit_index * indexed_total_deposits);
|
||||
let native_total_borrows = cm!(self.borrow_index * indexed_total_borrows);
|
||||
let native_total_deposits = self.deposit_index * indexed_total_deposits;
|
||||
let native_total_borrows = self.borrow_index * indexed_total_borrows;
|
||||
|
||||
// This will be >= 0, but can also be > 1
|
||||
let instantaneous_utilization = if native_total_deposits == I80F48::ZERO {
|
||||
I80F48::ZERO
|
||||
} else {
|
||||
cm!(native_total_borrows / native_total_deposits)
|
||||
native_total_borrows / native_total_deposits
|
||||
};
|
||||
|
||||
let borrow_rate = self.compute_interest_rate(instantaneous_utilization);
|
||||
|
@ -670,17 +668,16 @@ impl Bank {
|
|||
// we have
|
||||
// deposit_rate = borrow_rate * (old_borrow_index * indexed_borrows) / (old_deposit_index * indexed_deposits)
|
||||
// and the latter factor is exactly instantaneous_utilization.
|
||||
let deposit_rate = cm!(borrow_rate * instantaneous_utilization);
|
||||
let deposit_rate = borrow_rate * instantaneous_utilization;
|
||||
|
||||
// The loan fee rate is not distributed to depositors.
|
||||
let borrow_rate_with_fees = cm!(borrow_rate + self.loan_fee_rate);
|
||||
let borrow_fees = cm!(native_total_borrows * self.loan_fee_rate * diff_ts / YEAR_I80F48);
|
||||
let borrow_rate_with_fees = borrow_rate + self.loan_fee_rate;
|
||||
let borrow_fees = native_total_borrows * self.loan_fee_rate * diff_ts / YEAR_I80F48;
|
||||
|
||||
let borrow_index = cm!(
|
||||
(self.borrow_index * borrow_rate_with_fees * diff_ts) / YEAR_I80F48 + self.borrow_index
|
||||
);
|
||||
let borrow_index =
|
||||
(self.borrow_index * borrow_rate_with_fees * diff_ts) / YEAR_I80F48 + self.borrow_index;
|
||||
let deposit_index =
|
||||
cm!((self.deposit_index * deposit_rate * diff_ts) / YEAR_I80F48 + self.deposit_index);
|
||||
(self.deposit_index * deposit_rate * diff_ts) / YEAR_I80F48 + self.deposit_index;
|
||||
|
||||
Ok((
|
||||
deposit_index,
|
||||
|
@ -716,16 +713,16 @@ impl Bank {
|
|||
max_rate: I80F48,
|
||||
) -> I80F48 {
|
||||
if utilization <= util0 {
|
||||
let slope = cm!(rate0 / util0);
|
||||
cm!(slope * utilization)
|
||||
let slope = rate0 / util0;
|
||||
slope * utilization
|
||||
} else if utilization <= util1 {
|
||||
let extra_util = cm!(utilization - util0);
|
||||
let slope = cm!((rate1 - rate0) / (util1 - util0));
|
||||
cm!(rate0 + slope * extra_util)
|
||||
let extra_util = utilization - util0;
|
||||
let slope = (rate1 - rate0) / (util1 - util0);
|
||||
rate0 + slope * extra_util
|
||||
} else {
|
||||
let extra_util = cm!(utilization - util1);
|
||||
let slope = cm!((max_rate - rate1) / (I80F48::ONE - util1));
|
||||
cm!(rate1 + slope * extra_util)
|
||||
let extra_util = utilization - util1;
|
||||
let slope = (max_rate - rate1) / (I80F48::ONE - util1);
|
||||
rate1 + slope * extra_util
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -740,26 +737,24 @@ impl Bank {
|
|||
return I80F48::ZERO;
|
||||
}
|
||||
|
||||
let native_total_deposits = cm!(self.deposit_index * indexed_total_deposits);
|
||||
let native_total_borrows = cm!(self.borrow_index * indexed_total_borrows);
|
||||
let native_total_deposits = self.deposit_index * indexed_total_deposits;
|
||||
let native_total_borrows = self.borrow_index * indexed_total_borrows;
|
||||
let instantaneous_utilization = if native_total_deposits == I80F48::ZERO {
|
||||
I80F48::ZERO
|
||||
} else {
|
||||
cm!(native_total_borrows / native_total_deposits)
|
||||
native_total_borrows / native_total_deposits
|
||||
};
|
||||
|
||||
// Compute a time-weighted average since bank_rate_last_updated.
|
||||
let previous_avg_time =
|
||||
I80F48::from_num(cm!(self.index_last_updated - self.bank_rate_last_updated));
|
||||
let diff_ts = I80F48::from_num(cm!(now_ts - self.index_last_updated));
|
||||
let new_avg_time = I80F48::from_num(cm!(now_ts - self.bank_rate_last_updated));
|
||||
I80F48::from_num(self.index_last_updated - self.bank_rate_last_updated);
|
||||
let diff_ts = I80F48::from_num(now_ts - self.index_last_updated);
|
||||
let new_avg_time = I80F48::from_num(now_ts - self.bank_rate_last_updated);
|
||||
if new_avg_time <= 0 {
|
||||
return instantaneous_utilization;
|
||||
}
|
||||
cm!(
|
||||
(self.avg_utilization * previous_avg_time + instantaneous_utilization * diff_ts)
|
||||
/ new_avg_time
|
||||
)
|
||||
(self.avg_utilization * previous_avg_time + instantaneous_utilization * diff_ts)
|
||||
/ new_avg_time
|
||||
}
|
||||
|
||||
// computes new optimal rates and max rate
|
||||
|
@ -772,21 +767,21 @@ impl Bank {
|
|||
// move rates up when utilization is above optimal utilization, and vice versa
|
||||
// util factor is between -1 (avg util = 0) and +1 (avg util = 100%)
|
||||
let util_factor = if avg_util > optimal_util {
|
||||
cm!((avg_util - optimal_util) / (I80F48::ONE - optimal_util))
|
||||
(avg_util - optimal_util) / (I80F48::ONE - optimal_util)
|
||||
} else {
|
||||
cm!((avg_util - optimal_util) / optimal_util)
|
||||
(avg_util - optimal_util) / optimal_util
|
||||
};
|
||||
let adjustment = cm!(I80F48::ONE + self.adjustment_factor * util_factor);
|
||||
let adjustment = I80F48::ONE + self.adjustment_factor * util_factor;
|
||||
|
||||
// 1. irrespective of which leg current utilization is in, update all rates
|
||||
// 2. only update rates as long as new adjusted rates are above MINIMUM_MAX_RATE,
|
||||
// since we don't want to fall to such low rates that it would take a long time to
|
||||
// recover to high rates if utilization suddently increases to a high value
|
||||
if cm!(self.max_rate * adjustment) > MINIMUM_MAX_RATE {
|
||||
if (self.max_rate * adjustment) > MINIMUM_MAX_RATE {
|
||||
(
|
||||
cm!(self.rate0 * adjustment),
|
||||
cm!(self.rate1 * adjustment),
|
||||
cm!(self.max_rate * adjustment),
|
||||
(self.rate0 * adjustment),
|
||||
(self.rate1 * adjustment),
|
||||
(self.max_rate * adjustment),
|
||||
)
|
||||
} else {
|
||||
(self.rate0, self.rate1, self.max_rate)
|
||||
|
@ -828,7 +823,7 @@ impl Bank {
|
|||
} else {
|
||||
// The next line is around 500 CU
|
||||
let scale = self.deposit_weight_scale_start_quote / deposits_quote;
|
||||
cm!(self.init_asset_weight * I80F48::from_num(scale))
|
||||
self.init_asset_weight * I80F48::from_num(scale)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -848,7 +843,7 @@ impl Bank {
|
|||
} else {
|
||||
// The next line is around 500 CU
|
||||
let scale = borrows_quote / self.borrow_weight_scale_start_quote;
|
||||
cm!(self.init_liab_weight * I80F48::from_num(scale))
|
||||
self.init_liab_weight * I80F48::from_num(scale)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
use checked_math as cm;
|
||||
|
||||
use fixed::types::I80F48;
|
||||
|
||||
use crate::events::{Equity, TokenEquity};
|
||||
|
@ -35,7 +35,7 @@ pub fn compute_equity(
|
|||
.get(&serum_account.base_token_index)
|
||||
.unwrap_or(&I80F48::ZERO);
|
||||
let native_coin_total_i80f48 = I80F48::from_num(oo.native_coin_total);
|
||||
let new_equity = cm!(accumulated_equity + native_coin_total_i80f48 * oracle_price);
|
||||
let new_equity = accumulated_equity + native_coin_total_i80f48 * oracle_price;
|
||||
token_equity_map.insert(serum_account.base_token_index, new_equity);
|
||||
|
||||
// note quote token value
|
||||
|
@ -45,7 +45,7 @@ pub fn compute_equity(
|
|||
.get(&serum_account.quote_token_index)
|
||||
.unwrap_or(&I80F48::ZERO);
|
||||
let native_pc_total_i80f48 = I80F48::from_num(oo.native_pc_total);
|
||||
let new_equity = cm!(accumulated_equity + native_pc_total_i80f48 * oracle_price);
|
||||
let new_equity = accumulated_equity + native_pc_total_i80f48 * oracle_price;
|
||||
token_equity_map.insert(serum_account.quote_token_index, new_equity);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ use static_assertions::const_assert_eq;
|
|||
use crate::error::*;
|
||||
use crate::health::{HealthCache, HealthType};
|
||||
use crate::logs::{DeactivatePerpPositionLog, DeactivateTokenPositionLog};
|
||||
use checked_math as cm;
|
||||
|
||||
use super::dynamic_account::*;
|
||||
use super::BookSideOrderTree;
|
||||
|
@ -780,7 +779,7 @@ impl<
|
|||
perp_position.market_index = perp_market_index;
|
||||
|
||||
let mut settle_token_position = self.ensure_token_position(settle_token_index)?.0;
|
||||
cm!(settle_token_position.in_use_count += 1);
|
||||
settle_token_position.in_use_count += 1;
|
||||
}
|
||||
}
|
||||
if let Some(raw_index) = raw_index_opt {
|
||||
|
@ -798,7 +797,7 @@ impl<
|
|||
self.perp_position_mut(perp_market_index)?.market_index = PerpMarketIndex::MAX;
|
||||
|
||||
let mut settle_token_position = self.token_position_mut(settle_token_index)?.0;
|
||||
cm!(settle_token_position.in_use_count -= 1);
|
||||
settle_token_position.in_use_count -= 1;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -826,7 +825,7 @@ impl<
|
|||
perp_position.market_index = PerpMarketIndex::MAX;
|
||||
|
||||
let mut settle_token_position = self.token_position_mut(settle_token_index)?.0;
|
||||
cm!(settle_token_position.in_use_count -= 1);
|
||||
settle_token_position.in_use_count -= 1;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -842,10 +841,10 @@ impl<
|
|||
let mut perp_account = self.perp_position_mut(perp_market_index)?;
|
||||
match side {
|
||||
Side::Bid => {
|
||||
cm!(perp_account.bids_base_lots += order.quantity);
|
||||
perp_account.bids_base_lots += order.quantity;
|
||||
}
|
||||
Side::Ask => {
|
||||
cm!(perp_account.asks_base_lots += order.quantity);
|
||||
perp_account.asks_base_lots += order.quantity;
|
||||
}
|
||||
};
|
||||
let slot = order.owner_slot as usize;
|
||||
|
@ -869,10 +868,10 @@ impl<
|
|||
// accounting
|
||||
match order_side {
|
||||
Side::Bid => {
|
||||
cm!(perp_account.bids_base_lots -= quantity);
|
||||
perp_account.bids_base_lots -= quantity;
|
||||
}
|
||||
Side::Ask => {
|
||||
cm!(perp_account.asks_base_lots -= quantity);
|
||||
perp_account.asks_base_lots -= quantity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -897,22 +896,22 @@ impl<
|
|||
|
||||
let side = fill.taker_side().invert_side();
|
||||
let (base_change, quote_change) = fill.base_quote_change(side);
|
||||
let quote = cm!(I80F48::from(perp_market.quote_lot_size) * I80F48::from(quote_change));
|
||||
let fees = cm!(quote.abs() * I80F48::from_num(fill.maker_fee));
|
||||
let quote = I80F48::from(perp_market.quote_lot_size) * I80F48::from(quote_change);
|
||||
let fees = quote.abs() * I80F48::from_num(fill.maker_fee);
|
||||
pa.record_trading_fee(fees);
|
||||
pa.record_trade(perp_market, base_change, quote);
|
||||
|
||||
cm!(pa.maker_volume += quote.abs().to_num::<u64>());
|
||||
pa.maker_volume += quote.abs().to_num::<u64>();
|
||||
|
||||
if fill.maker_out() {
|
||||
self.remove_perp_order(fill.maker_slot as usize, base_change.abs())
|
||||
} else {
|
||||
match side {
|
||||
Side::Bid => {
|
||||
cm!(pa.bids_base_lots -= base_change.abs());
|
||||
pa.bids_base_lots -= base_change.abs();
|
||||
}
|
||||
Side::Ask => {
|
||||
cm!(pa.asks_base_lots -= base_change.abs());
|
||||
pa.asks_base_lots -= base_change.abs();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -932,10 +931,10 @@ impl<
|
|||
pa.remove_taker_trade(base_change, quote_change);
|
||||
// fees are assessed at time of trade; no need to assess fees here
|
||||
let quote_change_native =
|
||||
cm!(I80F48::from(perp_market.quote_lot_size) * I80F48::from(quote_change));
|
||||
I80F48::from(perp_market.quote_lot_size) * I80F48::from(quote_change);
|
||||
pa.record_trade(perp_market, base_change, quote_change_native);
|
||||
|
||||
cm!(pa.taker_volume += quote_change_native.abs().to_num::<u64>());
|
||||
pa.taker_volume += quote_change_native.abs().to_num::<u64>();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use checked_math as cm;
|
||||
|
||||
use derivative::Derivative;
|
||||
use fixed::types::I80F48;
|
||||
use static_assertions::const_assert_eq;
|
||||
|
@ -295,19 +295,19 @@ impl PerpPosition {
|
|||
pub fn add_taker_trade(&mut self, side: Side, base_lots: i64, quote_lots: i64) {
|
||||
match side {
|
||||
Side::Bid => {
|
||||
cm!(self.taker_base_lots += base_lots);
|
||||
cm!(self.taker_quote_lots -= quote_lots);
|
||||
self.taker_base_lots += base_lots;
|
||||
self.taker_quote_lots -= quote_lots;
|
||||
}
|
||||
Side::Ask => {
|
||||
cm!(self.taker_base_lots -= base_lots);
|
||||
cm!(self.taker_quote_lots += quote_lots);
|
||||
self.taker_base_lots -= base_lots;
|
||||
self.taker_quote_lots += quote_lots;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Remove taker trade after it has been processed on EventQueue
|
||||
pub fn remove_taker_trade(&mut self, base_change: i64, quote_change: i64) {
|
||||
cm!(self.taker_base_lots -= base_change);
|
||||
cm!(self.taker_quote_lots -= quote_change);
|
||||
self.taker_base_lots -= base_change;
|
||||
self.taker_quote_lots -= quote_change;
|
||||
}
|
||||
|
||||
pub fn is_active(&self) -> bool {
|
||||
|
@ -320,7 +320,7 @@ impl PerpPosition {
|
|||
|
||||
// Return base position in native units for a perp market
|
||||
pub fn base_position_native(&self, market: &PerpMarket) -> I80F48 {
|
||||
I80F48::from(cm!(self.base_position_lots * market.base_lot_size))
|
||||
I80F48::from(self.base_position_lots * market.base_lot_size)
|
||||
}
|
||||
|
||||
pub fn base_position_lots(&self) -> i64 {
|
||||
|
@ -347,12 +347,12 @@ impl PerpPosition {
|
|||
pub fn unsettled_funding(&self, perp_market: &PerpMarket) -> I80F48 {
|
||||
match self.base_position_lots.cmp(&0) {
|
||||
Ordering::Greater => {
|
||||
cm!((perp_market.long_funding - self.long_settled_funding)
|
||||
* I80F48::from_num(self.base_position_lots))
|
||||
(perp_market.long_funding - self.long_settled_funding)
|
||||
* I80F48::from_num(self.base_position_lots)
|
||||
}
|
||||
Ordering::Less => {
|
||||
cm!((perp_market.short_funding - self.short_settled_funding)
|
||||
* I80F48::from_num(self.base_position_lots))
|
||||
(perp_market.short_funding - self.short_settled_funding)
|
||||
* I80F48::from_num(self.base_position_lots)
|
||||
}
|
||||
Ordering::Equal => I80F48::ZERO,
|
||||
}
|
||||
|
@ -361,9 +361,9 @@ impl PerpPosition {
|
|||
/// Move unrealized funding payments into the quote_position
|
||||
pub fn settle_funding(&mut self, perp_market: &PerpMarket) {
|
||||
let funding = self.unsettled_funding(perp_market);
|
||||
cm!(self.quote_position_native -= funding);
|
||||
cm!(self.realized_other_pnl_native -= funding);
|
||||
cm!(self.realized_pnl_for_position_native -= funding);
|
||||
self.quote_position_native -= funding;
|
||||
self.realized_other_pnl_native -= funding;
|
||||
self.realized_pnl_for_position_native -= funding;
|
||||
|
||||
if self.base_position_lots.is_positive() {
|
||||
self.cumulative_long_funding += funding.to_num::<f64>();
|
||||
|
@ -387,7 +387,7 @@ impl PerpPosition {
|
|||
}
|
||||
|
||||
let old_position = self.base_position_lots;
|
||||
let new_position = cm!(old_position + base_change);
|
||||
let new_position = old_position + base_change;
|
||||
|
||||
// amount of lots that were reduced (so going from -5 to 10 lots is a reduction of 5)
|
||||
let reduced_lots;
|
||||
|
@ -404,9 +404,9 @@ impl PerpPosition {
|
|||
|
||||
// There can't be unrealized pnl without a base position, so fix the
|
||||
// realized_trade_pnl to cover everything that isn't realized_other_pnl.
|
||||
let total_realized_pnl = cm!(self.quote_position_native + quote_change_native);
|
||||
let new_realized_trade_pnl = cm!(total_realized_pnl - self.realized_other_pnl_native);
|
||||
newly_realized_pnl = cm!(new_realized_trade_pnl - self.realized_trade_pnl_native);
|
||||
let total_realized_pnl = self.quote_position_native + quote_change_native;
|
||||
let new_realized_trade_pnl = total_realized_pnl - self.realized_other_pnl_native;
|
||||
newly_realized_pnl = new_realized_trade_pnl - self.realized_trade_pnl_native;
|
||||
self.realized_trade_pnl_native = new_realized_trade_pnl;
|
||||
} else if old_position.signum() != new_position.signum() {
|
||||
// If the base position changes sign, we've crossed base_pos == 0 (or old_position == 0)
|
||||
|
@ -419,7 +419,7 @@ impl PerpPosition {
|
|||
|
||||
// Award realized pnl based on the old_position size
|
||||
newly_realized_pnl = I80F48::from_num(old_position * (new_avg_entry - old_avg_entry));
|
||||
cm!(self.realized_trade_pnl_native += newly_realized_pnl);
|
||||
self.realized_trade_pnl_native += newly_realized_pnl;
|
||||
|
||||
// Set entry and break-even based on the new_position entered
|
||||
self.avg_entry_price_per_base_lot = new_avg_entry;
|
||||
|
@ -430,10 +430,7 @@ impl PerpPosition {
|
|||
} else {
|
||||
// The old and new position have the same sign
|
||||
|
||||
cm!(self.quote_running_native += quote_change_native
|
||||
.round_to_zero()
|
||||
.checked_to_num::<i64>()
|
||||
.unwrap());
|
||||
self.quote_running_native += quote_change_native.round_to_zero().to_num::<i64>();
|
||||
|
||||
let is_increasing = old_position.signum() == base_change.signum();
|
||||
if is_increasing {
|
||||
|
@ -450,10 +447,9 @@ impl PerpPosition {
|
|||
// Decreasing position: pnl is realized, avg entry price does not change
|
||||
reduced_lots = base_change;
|
||||
let avg_entry = I80F48::from_num(self.avg_entry_price_per_base_lot);
|
||||
newly_realized_pnl =
|
||||
cm!(quote_change_native + I80F48::from(base_change) * avg_entry);
|
||||
cm!(self.realized_trade_pnl_native += newly_realized_pnl);
|
||||
cm!(self.realized_pnl_for_position_native += newly_realized_pnl);
|
||||
newly_realized_pnl = quote_change_native + I80F48::from(base_change) * avg_entry;
|
||||
self.realized_trade_pnl_native += newly_realized_pnl;
|
||||
self.realized_pnl_for_position_native += newly_realized_pnl;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -468,10 +464,10 @@ impl PerpPosition {
|
|||
// magnitude.
|
||||
if newly_realized_pnl.signum() == self.realized_trade_pnl_native.signum() {
|
||||
let realized_stable_value =
|
||||
cm!(I80F48::from(reduced_lots.abs() * perp_market.base_lot_size)
|
||||
* perp_market.stable_price());
|
||||
I80F48::from(reduced_lots.abs() * perp_market.base_lot_size)
|
||||
* perp_market.stable_price();
|
||||
let stable_value_fraction =
|
||||
cm!(I80F48::from_num(perp_market.settle_pnl_limit_factor) * realized_stable_value);
|
||||
I80F48::from_num(perp_market.settle_pnl_limit_factor) * realized_stable_value;
|
||||
|
||||
// The realized pnl settle limit change is restricted to actually realized pnl:
|
||||
// buying and then selling some base lots at the same price shouldn't affect
|
||||
|
@ -487,7 +483,7 @@ impl PerpPosition {
|
|||
.floor()
|
||||
.clamp_to_i64()
|
||||
};
|
||||
cm!(self.settle_pnl_limit_realized_trade += limit_change);
|
||||
self.settle_pnl_limit_realized_trade += limit_change;
|
||||
}
|
||||
|
||||
// Ensure the realized limit doesn't exceed the realized pnl
|
||||
|
@ -555,7 +551,7 @@ impl PerpPosition {
|
|||
limit_change.max(realized_pnl_change).min(0)
|
||||
};
|
||||
|
||||
cm!(self.settle_pnl_limit_settled_in_current_window_native += used_change);
|
||||
self.settle_pnl_limit_settled_in_current_window_native += used_change;
|
||||
}
|
||||
|
||||
/// Change the base and quote positions as the result of a trade
|
||||
|
@ -572,7 +568,7 @@ impl PerpPosition {
|
|||
}
|
||||
|
||||
fn change_quote_position(&mut self, quote_change_native: I80F48) {
|
||||
cm!(self.quote_position_native += quote_change_native);
|
||||
self.quote_position_native += quote_change_native;
|
||||
}
|
||||
|
||||
/// Does the user have any orders on the book?
|
||||
|
@ -606,14 +602,14 @@ impl PerpPosition {
|
|||
}
|
||||
assert_eq!(self.market_index, market.perp_market_index);
|
||||
-(self.quote_running_native as f64)
|
||||
/ (cm!(self.base_position_lots * market.base_lot_size) as f64)
|
||||
/ ((self.base_position_lots * market.base_lot_size) as f64)
|
||||
}
|
||||
|
||||
/// Calculate the PnL of the position for a given price
|
||||
pub fn unsettled_pnl(&self, perp_market: &PerpMarket, price: I80F48) -> Result<I80F48> {
|
||||
require_eq!(self.market_index, perp_market.perp_market_index);
|
||||
let base_native = self.base_position_native(perp_market);
|
||||
let pnl = cm!(self.quote_position_native() + base_native * price);
|
||||
let pnl = self.quote_position_native() + base_native * price;
|
||||
Ok(pnl)
|
||||
}
|
||||
|
||||
|
@ -622,12 +618,12 @@ impl PerpPosition {
|
|||
pub fn update_settle_limit(&mut self, market: &PerpMarket, now_ts: u64) {
|
||||
assert_eq!(self.market_index, market.perp_market_index);
|
||||
let window_size = market.settle_pnl_limit_window_size_ts;
|
||||
let window_start = cm!(self.settle_pnl_limit_window as u64 * window_size);
|
||||
let window_end = cm!(window_start + window_size);
|
||||
let window_start = self.settle_pnl_limit_window as u64 * window_size;
|
||||
let window_end = window_start + window_size;
|
||||
// now_ts < window_start can happen when window size is changed on the market
|
||||
let new_window = now_ts >= window_end || now_ts < window_start;
|
||||
if new_window {
|
||||
self.settle_pnl_limit_window = cm!(now_ts / window_size).try_into().unwrap();
|
||||
self.settle_pnl_limit_window = (now_ts / window_size).try_into().unwrap();
|
||||
self.settle_pnl_limit_settled_in_current_window_native = 0;
|
||||
}
|
||||
}
|
||||
|
@ -648,9 +644,7 @@ impl PerpPosition {
|
|||
}
|
||||
|
||||
let base_native = self.base_position_native(market);
|
||||
let position_value = cm!(market.stable_price() * base_native)
|
||||
.abs()
|
||||
.to_num::<f64>();
|
||||
let position_value = (market.stable_price() * base_native).abs().to_num::<f64>();
|
||||
let unrealized = (market.settle_pnl_limit_factor as f64 * position_value).clamp_to_i64();
|
||||
|
||||
let mut min_pnl = -unrealized;
|
||||
|
@ -725,8 +719,8 @@ impl PerpPosition {
|
|||
.max(self.realized_other_pnl_native)
|
||||
.min(I80F48::ZERO)
|
||||
};
|
||||
cm!(self.realized_other_pnl_native -= other_reduction);
|
||||
let trade_and_unrealized_settlement = cm!(settled_pnl - other_reduction);
|
||||
self.realized_other_pnl_native -= other_reduction;
|
||||
let trade_and_unrealized_settlement = settled_pnl - other_reduction;
|
||||
|
||||
// Then reduces realized_trade_pnl, similar to other_pnl above.
|
||||
let trade_reduction = if trade_and_unrealized_settlement > 0 {
|
||||
|
@ -738,14 +732,14 @@ impl PerpPosition {
|
|||
.max(self.realized_trade_pnl_native)
|
||||
.min(I80F48::ZERO)
|
||||
};
|
||||
cm!(self.realized_trade_pnl_native -= trade_reduction);
|
||||
self.realized_trade_pnl_native -= trade_reduction;
|
||||
|
||||
// Consume settle limit budget: We don't track consumption of realized_other_pnl
|
||||
// because settling it directly reduces its budget as well.
|
||||
let settled_pnl_i64 = trade_and_unrealized_settlement
|
||||
.round_to_zero()
|
||||
.clamp_to_i64();
|
||||
cm!(self.settle_pnl_limit_settled_in_current_window_native += settled_pnl_i64);
|
||||
self.settle_pnl_limit_settled_in_current_window_native += settled_pnl_i64;
|
||||
|
||||
self.apply_realized_trade_pnl_settle_limit_constraint(-trade_reduction)
|
||||
}
|
||||
|
@ -753,20 +747,20 @@ impl PerpPosition {
|
|||
/// Update perp position for a maker/taker fee payment
|
||||
pub fn record_trading_fee(&mut self, fee: I80F48) {
|
||||
self.change_quote_position(-fee);
|
||||
cm!(self.realized_other_pnl_native -= fee);
|
||||
cm!(self.realized_pnl_for_position_native -= fee);
|
||||
self.realized_other_pnl_native -= fee;
|
||||
self.realized_pnl_for_position_native -= fee;
|
||||
}
|
||||
|
||||
/// Adds immediately-settleable realized pnl when a liqor takes over pnl during liquidation
|
||||
pub fn record_liquidation_quote_change(&mut self, change: I80F48) {
|
||||
self.change_quote_position(change);
|
||||
cm!(self.realized_other_pnl_native += change);
|
||||
self.realized_other_pnl_native += change;
|
||||
}
|
||||
|
||||
/// Adds to the quote position and adds a recurring ("realized trade") settle limit
|
||||
pub fn record_liquidation_pnl_takeover(&mut self, change: I80F48, recurring_limit: I80F48) {
|
||||
self.change_quote_position(change);
|
||||
cm!(self.realized_trade_pnl_native += recurring_limit);
|
||||
self.realized_trade_pnl_native += recurring_limit;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ use switchboard_program::FastRoundResultAccountData;
|
|||
use switchboard_v2::AggregatorAccountData;
|
||||
|
||||
use crate::accounts_zerocopy::*;
|
||||
use crate::checked_math as cm;
|
||||
|
||||
use crate::error::*;
|
||||
|
||||
const DECIMAL_CONSTANT_ZERO_INDEX: i8 = 12;
|
||||
|
@ -153,7 +153,7 @@ pub fn oracle_price(
|
|||
let price = I80F48::from_num(price_data.price);
|
||||
|
||||
// Filter out bad prices
|
||||
if I80F48::from_num(price_data.conf) > cm!(config.conf_filter * price) {
|
||||
if I80F48::from_num(price_data.conf) > (config.conf_filter * price) {
|
||||
msg!(
|
||||
"Pyth conf interval too high; pubkey {} price: {} price_data.conf: {}",
|
||||
acc_info.key(),
|
||||
|
@ -185,9 +185,9 @@ pub fn oracle_price(
|
|||
return Err(MangoError::OracleStale.into());
|
||||
}
|
||||
|
||||
let decimals = cm!((price_account.expo as i8) + QUOTE_DECIMALS - (base_decimals as i8));
|
||||
let decimals = (price_account.expo as i8) + QUOTE_DECIMALS - (base_decimals as i8);
|
||||
let decimal_adj = power_of_ten(decimals);
|
||||
cm!(price * decimal_adj)
|
||||
price * decimal_adj
|
||||
}
|
||||
OracleType::SwitchboardV2 => {
|
||||
fn from_foreign_error(e: impl std::fmt::Display) -> Error {
|
||||
|
@ -205,7 +205,7 @@ pub fn oracle_price(
|
|||
.std_deviation
|
||||
.try_into()
|
||||
.map_err(from_foreign_error)?;
|
||||
if I80F48::from_num(std_deviation_decimal) > cm!(config.conf_filter * price) {
|
||||
if I80F48::from_num(std_deviation_decimal) > (config.conf_filter * price) {
|
||||
msg!(
|
||||
"Switchboard v2 std deviation too high; pubkey {} price: {} latest_confirmed_round.std_deviation: {}",
|
||||
acc_info.key(),
|
||||
|
@ -231,9 +231,9 @@ pub fn oracle_price(
|
|||
return Err(MangoError::OracleConfidence.into());
|
||||
}
|
||||
|
||||
let decimals = cm!(QUOTE_DECIMALS - (base_decimals as i8));
|
||||
let decimals = QUOTE_DECIMALS - (base_decimals as i8);
|
||||
let decimal_adj = power_of_ten(decimals);
|
||||
cm!(price * decimal_adj)
|
||||
price * decimal_adj
|
||||
}
|
||||
OracleType::SwitchboardV1 => {
|
||||
let result = FastRoundResultAccountData::deserialize(data).unwrap();
|
||||
|
@ -242,7 +242,7 @@ pub fn oracle_price(
|
|||
// Filter out bad prices
|
||||
let min_response = I80F48::from_num(result.result.min_response);
|
||||
let max_response = I80F48::from_num(result.result.max_response);
|
||||
if cm!(max_response - min_response) > cm!(config.conf_filter * price) {
|
||||
if (max_response - min_response) > (config.conf_filter * price) {
|
||||
msg!(
|
||||
"Switchboard v1 min-max response gap too wide; pubkey {} price: {} min_response: {} max_response {}",
|
||||
acc_info.key(),
|
||||
|
@ -267,9 +267,9 @@ pub fn oracle_price(
|
|||
return Err(MangoError::OracleConfidence.into());
|
||||
}
|
||||
|
||||
let decimals = cm!(QUOTE_DECIMALS - (base_decimals as i8));
|
||||
let decimals = QUOTE_DECIMALS - (base_decimals as i8);
|
||||
let decimal_adj = power_of_ten(decimals);
|
||||
cm!(price * decimal_adj)
|
||||
price * decimal_adj
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ use fixed::types::I80F48;
|
|||
use std::cell::RefMut;
|
||||
|
||||
use super::*;
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
/// Drop at most this many expired orders from a BookSide when trying to match orders.
|
||||
/// This exists as a guard against excessive compute use.
|
||||
|
@ -123,12 +122,12 @@ impl<'a> Orderbook<'a> {
|
|||
.min(best_opposing.node.quantity)
|
||||
.min(max_match_by_quote);
|
||||
|
||||
let match_quote_lots = cm!(match_base_lots * best_opposing_price);
|
||||
cm!(remaining_base_lots -= match_base_lots);
|
||||
cm!(remaining_quote_lots -= match_quote_lots);
|
||||
let match_quote_lots = match_base_lots * best_opposing_price;
|
||||
remaining_base_lots -= match_base_lots;
|
||||
remaining_quote_lots -= match_quote_lots;
|
||||
assert!(remaining_quote_lots >= 0);
|
||||
|
||||
let new_best_opposing_quantity = cm!(best_opposing.node.quantity - match_base_lots);
|
||||
let new_best_opposing_quantity = best_opposing.node.quantity - match_base_lots;
|
||||
let maker_out = new_best_opposing_quantity == 0;
|
||||
if maker_out {
|
||||
matched_order_deletes
|
||||
|
@ -156,8 +155,8 @@ impl<'a> Orderbook<'a> {
|
|||
event_queue.push_back(cast(fill)).unwrap();
|
||||
limit -= 1;
|
||||
}
|
||||
let total_quote_lots_taken = cm!(order.max_quote_lots - remaining_quote_lots);
|
||||
let total_base_lots_taken = cm!(order.max_base_lots - remaining_base_lots);
|
||||
let total_quote_lots_taken = order.max_quote_lots - remaining_quote_lots;
|
||||
let total_base_lots_taken = order.max_base_lots - remaining_base_lots;
|
||||
assert!(total_quote_lots_taken >= 0);
|
||||
assert!(total_base_lots_taken >= 0);
|
||||
|
||||
|
@ -354,22 +353,22 @@ fn apply_fees(
|
|||
perp_position: &mut PerpPosition,
|
||||
quote_lots: i64,
|
||||
) -> Result<()> {
|
||||
let quote_native = I80F48::from_num(market.quote_lot_size.checked_mul(quote_lots).unwrap());
|
||||
let quote_native = I80F48::from_num(market.quote_lot_size * quote_lots);
|
||||
|
||||
let maker_fees = cm!(quote_native * market.maker_fee);
|
||||
let taker_fees = cm!(quote_native * market.taker_fee);
|
||||
let maker_fees = quote_native * market.maker_fee;
|
||||
let taker_fees = quote_native * market.taker_fee;
|
||||
|
||||
// taker fees should never be negative
|
||||
require_gte!(taker_fees, 0);
|
||||
|
||||
// The maker fees apply to the maker's account only when the fill event is consumed.
|
||||
perp_position.record_trading_fee(taker_fees);
|
||||
cm!(perp_position.taker_volume += taker_fees.to_num::<u64>());
|
||||
perp_position.taker_volume += taker_fees.to_num::<u64>();
|
||||
|
||||
// Accrue maker fees immediately: they can be negative and applying them later
|
||||
// risks that fees_accrued is settled to 0 before they apply. It going negative
|
||||
// breaks assumptions.
|
||||
cm!(market.fees_accrued += taker_fees + maker_fees);
|
||||
market.fees_accrued += taker_fees + maker_fees;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -378,6 +377,6 @@ fn apply_fees(
|
|||
fn apply_penalty(market: &mut PerpMarket, perp_position: &mut PerpPosition) -> Result<()> {
|
||||
let fee_penalty = I80F48::from_num(market.fee_penalty);
|
||||
perp_position.record_trading_fee(fee_penalty);
|
||||
cm!(market.fees_accrued += fee_penalty);
|
||||
market.fees_accrued += fee_penalty;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
|
|||
use static_assertions::const_assert_eq;
|
||||
|
||||
use super::*;
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
#[derive(
|
||||
Eq,
|
||||
|
@ -174,7 +173,7 @@ impl BookSide {
|
|||
pub fn impact_price(&self, quantity: i64, now_ts: u64, oracle_price_lots: i64) -> Option<i64> {
|
||||
let mut sum: i64 = 0;
|
||||
for order in self.iter_valid(now_ts, oracle_price_lots) {
|
||||
cm!(sum += order.node.quantity);
|
||||
sum += order.node.quantity;
|
||||
if sum >= quantity {
|
||||
return Some(order.price_lots);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
use super::*;
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
/// Perp order parameters
|
||||
pub struct Order {
|
||||
|
@ -138,7 +137,7 @@ impl Order {
|
|||
order_type,
|
||||
..
|
||||
} => {
|
||||
let price_lots = cm!(oracle_price_lots + price_offset_lots);
|
||||
let price_lots = oracle_price_lots + price_offset_lots;
|
||||
self.price_for_order_type(
|
||||
now_ts,
|
||||
oracle_price_lots,
|
||||
|
@ -150,7 +149,7 @@ impl Order {
|
|||
};
|
||||
let price_data = match self.params {
|
||||
OrderParams::OraclePegged { .. } => {
|
||||
oracle_pegged_price_data(cm!(price_lots - oracle_price_lots))
|
||||
oracle_pegged_price_data(price_lots - oracle_price_lots)
|
||||
}
|
||||
_ => fixed_price_data(price_lots)?,
|
||||
};
|
||||
|
@ -179,7 +178,7 @@ fn market_order_limit_for_side(side: Side) -> i64 {
|
|||
/// the best opposing order
|
||||
fn post_only_slide_limit(side: Side, best_other_side: i64, limit: i64) -> i64 {
|
||||
match side {
|
||||
Side::Bid => limit.min(cm!(best_other_side - 1)),
|
||||
Side::Ask => limit.max(cm!(best_other_side + 1)),
|
||||
Side::Bid => limit.min(best_other_side - 1),
|
||||
Side::Ask => limit.max(best_other_side + 1),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -250,14 +250,8 @@ impl FillEvent {
|
|||
|
||||
pub fn base_quote_change(&self, side: Side) -> (i64, i64) {
|
||||
match side {
|
||||
Side::Bid => (
|
||||
self.quantity,
|
||||
-self.price.checked_mul(self.quantity).unwrap(),
|
||||
),
|
||||
Side::Ask => (
|
||||
-self.quantity,
|
||||
self.price.checked_mul(self.quantity).unwrap(),
|
||||
),
|
||||
Side::Bid => (self.quantity, -self.price * self.quantity),
|
||||
Side::Ask => (-self.quantity, self.price * self.quantity),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ use crate::error::MangoError;
|
|||
use crate::logs::PerpUpdateFundingLog;
|
||||
use crate::state::orderbook::Side;
|
||||
use crate::state::{oracle, TokenIndex};
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
use super::{orderbook, OracleConfig, Orderbook, StablePriceModel, DAY_I80F48};
|
||||
|
||||
|
@ -279,9 +278,9 @@ impl PerpMarket {
|
|||
let diff_price = match (bid, ask) {
|
||||
(Some(bid), Some(ask)) => {
|
||||
// calculate mid-market rate
|
||||
let mid_price = bid.checked_add(ask).unwrap() / 2;
|
||||
let mid_price = (bid + ask) / 2;
|
||||
let book_price = self.lot_to_native_price(mid_price);
|
||||
let diff = cm!(book_price / index_price - I80F48::ONE);
|
||||
let diff = book_price / index_price - I80F48::ONE;
|
||||
diff.clamp(self.min_funding, self.max_funding)
|
||||
}
|
||||
(Some(_bid), None) => self.max_funding,
|
||||
|
@ -290,9 +289,9 @@ impl PerpMarket {
|
|||
};
|
||||
|
||||
let diff_ts = I80F48::from_num(now_ts - self.funding_last_updated as u64);
|
||||
let time_factor = cm!(diff_ts / DAY_I80F48);
|
||||
let time_factor = diff_ts / DAY_I80F48;
|
||||
let base_lot_size = I80F48::from_num(self.base_lot_size);
|
||||
let funding_delta = cm!(index_price * diff_price * base_lot_size * time_factor);
|
||||
let funding_delta = index_price * diff_price * base_lot_size * time_factor;
|
||||
|
||||
self.long_funding += funding_delta;
|
||||
self.short_funding += funding_delta;
|
||||
|
@ -318,19 +317,12 @@ impl PerpMarket {
|
|||
|
||||
/// Convert from the price stored on the book to the price used in value calculations
|
||||
pub fn lot_to_native_price(&self, price: i64) -> I80F48 {
|
||||
I80F48::from_num(price)
|
||||
.checked_mul(I80F48::from_num(self.quote_lot_size))
|
||||
.unwrap()
|
||||
.checked_div(I80F48::from_num(self.base_lot_size))
|
||||
.unwrap()
|
||||
I80F48::from_num(price) * I80F48::from_num(self.quote_lot_size)
|
||||
/ I80F48::from_num(self.base_lot_size)
|
||||
}
|
||||
|
||||
pub fn native_price_to_lot(&self, price: I80F48) -> i64 {
|
||||
price
|
||||
.checked_mul(I80F48::from_num(self.base_lot_size))
|
||||
.unwrap()
|
||||
.checked_div(I80F48::from_num(self.quote_lot_size))
|
||||
.unwrap()
|
||||
(price * I80F48::from_num(self.base_lot_size) / I80F48::from_num(self.quote_lot_size))
|
||||
.to_num()
|
||||
}
|
||||
|
||||
|
@ -342,8 +334,8 @@ impl PerpMarket {
|
|||
oracle_price: I80F48,
|
||||
) -> bool {
|
||||
match side {
|
||||
Side::Bid => native_price <= cm!(self.maint_base_liab_weight * oracle_price),
|
||||
Side::Ask => native_price >= cm!(self.maint_base_asset_weight * oracle_price),
|
||||
Side::Bid => native_price <= (self.maint_base_liab_weight * oracle_price),
|
||||
Side::Ask => native_price >= (self.maint_base_asset_weight * oracle_price),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -360,7 +352,7 @@ impl PerpMarket {
|
|||
// would be appreciated. Luckily, this will be an extremely rare situation.
|
||||
I80F48::ZERO
|
||||
} else {
|
||||
cm!(loss / I80F48::from(self.open_interest))
|
||||
loss / I80F48::from(self.open_interest)
|
||||
};
|
||||
self.long_funding -= socialized_loss;
|
||||
self.short_funding += socialized_loss;
|
||||
|
@ -383,11 +375,11 @@ impl PerpMarket {
|
|||
let low_health_fee = if source_liq_end_health < 0 {
|
||||
let fee_fraction = I80F48::from_num(self.settle_fee_fraction_low_health);
|
||||
if source_maint_health < 0 {
|
||||
cm!(settlement * fee_fraction)
|
||||
settlement * fee_fraction
|
||||
} else {
|
||||
cm!(settlement
|
||||
settlement
|
||||
* fee_fraction
|
||||
* (-source_liq_end_health / (source_maint_health - source_liq_end_health)))
|
||||
* (-source_liq_end_health / (source_maint_health - source_liq_end_health))
|
||||
}
|
||||
} else {
|
||||
I80F48::ZERO
|
||||
|
@ -401,7 +393,7 @@ impl PerpMarket {
|
|||
};
|
||||
|
||||
// Fees only apply when the settlement is large enough
|
||||
let fee = cm!(low_health_fee + flat_fee).min(settlement);
|
||||
let fee = (low_health_fee + flat_fee).min(settlement);
|
||||
|
||||
// Safety check to prevent any accidental negative transfer
|
||||
require!(fee >= 0, MangoError::SettlementAmountMustBePositive);
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use crate::util::checked_math as cm;
|
||||
use anchor_lang::prelude::*;
|
||||
use derivative::Derivative;
|
||||
use static_assertions::const_assert_eq;
|
||||
|
@ -123,7 +122,7 @@ impl StablePriceModel {
|
|||
//
|
||||
// Update delay price
|
||||
//
|
||||
cm!(self.delay_accumulator_time += dt as u32);
|
||||
self.delay_accumulator_time += dt as u32;
|
||||
self.delay_accumulator_price += oracle_price * dt_limited;
|
||||
|
||||
let delay_interval_index = self.delay_interval_index(now_ts);
|
||||
|
|
|
@ -12,14 +12,6 @@ macro_rules! zip {
|
|||
#[allow(unused_imports)]
|
||||
pub(crate) use zip;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! checked_math {
|
||||
($x: expr) => {
|
||||
checked_math::checked_math_or_panic!($x)
|
||||
};
|
||||
}
|
||||
pub(crate) use checked_math;
|
||||
|
||||
pub fn fill_from_str<const N: usize>(name: &str) -> Result<[u8; N]> {
|
||||
let name_bytes = name.as_bytes();
|
||||
require!(name_bytes.len() < N, MangoError::SomeError);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
pub use anchor_lang::prelude::Pubkey;
|
||||
pub use fixed::types::I80F48;
|
||||
pub use fixed_macro::types::I80F48;
|
||||
pub use solana_program_test::*;
|
||||
pub use solana_sdk::transport::TransportError;
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ async fn test_health_compute_tokens() -> Result<(), TransportError> {
|
|||
#[tokio::test]
|
||||
async fn test_health_compute_serum() -> Result<(), TransportError> {
|
||||
let mut test_builder = TestContextBuilder::new();
|
||||
test_builder.test().set_compute_max_units(80_000);
|
||||
test_builder.test().set_compute_max_units(90_000);
|
||||
let context = test_builder.start_default().await;
|
||||
let solana = &context.solana.clone();
|
||||
|
||||
|
@ -148,7 +148,7 @@ async fn test_health_compute_serum() -> Result<(), TransportError> {
|
|||
}
|
||||
|
||||
// TODO: actual explicit CU comparisons.
|
||||
// On 2023-2-5 the final deposit costs 78587 CU and each new market increases it by roughly 5900 CU
|
||||
// On 2023-2-23 the final deposit costs 81141 CU and each new market increases it by roughly 6184 CU
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
|||
|
||||
let price_lots = {
|
||||
let perp_market = solana.get_account::<PerpMarket>(perp_market).await;
|
||||
perp_market.native_price_to_lot(I80F48!(1))
|
||||
perp_market.native_price_to_lot(I80F48::ONE)
|
||||
};
|
||||
|
||||
//
|
||||
|
@ -537,7 +537,7 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
|
|||
|
||||
let price_lots = {
|
||||
let perp_market = solana.get_account::<PerpMarket>(perp_market).await;
|
||||
perp_market.native_price_to_lot(I80F48!(1))
|
||||
perp_market.native_price_to_lot(I80F48::ONE)
|
||||
};
|
||||
assert_eq!(price_lots, 1000);
|
||||
|
||||
|
@ -918,7 +918,7 @@ async fn test_perp_realize_partially() -> Result<(), TransportError> {
|
|||
.unwrap();
|
||||
|
||||
let perp_market_data = solana.get_account::<PerpMarket>(perp_market).await;
|
||||
let price_lots = perp_market_data.native_price_to_lot(I80F48!(1000));
|
||||
let price_lots = perp_market_data.native_price_to_lot(I80F48::from(1000));
|
||||
set_perp_stub_oracle_price(solana, group, perp_market, &tokens[1], admin, 1000.0).await;
|
||||
|
||||
//
|
||||
|
|
|
@ -496,7 +496,9 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_perp_settle_pnl_fees() -> Result<(), TransportError> {
|
||||
let context = TestContext::new().await;
|
||||
let mut test_builder = TestContextBuilder::new();
|
||||
test_builder.test().set_compute_max_units(90_000);
|
||||
let context = test_builder.start_default().await;
|
||||
let solana = &context.solana.clone();
|
||||
|
||||
let admin = TestKeypair::new();
|
||||
|
|
Loading…
Reference in New Issue