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:
Christian Kamm 2023-02-24 11:56:33 +01:00 committed by GitHub
parent 330739364f
commit 5c7a2e3e10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 390 additions and 508 deletions

3
.gitmodules vendored
View File

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

1
3rdparty/fixed vendored Submodule

@ -0,0 +1 @@
Subproject commit d0f6e6b64e9a434ed0a529538c6fd056b79507df

67
Cargo.lock generated
View File

@ -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"

View File

@ -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"] }

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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;

View File

@ -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());

View File

@ -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() {

View File

@ -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)

View File

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

View File

@ -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(())

View File

@ -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,
)?;

View File

@ -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(())
}

View File

@ -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;

View File

@ -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 {

View File

@ -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(),

View File

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

View File

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

View File

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

View File

@ -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);

View File

@ -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",

View File

@ -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::*;

View File

@ -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(),
});

View File

@ -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)?;
}

View File

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

View File

@ -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);
}

View File

@ -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(())
}

View File

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

View File

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

View File

@ -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(())
}

View File

@ -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);
}

View File

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

View File

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

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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(())
}

View File

@ -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;
//

View File

@ -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();