Track cumulative net deposits (deposits - withdraws) (#91)
Track cumulative net deposits (deposits - withdraws) using prices at the time of the deposit and withdraw. This is used for calculating overall pnl (across all tokens). I want to store UI amount * UI price = (native amount / base decimals) * (oracle price * base decimals / quote decimals) => native amount * oracle price / quote decimals. I have used f32 here to reduce the space required on the mango account - we don't need so much precision for this as it's purely a display value. I've also included a field for net_settled - this will be used for perp pnl but is not implemented yet (as perp settling instructions are not ready). Co-authored-by: Christian Kamm <mail@ckamm.de>
This commit is contained in:
parent
5b72e85634
commit
348fbc1cb9
|
@ -6,6 +6,7 @@ use fixed::types::I80F48;
|
||||||
|
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::state::*;
|
use crate::state::*;
|
||||||
|
use crate::util::checked_math as cm;
|
||||||
|
|
||||||
use crate::logs::{DepositLog, TokenBalanceLog};
|
use crate::logs::{DepositLog, TokenBalanceLog};
|
||||||
|
|
||||||
|
@ -65,9 +66,10 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
|
||||||
let (position, raw_token_index, active_token_index) =
|
let (position, raw_token_index, active_token_index) =
|
||||||
account.tokens.get_mut_or_create(token_index)?;
|
account.tokens.get_mut_or_create(token_index)?;
|
||||||
|
|
||||||
|
let amount_i80f48 = I80F48::from(amount);
|
||||||
let position_is_active = {
|
let position_is_active = {
|
||||||
let mut bank = ctx.accounts.bank.load_mut()?;
|
let mut bank = ctx.accounts.bank.load_mut()?;
|
||||||
bank.deposit(position, I80F48::from(amount))?
|
bank.deposit(position, amount_i80f48)?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Transfer the actual tokens
|
// Transfer the actual tokens
|
||||||
|
@ -78,6 +80,10 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
|
||||||
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account)?;
|
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account)?;
|
||||||
let (bank, oracle_price) =
|
let (bank, oracle_price) =
|
||||||
retriever.bank_and_oracle(&ctx.accounts.group.key(), active_token_index, token_index)?;
|
retriever.bank_and_oracle(&ctx.accounts.group.key(), active_token_index, token_index)?;
|
||||||
|
|
||||||
|
// Update the net deposits - adjust by price so different tokens are on the same basis (in USD terms)
|
||||||
|
account.net_deposits += cm!(amount_i80f48 * oracle_price * QUOTE_NATIVE_TO_UI).to_num::<f32>();
|
||||||
|
|
||||||
emit!(TokenBalanceLog {
|
emit!(TokenBalanceLog {
|
||||||
mango_account: ctx.accounts.account.key(),
|
mango_account: ctx.accounts.account.key(),
|
||||||
token_index: token_index,
|
token_index: token_index,
|
||||||
|
|
|
@ -8,6 +8,7 @@ use fixed::types::I80F48;
|
||||||
|
|
||||||
use crate::logs::{TokenBalanceLog, WithdrawLog};
|
use crate::logs::{TokenBalanceLog, WithdrawLog};
|
||||||
use crate::state::new_fixed_order_account_retriever;
|
use crate::state::new_fixed_order_account_retriever;
|
||||||
|
use crate::util::checked_math as cm;
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct TokenWithdraw<'info> {
|
pub struct TokenWithdraw<'info> {
|
||||||
|
@ -71,7 +72,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
|
||||||
|
|
||||||
// The bank will also be passed in remainingAccounts. Use an explicit scope
|
// The bank will also be passed in remainingAccounts. Use an explicit scope
|
||||||
// to drop the &mut before we borrow it immutably again later.
|
// to drop the &mut before we borrow it immutably again later.
|
||||||
let position_is_active = {
|
let (position_is_active, amount_i80f48) = {
|
||||||
let mut bank = ctx.accounts.bank.load_mut()?;
|
let mut bank = ctx.accounts.bank.load_mut()?;
|
||||||
let native_position = position.native(&bank);
|
let native_position = position.native(&bank);
|
||||||
|
|
||||||
|
@ -105,7 +106,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
|
||||||
amount,
|
amount,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
position_is_active
|
(position_is_active, amount_i80f48)
|
||||||
};
|
};
|
||||||
|
|
||||||
let indexed_position = position.indexed_position;
|
let indexed_position = position.indexed_position;
|
||||||
|
@ -113,6 +114,10 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
|
||||||
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account)?;
|
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account)?;
|
||||||
let (bank, oracle_price) =
|
let (bank, oracle_price) =
|
||||||
retriever.bank_and_oracle(&ctx.accounts.group.key(), active_token_index, token_index)?;
|
retriever.bank_and_oracle(&ctx.accounts.group.key(), active_token_index, token_index)?;
|
||||||
|
|
||||||
|
// Update the net deposits - adjust by price so different tokens are on the same basis (in USD terms)
|
||||||
|
account.net_deposits -= cm!(amount_i80f48 * oracle_price * QUOTE_NATIVE_TO_UI).to_num::<f32>();
|
||||||
|
|
||||||
emit!(TokenBalanceLog {
|
emit!(TokenBalanceLog {
|
||||||
mango_account: ctx.accounts.account.key(),
|
mango_account: ctx.accounts.account.key(),
|
||||||
token_index: token_index,
|
token_index: token_index,
|
||||||
|
|
|
@ -736,6 +736,14 @@ pub struct MangoAccount {
|
||||||
|
|
||||||
// pub info: [u8; INFO_LEN], // TODO: Info could be in a separate PDA?
|
// pub info: [u8; INFO_LEN], // TODO: Info could be in a separate PDA?
|
||||||
pub reserved: [u8; 4],
|
pub reserved: [u8; 4],
|
||||||
|
|
||||||
|
// Cumulative (deposits - withdraws)
|
||||||
|
// using USD prices at the time of the deposit/withdraw
|
||||||
|
// in UI USD units
|
||||||
|
pub net_deposits: f32,
|
||||||
|
// Cumulative settles on perp positions
|
||||||
|
// TODO: unimplemented
|
||||||
|
pub net_settled: f32,
|
||||||
}
|
}
|
||||||
const_assert_eq!(
|
const_assert_eq!(
|
||||||
size_of::<MangoAccount>(),
|
size_of::<MangoAccount>(),
|
||||||
|
@ -745,6 +753,7 @@ const_assert_eq!(
|
||||||
+ size_of::<MangoAccountPerpPositions>()
|
+ size_of::<MangoAccountPerpPositions>()
|
||||||
+ 4
|
+ 4
|
||||||
+ 4
|
+ 4
|
||||||
|
+ 2 * 4 // net_deposits and net_settled
|
||||||
);
|
);
|
||||||
const_assert_eq!(size_of::<MangoAccount>() % 8, 0);
|
const_assert_eq!(size_of::<MangoAccount>() % 8, 0);
|
||||||
|
|
||||||
|
@ -810,6 +819,8 @@ impl Default for MangoAccount {
|
||||||
account_num: 0,
|
account_num: 0,
|
||||||
bump: 0,
|
bump: 0,
|
||||||
reserved: Default::default(),
|
reserved: Default::default(),
|
||||||
|
net_deposits: 0.0,
|
||||||
|
net_settled: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,6 @@ use crate::accounts_zerocopy::*;
|
||||||
use crate::checked_math as cm;
|
use crate::checked_math as cm;
|
||||||
use crate::error::MangoError;
|
use crate::error::MangoError;
|
||||||
|
|
||||||
pub const QUOTE_DECIMALS: i8 = 6;
|
|
||||||
|
|
||||||
const LOOKUP_START: i8 = -12;
|
const LOOKUP_START: i8 = -12;
|
||||||
const LOOKUP: [I80F48; 25] = [
|
const LOOKUP: [I80F48; 25] = [
|
||||||
I80F48::from_bits((1 << 48) / 10i128.pow(12u32)),
|
I80F48::from_bits((1 << 48) / 10i128.pow(12u32)),
|
||||||
|
@ -44,6 +42,9 @@ const LOOKUP: [I80F48; 25] = [
|
||||||
];
|
];
|
||||||
const LOOKUP_FN: fn(i8) -> usize = |decimals: i8| (decimals - LOOKUP_START) as usize;
|
const LOOKUP_FN: fn(i8) -> usize = |decimals: i8| (decimals - LOOKUP_START) as usize;
|
||||||
|
|
||||||
|
pub const QUOTE_DECIMALS: i8 = 6;
|
||||||
|
pub const QUOTE_NATIVE_TO_UI: I80F48 = LOOKUP[(-QUOTE_DECIMALS - LOOKUP_START) as usize];
|
||||||
|
|
||||||
pub mod switchboard_v1_devnet_oracle {
|
pub mod switchboard_v1_devnet_oracle {
|
||||||
use solana_program::declare_id;
|
use solana_program::declare_id;
|
||||||
declare_id!("7azgmy1pFXHikv36q1zZASvFq5vFa39TT9NweVugKKTU");
|
declare_id!("7azgmy1pFXHikv36q1zZASvFq5vFa39TT9NweVugKKTU");
|
||||||
|
|
|
@ -81,6 +81,13 @@ async fn test_basic() -> Result<(), TransportError> {
|
||||||
);
|
);
|
||||||
let bank_data: Bank = solana.get_account(bank).await;
|
let bank_data: Bank = solana.get_account(bank).await;
|
||||||
assert!(bank_data.native_deposits() - I80F48::from_num(deposit_amount) < dust_threshold);
|
assert!(bank_data.native_deposits() - I80F48::from_num(deposit_amount) < dust_threshold);
|
||||||
|
|
||||||
|
let account_data: MangoAccount = solana.get_account(account).await;
|
||||||
|
// Assumes oracle price of 1
|
||||||
|
assert_eq!(
|
||||||
|
account_data.net_deposits,
|
||||||
|
(I80F48::from_num(deposit_amount) * QUOTE_NATIVE_TO_UI).to_num::<f32>()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -132,6 +139,13 @@ async fn test_basic() -> Result<(), TransportError> {
|
||||||
bank_data.native_deposits() - I80F48::from_num(start_amount - withdraw_amount)
|
bank_data.native_deposits() - I80F48::from_num(start_amount - withdraw_amount)
|
||||||
< dust_threshold
|
< dust_threshold
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let account_data: MangoAccount = solana.get_account(account).await;
|
||||||
|
// Assumes oracle price of 1
|
||||||
|
assert_eq!(
|
||||||
|
account_data.net_deposits,
|
||||||
|
(I80F48::from_num(start_amount - withdraw_amount) * QUOTE_NATIVE_TO_UI).to_num::<f32>()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in New Issue