Audit fixes for the fee buyback feature (#608)

* reduce_buyback_fees_accrued: forbid reducing more than accrued
* Buyback fees: Expire accrued during event processing
This commit is contained in:
Christian Kamm 2023-06-15 10:43:31 +02:00 committed by GitHub
parent 438da67653
commit 66c6926419
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 26 additions and 12 deletions

View File

@ -93,7 +93,7 @@ pub fn account_buyback_fees_with_mngo(
let mut max_buyback_mngo = max_buyback_usd / mngo_buyback_price;
// buyback is restricted to account's token position
max_buyback_mngo = max_buyback_mngo.min(account_mngo);
max_buyback_usd = max_buyback_mngo * mngo_buyback_price;
max_buyback_usd = max_buyback_usd.min(max_buyback_mngo * mngo_buyback_price);
let max_buyback_fees = max_buyback_usd / fees_liab_price;
// move mngo from user to dao

View File

@ -74,7 +74,12 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
group,
event_queue
);
maker_taker.execute_perp_maker(perp_market_index, &mut perp_market, fill)?;
maker_taker.execute_perp_maker(
perp_market_index,
&mut perp_market,
fill,
&group,
)?;
maker_taker.execute_perp_taker(perp_market_index, &mut perp_market, fill)?;
emit_perp_balances(
group_key,
@ -86,7 +91,7 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
load_mango_account!(maker, fill.maker, mango_account_ais, group, event_queue);
load_mango_account!(taker, fill.taker, mango_account_ais, group, event_queue);
maker.execute_perp_maker(perp_market_index, &mut perp_market, fill)?;
maker.execute_perp_maker(perp_market_index, &mut perp_market, fill, &group)?;
taker.execute_perp_taker(perp_market_index, &mut perp_market, fill)?;
emit_perp_balances(
group_key,

View File

@ -14,7 +14,6 @@ use crate::error::*;
use crate::health::{HealthCache, HealthType};
use crate::logs::{DeactivatePerpPositionLog, DeactivateTokenPositionLog};
use super::dynamic_account::*;
use super::BookSideOrderTree;
use super::FillEvent;
use super::LeafNode;
@ -24,6 +23,7 @@ use super::PerpOpenOrder;
use super::Serum3MarketIndex;
use super::TokenIndex;
use super::FREE_ORDER_SLOT;
use super::{dynamic_account::*, Group};
use super::{PerpPosition, Serum3Orders, TokenPosition};
use super::{Side, SideAndOrderTree};
@ -300,17 +300,22 @@ impl MangoAccountFixed {
}
/// Add new fees that are usable with the buyback fees feature.
///
/// Any call to this should be preceeded by a call to expire_buyback_fees earlier
/// in the same instruction.
pub fn accrue_buyback_fees(&mut self, amount: u64) {
self.buyback_fees_accrued_current =
self.buyback_fees_accrued_current.saturating_add(amount);
}
/// Reduce the available buyback fees amount because it was used up.
///
/// Panics if `amount` exceeds the available accrued amount
pub fn reduce_buyback_fees_accrued(&mut self, amount: u64) {
if amount > self.buyback_fees_accrued_previous {
self.buyback_fees_accrued_current = self
.buyback_fees_accrued_current
.saturating_sub(amount - self.buyback_fees_accrued_previous);
let remaining_amount = amount - self.buyback_fees_accrued_previous;
assert!(remaining_amount <= self.buyback_fees_accrued_current);
self.buyback_fees_accrued_current -= remaining_amount;
self.buyback_fees_accrued_previous = 0;
} else {
self.buyback_fees_accrued_previous -= amount;
@ -948,14 +953,17 @@ impl<
perp_market_index: PerpMarketIndex,
perp_market: &mut PerpMarket,
fill: &FillEvent,
group: &Group,
) -> Result<()> {
let side = fill.taker_side().invert_side();
let (base_change, quote_change) = fill.base_quote_change(side);
let quote = I80F48::from(perp_market.quote_lot_size) * I80F48::from(quote_change);
let fees = quote.abs() * I80F48::from_num(fill.maker_fee);
if fees.is_positive() {
self.fixed_mut()
.accrue_buyback_fees(fees.floor().to_num::<u64>());
let f = self.fixed_mut();
let now_ts = Clock::get().unwrap().unix_timestamp.try_into().unwrap();
f.expire_buyback_fees(now_ts, group.buyback_fees_expiry_interval);
f.accrue_buyback_fees(fees.floor().to_num::<u64>());
}
let pa = self.perp_position_mut(perp_market_index)?;
pa.settle_funding(perp_market);
@ -1568,7 +1576,7 @@ mod tests {
assert_eq!(fixed.buyback_fees_expiry_timestamp, 1070);
assert_eq!(fixed.buyback_fees_accrued(), 12);
fixed.reduce_buyback_fees_accrued(100);
fixed.reduce_buyback_fees_accrued(12);
assert_eq!(fixed.buyback_fees_accrued(), 0);
}
}

View File

@ -21,7 +21,7 @@ mod queue;
#[cfg(test)]
mod tests {
use super::*;
use crate::state::{MangoAccount, MangoAccountValue, PerpMarket, FREE_ORDER_SLOT};
use crate::state::{Group, MangoAccount, MangoAccountValue, PerpMarket, FREE_ORDER_SLOT};
use anchor_lang::prelude::*;
use bytemuck::Zeroable;
use fixed::types::I80F48;
@ -238,6 +238,7 @@ mod tests {
#[test]
fn book_new_order() {
let group = Group::zeroed();
let (mut market, oracle_price, mut event_queue, book_accs) = test_setup(1000.0);
let mut book = book_accs.orderbook();
let settle_token_index = 0;
@ -396,7 +397,7 @@ mod tests {
// simulate event queue processing
maker
.execute_perp_maker(market.perp_market_index, &mut market, fill)
.execute_perp_maker(market.perp_market_index, &mut market, fill, &group)
.unwrap();
taker
.execute_perp_taker(market.perp_market_index, &mut market, fill)