Merge branch 'dev'

This commit is contained in:
microwavedcola1 2022-08-08 18:52:41 +02:00
commit 31b71a285d
49 changed files with 941 additions and 379 deletions

View File

@ -12,7 +12,7 @@ url = "https://anchor.projectserum.com"
[provider]
cluster = "localnet"
wallet = "/home/mc/.config/solana/id.json"
wallet = "~/.config/solana/id.json"
[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 anchor-tests/*.ts"

1
Cargo.lock generated
View File

@ -2919,6 +2919,7 @@ dependencies = [
"fixed",
"fixed-macro",
"futures 0.3.21",
"itertools 0.10.3",
"log 0.4.17",
"mango-v4",
"pyth-sdk-solana",

2
anchor

@ -1 +1 @@
Subproject commit 2058b6461cb0de5af90b04eb8fae4225a368251e
Subproject commit b52f23614601652a99ec6c27aec77bd327363b31

View File

@ -283,4 +283,8 @@ describe('mango-v4', () => {
1000,
);
});
it('update index and rate', async () => {
envClient.updateIndexAndRate(group, 'USDC');
});
});

View File

@ -15,7 +15,7 @@ use bincode::Options;
use fixed::types::I80F48;
use itertools::Itertools;
use mango_v4::instructions::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side};
use mango_v4::state::{AccountSize, Bank, Group, MangoAccountValue, Serum3MarketIndex, TokenIndex};
use mango_v4::state::{Bank, Group, MangoAccountValue, Serum3MarketIndex, TokenIndex};
use solana_client::nonblocking::rpc_client::RpcClient as RpcClientAsync;
use solana_client::rpc_client::RpcClient;
@ -206,7 +206,10 @@ impl MangoClient {
data: anchor_lang::InstructionData::data(&mango_v4::instruction::AccountCreate {
account_num,
name: mango_account_name.to_owned(),
account_size: AccountSize::Small,
token_count: 8,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
}),
})
.signer(owner)

View File

@ -1,4 +0,0 @@
RPC_URL=https://mango.devnet.rpcpool.com
PAYER_KEYPAIR=~/.config/solana/mango-devnet.json
GROUP_FROM_ADMIN_KEYPAIR=~/.config/solana/admin.json
MANGO_ACCOUNT_NAME=Account

View File

@ -1,4 +0,0 @@
RPC_URL=https://mango.rpcpool.com/
PAYER_KEYPAIR=~/.config/solana/mango-mainnet.json
GROUP=grouppubkey
MANGO_ACCOUNT_NAME=Account

View File

@ -17,6 +17,7 @@ env_logger = "0.8.4"
fixed = { version = "=1.11.0", features = ["serde", "borsh"] }
fixed-macro = "^1.1.1"
futures = "0.3.21"
itertools = "0.10.3"
log = "0.4.0"
mango-v4 = { path = "../programs/mango-v4", features = ["no-entrypoint", "client"] }
pyth-sdk-solana = "0.1.0"

View File

@ -1,12 +1,14 @@
use std::{sync::Arc, time::Duration};
use crate::MangoClient;
use itertools::Itertools;
use anchor_lang::{__private::bytemuck::cast_ref, solana_program};
use client::prettify_client_error;
use futures::Future;
use mango_v4::state::{EventQueue, EventType, FillEvent, OutEvent, PerpMarket, TokenIndex};
use solana_sdk::{
compute_budget::ComputeBudgetInstruction,
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
};
@ -22,7 +24,16 @@ pub async fn runner(
.context
.tokens
.keys()
.map(|&token_index| loop_update_index_and_rate(mango_client.clone(), token_index))
// TokenUpdateIndexAndRate is known to take max 71k cu
// from cargo test-bpf local tests
.chunks(15)
.into_iter()
.map(|chunk| {
loop_update_index_and_rate(
mango_client.clone(),
chunk.copied().collect::<Vec<TokenIndex>>(),
)
})
.collect::<Vec<_>>();
let handles2 = mango_client
@ -49,57 +60,66 @@ pub async fn runner(
Ok(())
}
pub async fn loop_update_index_and_rate(mango_client: Arc<MangoClient>, token_index: TokenIndex) {
pub async fn loop_update_index_and_rate(
mango_client: Arc<MangoClient>,
token_indices: Vec<TokenIndex>,
) {
let mut interval = time::interval(Duration::from_secs(5));
loop {
interval.tick().await;
let client = mango_client.clone();
let res = tokio::task::spawn_blocking(move || -> anyhow::Result<()> {
let token = client.context.token(token_index);
let banks_for_a_token = token.mint_info.banks();
let token_name = &token.name;
let oracle = token.mint_info.oracle;
let token_indices_clone = token_indices.clone();
let sig_result = client
.program()
.request()
.instruction({
let mut ix = Instruction {
program_id: mango_v4::id(),
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::TokenUpdateIndexAndRate {
mint_info: token.mint_info_address,
oracle,
instructions: solana_program::sysvar::instructions::id(),
},
None,
),
data: anchor_lang::InstructionData::data(
&mango_v4::instruction::TokenUpdateIndexAndRate {},
),
};
let mut banks = banks_for_a_token
.iter()
.map(|bank_pubkey| AccountMeta {
pubkey: *bank_pubkey,
is_signer: false,
is_writable: true,
})
.collect::<Vec<_>>();
ix.accounts.append(&mut banks);
ix
})
.send()
.map_err(prettify_client_error);
let res = tokio::task::spawn_blocking(move || -> anyhow::Result<()> {
let token_names = token_indices_clone
.iter()
.map(|token_index| client.context.token(*token_index).name.to_owned())
.join(", ");
let program = client.program();
let mut req = program.request();
req = req.instruction(ComputeBudgetInstruction::set_compute_unit_price(1));
for token_index in token_indices_clone.iter() {
let token = client.context.token(*token_index);
let banks_for_a_token = token.mint_info.banks();
let oracle = token.mint_info.oracle;
let mut ix = Instruction {
program_id: mango_v4::id(),
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::TokenUpdateIndexAndRate {
group: token.mint_info.group,
mint_info: token.mint_info_address,
oracle,
instructions: solana_program::sysvar::instructions::id(),
},
None,
),
data: anchor_lang::InstructionData::data(
&mango_v4::instruction::TokenUpdateIndexAndRate {},
),
};
let mut banks = banks_for_a_token
.iter()
.map(|bank_pubkey| AccountMeta {
pubkey: *bank_pubkey,
is_signer: false,
is_writable: true,
})
.collect::<Vec<_>>();
ix.accounts.append(&mut banks);
req = req.instruction(ix);
}
let sig_result = req.send().map_err(prettify_client_error);
if let Err(e) = sig_result {
log::error!("{:?}", e)
} else {
log::info!(
"update_index_and_rate {} {:?}",
token_name,
token_names,
sig_result.unwrap()
)
}
@ -240,6 +260,7 @@ pub async fn loop_update_funding(
program_id: mango_v4::id(),
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::PerpUpdateFunding {
group: perp_market.group,
perp_market: pk,
asks: perp_market.asks,
bids: perp_market.bids,

View File

@ -76,7 +76,7 @@ fn main() -> Result<(), anyhow::Error> {
};
let mango_client = Arc::new(MangoClient::new_for_existing_account(
Client::new(cluster, commitment, &owner, Some(Duration::from_secs(1))),
Client::new(cluster, commitment, &owner, Some(Duration::from_secs(10))),
cli.mango_account,
owner,
)?);

View File

@ -96,7 +96,7 @@ async fn main() -> anyhow::Result<()> {
let rpc_url = cli.rpc_url;
let ws_url = rpc_url.replace("https", "wss");
let rpc_timeout = Duration::from_secs(1);
let rpc_timeout = Duration::from_secs(10);
let cluster = Cluster::Custom(rpc_url.clone(), ws_url.clone());
let commitment = CommitmentConfig::processed();
let client = Client::new(cluster.clone(), commitment, &liqor_owner, Some(rpc_timeout));

View File

@ -8,7 +8,12 @@ use crate::state::*;
pub struct AccountClose<'info> {
pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group, has_one = owner, close = sol_destination)]
#[account(
mut,
has_one = group,
has_one = owner,
close = sol_destination
)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,

View File

@ -5,7 +5,7 @@ use crate::state::*;
use crate::util::fill32_from_str;
#[derive(Accounts)]
#[instruction(account_num: u32, account_size: AccountSize)]
#[instruction(account_num: u32, token_count: u8, serum3_count: u8, perp_count: u8, perp_oo_count: u8)]
pub struct AccountCreate<'info> {
pub group: AccountLoader<'info, Group>,
@ -14,7 +14,7 @@ pub struct AccountCreate<'info> {
seeds = [group.key().as_ref(), b"MangoAccount".as_ref(), owner.key().as_ref(), &account_num.to_le_bytes()],
bump,
payer = payer,
space = MangoAccount::space(account_size),
space = MangoAccount::space(token_count, serum3_count, perp_count, perp_oo_count)?,
)]
pub account: AccountLoaderDynamic<'info, MangoAccount>,
pub owner: Signer<'info>,
@ -28,7 +28,10 @@ pub struct AccountCreate<'info> {
pub fn account_create(
ctx: Context<AccountCreate>,
account_num: u32,
account_size: AccountSize,
token_count: u8,
serum3_count: u8,
perp_count: u8,
perp_oo_count: u8,
name: String,
) -> Result<()> {
let mut account = ctx.accounts.account.load_init()?;
@ -47,7 +50,7 @@ pub fn account_create(
account.fixed.set_being_liquidated(false);
account.fixed.set_bankrupt(false);
account.expand_dynamic_content(account_size)?;
account.expand_dynamic_content(token_count, serum3_count, perp_count, perp_oo_count)?;
Ok(())
}

View File

@ -20,15 +20,14 @@ pub struct AccountExpand<'info> {
pub system_program: Program<'info, System>,
}
pub fn account_expand(ctx: Context<AccountExpand>) -> Result<()> {
let account_size = {
let account = ctx.accounts.account.load()?;
account.size()
};
require_eq!(account_size, AccountSize::Small);
let new_space = MangoAccount::space(AccountSize::Large);
pub fn account_expand(
ctx: Context<AccountExpand>,
token_count: u8,
serum3_count: u8,
perp_count: u8,
perp_oo_count: u8,
) -> Result<()> {
let new_space = MangoAccount::space(token_count, serum3_count, perp_count, perp_oo_count)?;
let new_rent_minimum = Rent::get()?.minimum_balance(new_space);
let realloc_account = ctx.accounts.account.as_ref();
@ -55,7 +54,7 @@ pub fn account_expand(ctx: Context<AccountExpand>) -> Result<()> {
// expand dynamic content, e.g. to grow token positions, we need to slide serum3orders further later, and so on....
let mut account = ctx.accounts.account.load_mut()?;
account.expand_dynamic_content(AccountSize::Large)?;
account.expand_dynamic_content(token_count, serum3_count, perp_count, perp_oo_count)?;
Ok(())
}

View File

@ -10,6 +10,7 @@ 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;
use anchor_spl::token::{self, Token, TokenAccount};
use fixed::types::I80F48;
@ -76,19 +77,20 @@ pub fn flash_loan_begin<'key, 'accounts, 'remaining, 'info>(
require_keys_eq!(bank.group, ctx.accounts.group.key());
require_keys_eq!(bank.vault, *vault_ai.key);
let vault = Account::<TokenAccount>::try_from(vault_ai)?;
let token_account = Account::<TokenAccount>::try_from(token_account_ai)?;
bank.flash_loan_approved_amount = *amount;
bank.flash_loan_vault_initial = token_account.amount;
bank.flash_loan_token_account_initial = token_account.amount;
// Transfer the loaned funds
if *amount > 0 {
// Provide a readable error message in case the vault doesn't have enough tokens
if token_account.amount < *amount {
if vault.amount < *amount {
return err!(MangoError::InsufficentBankVaultFunds).with_context(|| {
format!(
"bank vault {} does not have enough tokens, need {} but have {}",
vault_ai.key, amount, token_account.amount
vault_ai.key, amount, vault.amount
)
});
}
@ -141,11 +143,11 @@ pub fn flash_loan_begin<'key, 'accounts, 'remaining, 'info>(
// must be the FlashLoanEnd instruction
require!(
ix.data[0..8] == [178, 170, 2, 78, 240, 23, 190, 178],
ix.data[0..8] == crate::instruction::FlashLoanEnd::discriminator(),
MangoError::SomeError
);
// check that the same vaults are passed
// check that the same vaults and token accounts are passed
let begin_accounts = &ctx.remaining_accounts[num_loans..];
let end_accounts = &ix.accounts[ix.accounts.len() - 2 * num_loans..];
for (begin_account, end_account) in begin_accounts.iter().zip(end_accounts.iter()) {
@ -180,6 +182,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoanEnd<'info>>,
) -> Result<()> {
let mut account = ctx.accounts.account.load_mut()?;
let group = account.fixed.group;
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
@ -233,7 +236,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
// The Begin instruction only checks that End ends with the same vault accounts -
// but there could be an extra vault account in End, or a different bank could be
// used for the same vault.
require_neq!(bank.flash_loan_vault_initial, u64::MAX);
require_neq!(bank.flash_loan_token_account_initial, u64::MAX);
// Create the token position now, so we can compute the pre-health with fixed order health accounts
let (_, raw_token_index, _) = account.token_get_mut_or_create(bank.token_index)?;
@ -241,7 +244,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
// Transfer any excess over the inital balance of the token account back
// into the vault. Compute the total change in the vault balance.
let mut change = -I80F48::from(bank.flash_loan_approved_amount);
if token_account.amount > bank.flash_loan_vault_initial {
if token_account.amount > bank.flash_loan_token_account_initial {
let transfer_ctx = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
token::Transfer {
@ -250,7 +253,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
authority: ctx.accounts.owner.to_account_info(),
},
);
let repay = token_account.amount - bank.flash_loan_vault_initial;
let repay = token_account.amount - bank.flash_loan_token_account_initial;
token::transfer(transfer_ctx, repay)?;
let repay = I80F48::from(repay);
@ -322,7 +325,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
}
bank.flash_loan_approved_amount = 0;
bank.flash_loan_vault_initial = u64::MAX;
bank.flash_loan_token_account_initial = u64::MAX;
token_loan_details.push(FlashLoanTokenDetail {
token_index: position.token_index,
@ -335,6 +338,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
});
emit!(TokenBalanceLog {
mango_group: group.key(),
mango_account: ctx.accounts.account.key(),
token_index: bank.token_index as u16,
indexed_position: position.indexed_position.to_bits(),
@ -345,6 +349,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
}
emit!(FlashLoanLog {
mango_group: group.key(),
mango_account: ctx.accounts.account.key(),
token_loan_details
});

View File

@ -157,6 +157,7 @@ pub fn liq_token_with_token(
);
emit!(LiquidateTokenAndTokenLog {
mango_group: ctx.accounts.group.key(),
liqee: ctx.accounts.liqee.key(),
liqor: ctx.accounts.liqor.key(),
asset_token_index,
@ -170,6 +171,7 @@ pub fn liq_token_with_token(
// liqee asset
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqee.key(),
token_index: asset_token_index,
indexed_position: liqee_asset_position_indexed.to_bits(),
@ -179,6 +181,7 @@ pub fn liq_token_with_token(
});
// liqee liab
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqee.key(),
token_index: liab_token_index,
indexed_position: liqee_liab_position_indexed.to_bits(),
@ -188,6 +191,7 @@ pub fn liq_token_with_token(
});
// liqor asset
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(),
token_index: asset_token_index,
indexed_position: liqor_asset_position_indexed.to_bits(),
@ -197,6 +201,7 @@ pub fn liq_token_with_token(
});
// liqor liab
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(),
token_index: liab_token_index,
indexed_position: liqor_liab_position_indexed.to_bits(),

View File

@ -61,6 +61,7 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
fill,
)?;
emit_perp_balances(
ctx.accounts.group.key(),
fill.maker,
perp_market.perp_market_index as u64,
fill.price,
@ -101,6 +102,7 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
fill,
)?;
emit_perp_balances(
ctx.accounts.group.key(),
fill.maker,
perp_market.perp_market_index as u64,
fill.price,
@ -110,6 +112,7 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
&perp_market,
);
emit_perp_balances(
ctx.accounts.group.key(),
fill.taker,
perp_market.perp_market_index as u64,
fill.price,

View File

@ -1,15 +1,18 @@
use anchor_lang::prelude::*;
use crate::accounts_zerocopy::*;
use crate::state::{oracle_price, Book, BookSide, PerpMarket};
use crate::state::{oracle_price, Book, BookSide, Group, PerpMarket};
#[derive(Accounts)]
pub struct PerpUpdateFunding<'info> {
pub group: AccountLoader<'info, Group>, // Required for group metadata parsing
#[account(
mut,
has_one = bids,
has_one = asks,
has_one = oracle,
constraint = perp_market.load()?.group.key() == group.key(),
)]
pub perp_market: AccountLoader<'info, PerpMarket>,
#[account(mut)]

View File

@ -83,6 +83,7 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
cm!(amount_i80f48 * oracle_price * QUOTE_NATIVE_TO_UI).to_num::<f32>();
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index,
indexed_position: indexed_position.to_bits(),
@ -111,6 +112,7 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
}
emit!(DepositLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
signer: ctx.accounts.token_authority.key(),
token_index,

View File

@ -30,6 +30,7 @@ pub fn token_edit(
bank_num: u64,
oracle_opt: Option<Pubkey>,
oracle_config_opt: Option<OracleConfig>,
group_insurance_fund_opt: Option<bool>,
interest_rate_params_opt: Option<InterestRateParams>,
loan_fee_rate_opt: Option<f32>,
loan_origination_fee_rate_opt: Option<f32>,
@ -62,6 +63,10 @@ pub fn token_edit(
bank.oracle_config = oracle_config;
};
if let Some(group_insurance_fund) = group_insurance_fund_opt {
mint_info.group_insurance_fund = if group_insurance_fund { 1 } else { 0 };
};
// unchanged -
// deposit_index
// borrow_index
@ -109,7 +114,7 @@ pub fn token_edit(
// unchanged -
// dust
// flash_loan_vault_initial
// flash_loan_token_account_initial
// flash_loan_approved_amount
// token_index
// bump

View File

@ -131,13 +131,13 @@ pub fn token_register(
init_liab_weight: I80F48::from_num(init_liab_weight),
liquidation_fee: I80F48::from_num(liquidation_fee),
dust: I80F48::ZERO,
flash_loan_vault_initial: u64::MAX,
flash_loan_token_account_initial: u64::MAX,
flash_loan_approved_amount: 0,
token_index,
bump: *ctx.bumps.get("bank").ok_or(MangoError::SomeError)?,
mint_decimals: ctx.accounts.mint.decimals,
bank_num: 0,
reserved: [0; 256],
reserved: [0; 2560],
};
require_gt!(bank.max_rate, MINIMUM_MAX_RATE);
@ -145,14 +145,14 @@ pub fn token_register(
*mint_info = MintInfo {
group: ctx.accounts.group.key(),
token_index,
group_insurance_fund: 1,
padding1: Default::default(),
mint: ctx.accounts.mint.key(),
banks: Default::default(),
vaults: Default::default(),
oracle: ctx.accounts.oracle.key(),
registration_time: Clock::get()?.unix_timestamp,
group_insurance_fund: 1,
reserved: [0; 255],
reserved: [0; 2560],
};
mint_info.banks[0] = ctx.accounts.bank.key();

View File

@ -88,28 +88,29 @@ pub fn token_register_trustless(
index_last_updated: Clock::get()?.unix_timestamp,
bank_rate_last_updated: Clock::get()?.unix_timestamp,
avg_utilization: I80F48::ZERO,
adjustment_factor: I80F48::from_num(0.02),
// 10% daily adjustment at 0% or 100% utilization
adjustment_factor: I80F48::from_num(0.004),
util0: I80F48::from_num(0.7),
rate0: I80F48::from_num(0.1),
util1: I80F48::from_num(0.8),
util1: I80F48::from_num(0.85),
rate1: I80F48::from_num(0.2),
max_rate: I80F48::from_num(2.0),
collected_fees_native: I80F48::ZERO,
loan_origination_fee_rate: I80F48::from_num(0.001),
loan_origination_fee_rate: I80F48::from_num(0.0005),
loan_fee_rate: I80F48::from_num(0.005),
maint_asset_weight: I80F48::from_num(0),
init_asset_weight: I80F48::from_num(0),
maint_liab_weight: I80F48::from_num(1.25),
init_liab_weight: I80F48::from_num(1.5),
liquidation_fee: I80F48::from_num(0.125),
maint_liab_weight: I80F48::from_num(1.4), // 2.5x
init_liab_weight: I80F48::from_num(1.8), // 1.25x
liquidation_fee: I80F48::from_num(0.2),
dust: I80F48::ZERO,
flash_loan_vault_initial: u64::MAX,
flash_loan_token_account_initial: u64::MAX,
flash_loan_approved_amount: 0,
token_index,
bump: *ctx.bumps.get("bank").ok_or(MangoError::SomeError)?,
mint_decimals: ctx.accounts.mint.decimals,
bank_num: 0,
reserved: [0; 256],
reserved: [0; 2560],
};
require_gt!(bank.max_rate, MINIMUM_MAX_RATE);
@ -117,14 +118,14 @@ pub fn token_register_trustless(
*mint_info = MintInfo {
group: ctx.accounts.group.key(),
token_index,
group_insurance_fund: 0,
padding1: Default::default(),
mint: ctx.accounts.mint.key(),
banks: Default::default(),
vaults: Default::default(),
oracle: ctx.accounts.oracle.key(),
registration_time: Clock::get()?.unix_timestamp,
group_insurance_fund: 0,
reserved: [0; 255],
reserved: [0; 2560],
};
mint_info.banks[0] = ctx.accounts.bank.key();

View File

@ -5,16 +5,25 @@ use crate::logs::{UpdateIndexLog, UpdateRateLog};
use crate::state::HOUR;
use crate::{
accounts_zerocopy::{AccountInfoRef, LoadMutZeroCopyRef, LoadZeroCopyRef},
state::{oracle_price, Bank, MintInfo},
state::{oracle_price, Bank, Group, MintInfo},
};
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 {
use solana_program::declare_id;
declare_id!("ComputeBudget111111111111111111111111111111");
}
#[derive(Accounts)]
pub struct TokenUpdateIndexAndRate<'info> {
pub group: AccountLoader<'info, Group>, // Required for group metadata parsing
#[account(
has_one = oracle
has_one = oracle,
constraint = mint_info.load()?.group.key() == group.key(),
)]
pub mint_info: AccountLoader<'info, MintInfo>,
@ -42,8 +51,10 @@ pub fn token_update_index_and_rate(ctx: Context<TokenUpdateIndexAndRate>) -> Res
// for now we just whitelist to other token_update_index_and_rate ix
// 2. we want to forbid cpi, since ix we would like to blacklist could just be called from cpi
require!(
ix.program_id == crate::id()
&& ix.data[0..8] == [131, 136, 194, 39, 11, 50, 10, 198], // token_update_index_and_rate
(ix.program_id == crate::id()
&& ix.data[0..8]
== crate::instruction::TokenUpdateIndexAndRate::discriminator())
|| (ix.program_id == compute_budget::id()),
MangoError::SomeError
);

View File

@ -125,6 +125,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
cm!(amount_i80f48 * oracle_price * QUOTE_NATIVE_TO_UI).to_num::<f32>();
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index,
indexed_position: indexed_position.to_bits(),
@ -151,6 +152,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
}
emit!(WithdrawLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
signer: ctx.accounts.owner.key(),
token_index,

View File

@ -19,16 +19,14 @@ pub mod serum3_cpi;
pub mod state;
pub mod types;
use state::{
AccountSize, OracleConfig, OrderType, PerpMarketIndex, Serum3MarketIndex, Side, TokenIndex,
};
use state::{OracleConfig, OrderType, PerpMarketIndex, Serum3MarketIndex, Side, TokenIndex};
declare_id!("m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD");
#[program]
pub mod mango_v4 {
use crate::state::{AccountSize, OracleConfig};
use crate::state::OracleConfig;
use super::*;
@ -98,6 +96,7 @@ pub mod mango_v4 {
bank_num: u64,
oracle_opt: Option<Pubkey>,
oracle_config_opt: Option<OracleConfig>,
group_insurance_fund_opt: Option<bool>,
interest_rate_params_opt: Option<InterestRateParams>,
loan_fee_rate_opt: Option<f32>,
loan_origination_fee_rate_opt: Option<f32>,
@ -112,6 +111,7 @@ pub mod mango_v4 {
bank_num,
oracle_opt,
oracle_config_opt,
group_insurance_fund_opt,
interest_rate_params_opt,
loan_fee_rate_opt,
loan_origination_fee_rate_opt,
@ -146,14 +146,31 @@ pub mod mango_v4 {
pub fn account_create(
ctx: Context<AccountCreate>,
account_num: u32,
account_size: AccountSize,
token_count: u8,
serum3_count: u8,
perp_count: u8,
perp_oo_count: u8,
name: String,
) -> Result<()> {
instructions::account_create(ctx, account_num, account_size, name)
instructions::account_create(
ctx,
account_num,
token_count,
serum3_count,
perp_count,
perp_oo_count,
name,
)
}
pub fn account_expand(ctx: Context<AccountExpand>) -> Result<()> {
instructions::account_expand(ctx)
pub fn account_expand(
ctx: Context<AccountExpand>,
token_count: u8,
serum3_count: u8,
perp_count: u8,
perp_oo_count: u8,
) -> Result<()> {
instructions::account_expand(ctx, token_count, serum3_count, perp_count, perp_oo_count)
}
pub fn account_edit(

View File

@ -4,6 +4,7 @@ use borsh::BorshSerialize;
/// Warning: This function needs 512+ bytes free on the stack
pub fn emit_perp_balances(
mango_group: Pubkey,
mango_account: Pubkey,
market_index: u64,
price: i64,
@ -11,6 +12,7 @@ pub fn emit_perp_balances(
pm: &PerpMarket,
) {
emit!(PerpBalanceLog {
mango_group,
mango_account,
market_index,
base_position: pp.base_position_lots,
@ -25,6 +27,7 @@ pub fn emit_perp_balances(
#[event]
pub struct PerpBalanceLog {
pub mango_group: Pubkey,
pub mango_account: Pubkey,
pub market_index: u64, // IDL doesn't support usize
pub base_position: i64,
@ -38,6 +41,7 @@ pub struct PerpBalanceLog {
#[event]
pub struct TokenBalanceLog {
pub mango_group: Pubkey,
pub mango_account: Pubkey,
pub token_index: u16, // IDL doesn't support usize
pub indexed_position: i128, // on client convert i128 to I80F48 easily by passing in the BN to I80F48 ctor
@ -46,14 +50,6 @@ pub struct TokenBalanceLog {
pub price: i128, // I80F48
}
#[event]
pub struct MarginTradeLog {
pub mango_account: Pubkey,
pub token_indexes: Vec<u16>,
pub pre_indexed_positions: Vec<i128>,
pub post_indexed_positions: Vec<i128>,
}
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct FlashLoanTokenDetail {
pub token_index: u16,
@ -67,12 +63,14 @@ pub struct FlashLoanTokenDetail {
#[event]
pub struct FlashLoanLog {
pub mango_group: Pubkey,
pub mango_account: Pubkey,
pub token_loan_details: Vec<FlashLoanTokenDetail>,
}
#[event]
pub struct WithdrawLog {
pub mango_group: Pubkey,
pub mango_account: Pubkey,
pub signer: Pubkey,
pub token_index: u16,
@ -82,6 +80,7 @@ pub struct WithdrawLog {
#[event]
pub struct DepositLog {
pub mango_group: Pubkey,
pub mango_account: Pubkey,
pub signer: Pubkey,
pub token_index: u16,
@ -147,6 +146,7 @@ pub struct UpdateRateLog {
#[event]
pub struct LiquidateTokenAndTokenLog {
pub mango_group: Pubkey,
pub liqee: Pubkey,
pub liqor: Pubkey,
pub asset_token_index: u16,

View File

@ -82,7 +82,7 @@ pub struct Bank {
// Collection of all fractions-of-native-tokens that got rounded away
pub dust: I80F48,
pub flash_loan_vault_initial: u64,
pub flash_loan_token_account_initial: u64,
pub flash_loan_approved_amount: u64,
// Index into TokenInfo on the group
@ -94,11 +94,11 @@ pub struct Bank {
pub bank_num: u32,
pub reserved: [u8; 256],
pub reserved: [u8; 2560],
}
const_assert_eq!(
size_of::<Bank>(),
32 + 16 + 32 * 3 + 16 + 16 * 6 + 8 * 2 + 16 * 16 + 8 * 2 + 2 + 1 + 1 + 4 + 256
32 + 16 + 32 * 3 + 16 + 16 * 6 + 8 * 2 + 16 * 16 + 8 * 2 + 2 + 1 + 1 + 4 + 2560
);
const_assert_eq!(size_of::<Bank>() % 8, 0);
@ -145,7 +145,10 @@ impl std::fmt::Debug for Bank {
"flash_loan_approved_amount",
&self.flash_loan_approved_amount,
)
.field("flash_loan_vault_initial", &self.flash_loan_vault_initial)
.field(
"flash_loan_token_account_initial",
&self.flash_loan_token_account_initial,
)
.field("reserved", &self.reserved)
.finish()
}
@ -185,11 +188,11 @@ impl Bank {
liquidation_fee: existing_bank.liquidation_fee,
dust: I80F48::ZERO,
flash_loan_approved_amount: 0,
flash_loan_vault_initial: u64::MAX,
flash_loan_token_account_initial: u64::MAX,
token_index: existing_bank.token_index,
bump: existing_bank.bump,
mint_decimals: existing_bank.mint_decimals,
reserved: [0; 256],
reserved: [0; 2560],
bank_num,
}
}
@ -501,10 +504,16 @@ impl Bank {
// interest rate legs 2 and 3 are seen as punitive legs, encouraging utilization to move towards optimal utilization
// lets choose util0 as optimal utilization and 0 to utli0 as the leg where we want the utlization to preferably be
let optimal_util = self.util0;
// use avg_utilization and not instantaneous_utilization so that rates cannot be manupulated easily
let util_diff = self.avg_utilization - optimal_util;
// use avg_utilization and not instantaneous_utilization so that rates cannot be manipulated easily
let avg_util = self.avg_utilization;
// move rates up when utilization is above optimal utilization, and vice versa
let adjustment = I80F48::ONE + self.adjustment_factor * util_diff;
// 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))
} else {
cm!((avg_util - optimal_util) / optimal_util)
};
let adjustment = cm!(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,

View File

@ -33,9 +33,9 @@ pub struct Group {
pub padding2: [u8; 5],
pub reserved: [u8; 256],
pub reserved: [u8; 2560],
}
const_assert_eq!(size_of::<Group>(), 32 * 5 + 4 + 4 + 1 + 1 + 6 + 256);
const_assert_eq!(size_of::<Group>(), 32 * 5 + 4 + 4 + 1 + 1 + 6 + 2560);
const_assert_eq!(size_of::<Group>() % 8, 0);
impl Group {

View File

@ -1,13 +1,10 @@
use std::fmt;
use std::mem::size_of;
use anchor_lang::prelude::*;
use arrayref::array_ref;
use fixed::types::I80F48;
use num_enum::IntoPrimitive;
use num_enum::TryFromPrimitive;
use solana_program::program_memory::sol_memmove;
use static_assertions::const_assert_eq;
@ -33,42 +30,6 @@ const BORSH_VEC_PADDING_BYTES: usize = 4;
const BORSH_VEC_SIZE_BYTES: usize = 4;
const DEFAULT_MANGO_ACCOUNT_VERSION: u8 = 1;
#[derive(
Debug,
Eq,
PartialEq,
Clone,
Copy,
TryFromPrimitive,
IntoPrimitive,
AnchorSerialize,
AnchorDeserialize,
)]
#[repr(u8)]
pub enum AccountSize {
Small = 0,
Large = 1,
}
impl fmt::Display for AccountSize {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
AccountSize::Small => write!(f, "Small"),
AccountSize::Large => write!(f, "Large"),
}
}
}
impl AccountSize {
pub fn space(&self) -> (u8, u8, u8, u8) {
match self {
AccountSize::Small => (8, 2, 2, 2),
AccountSize::Large => (16, 8, 8, 8),
}
}
}
// Mango Account
// This struct definition is only for clients e.g. typescript, so that they can easily use out of the box
// deserialization and not have to do custom deserialization
@ -158,11 +119,19 @@ impl Default for MangoAccount {
}
impl MangoAccount {
pub fn space(account_size: AccountSize) -> usize {
let (token_count, serum3_count, perp_count, perp_oo_count) = account_size.space();
pub fn space(
token_count: u8,
serum3_count: u8,
perp_count: u8,
perp_oo_count: u8,
) -> Result<usize> {
require_gte!(16, token_count);
require_gte!(8, serum3_count);
require_gte!(8, perp_count);
require_gte!(64, perp_oo_count);
8 + size_of::<MangoAccountFixed>()
+ Self::dynamic_size(token_count, serum3_count, perp_count, perp_oo_count)
Ok(8 + size_of::<MangoAccountFixed>()
+ Self::dynamic_size(token_count, serum3_count, perp_count, perp_oo_count))
}
pub fn dynamic_token_vec_offset() -> usize {
@ -202,7 +171,7 @@ impl MangoAccount {
#[test]
fn test_dynamic_offsets() {
let mut account = MangoAccount::default();
account.tokens.resize(16, TokenPosition::default());
account.tokens.resize(8, TokenPosition::default());
account.serum3.resize(8, Serum3Orders::default());
account.perps.resize(8, PerpPositions::default());
account
@ -210,7 +179,7 @@ fn test_dynamic_offsets() {
.resize(8, PerpOpenOrders::default());
assert_eq!(
8 + AnchorSerialize::try_to_vec(&account).unwrap().len(),
MangoAccount::space(AccountSize::Large)
MangoAccount::space(8, 8, 8, 8).unwrap()
);
}
@ -564,13 +533,6 @@ impl<
dynamic: self.dynamic(),
}
}
pub fn size(&self) -> AccountSize {
if self.header().perp_count() > 4 {
return AccountSize::Large;
}
AccountSize::Small
}
}
impl<
@ -814,7 +776,7 @@ impl<
let side = fill.taker_side.invert_side();
let (base_change, quote_change) = fill.base_quote_change(side);
pa.change_base_position(perp_market, base_change);
pa.change_base_and_entry_positions(perp_market, base_change, quote_change);
let quote = I80F48::from_num(
perp_market
.quote_lot_size
@ -856,7 +818,7 @@ impl<
let (base_change, quote_change) = fill.base_quote_change(fill.taker_side);
pa.remove_taker_trade(base_change, quote_change);
pa.change_base_position(perp_market, base_change);
pa.change_base_and_entry_positions(perp_market, base_change, quote_change);
let quote = I80F48::from_num(perp_market.quote_lot_size * quote_change);
// fees are assessed at time of trade; no need to assess fees here
@ -915,14 +877,17 @@ impl<
dst.copy_from_slice(&BorshVecLength::from(count).to_le_bytes());
}
pub fn expand_dynamic_content(&mut self, account_size: AccountSize) -> Result<()> {
let (new_token_count, new_serum3_count, new_perp_count, new_perp_oo_count) =
account_size.space();
require_gt!(new_token_count, self.header().token_count);
require_gt!(new_serum3_count, self.header().serum3_count);
require_gt!(new_perp_count, self.header().perp_count);
require_gt!(new_perp_oo_count, self.header().perp_oo_count);
pub fn expand_dynamic_content(
&mut self,
new_token_count: u8,
new_serum3_count: u8,
new_perp_count: u8,
new_perp_oo_count: u8,
) -> Result<()> {
require_gte!(new_token_count, self.header().token_count);
require_gte!(new_serum3_count, self.header().serum3_count);
require_gte!(new_perp_count, self.header().perp_count);
require_gte!(new_perp_oo_count, self.header().perp_oo_count);
// create a temp copy to compute new starting offsets
let new_header = MangoAccountDynamicHeader {
@ -937,52 +902,63 @@ impl<
// expand dynamic components by first moving existing positions, and then setting new ones to defaults
// perp oo
unsafe {
sol_memmove(
&mut dynamic[new_header.perp_oo_offset(0)],
&mut dynamic[old_header.perp_oo_offset(0)],
size_of::<PerpOpenOrders>() * old_header.perp_oo_count(),
);
}
for i in old_header.perp_oo_count..new_perp_oo_count {
*get_helper_mut(dynamic, new_header.perp_oo_offset(i.into())) =
PerpOpenOrders::default();
if new_header.perp_oo_count() > old_header.perp_oo_count() {
unsafe {
sol_memmove(
&mut dynamic[new_header.perp_oo_offset(0)],
&mut dynamic[old_header.perp_oo_offset(0)],
size_of::<PerpOpenOrders>() * old_header.perp_oo_count(),
);
}
for i in old_header.perp_oo_count..new_perp_oo_count {
*get_helper_mut(dynamic, new_header.perp_oo_offset(i.into())) =
PerpOpenOrders::default();
}
}
// perp positions
unsafe {
sol_memmove(
&mut dynamic[new_header.perp_offset(0)],
&mut dynamic[old_header.perp_offset(0)],
size_of::<PerpPositions>() * old_header.perp_count(),
);
}
for i in old_header.perp_count..new_perp_count {
*get_helper_mut(dynamic, new_header.perp_offset(i.into())) = PerpPositions::default();
if new_header.perp_count() > old_header.perp_count() {
unsafe {
sol_memmove(
&mut dynamic[new_header.perp_offset(0)],
&mut dynamic[old_header.perp_offset(0)],
size_of::<PerpPositions>() * old_header.perp_count(),
);
}
for i in old_header.perp_count..new_perp_count {
*get_helper_mut(dynamic, new_header.perp_offset(i.into())) =
PerpPositions::default();
}
}
// serum3 positions
unsafe {
sol_memmove(
&mut dynamic[new_header.serum3_offset(0)],
&mut dynamic[old_header.serum3_offset(0)],
size_of::<Serum3Orders>() * old_header.serum3_count(),
);
}
for i in old_header.serum3_count..new_serum3_count {
*get_helper_mut(dynamic, new_header.serum3_offset(i.into())) = Serum3Orders::default();
if new_header.serum3_count() > old_header.serum3_count() {
unsafe {
sol_memmove(
&mut dynamic[new_header.serum3_offset(0)],
&mut dynamic[old_header.serum3_offset(0)],
size_of::<Serum3Orders>() * old_header.serum3_count(),
);
}
for i in old_header.serum3_count..new_serum3_count {
*get_helper_mut(dynamic, new_header.serum3_offset(i.into())) =
Serum3Orders::default();
}
}
// token positions
unsafe {
sol_memmove(
&mut dynamic[new_header.token_offset(0)],
&mut dynamic[old_header.token_offset(0)],
size_of::<TokenPosition>() * old_header.token_count(),
);
}
for i in old_header.token_count..new_token_count {
*get_helper_mut(dynamic, new_header.token_offset(i.into())) = TokenPosition::default();
if new_header.token_count() > old_header.token_count() {
unsafe {
sol_memmove(
&mut dynamic[new_header.token_offset(0)],
&mut dynamic[old_header.token_offset(0)],
size_of::<TokenPosition>() * old_header.token_count(),
);
}
for i in old_header.token_count..new_token_count {
*get_helper_mut(dynamic, new_header.token_offset(i.into())) =
TokenPosition::default();
}
}
// update header

View File

@ -148,6 +148,11 @@ pub struct PerpPositions {
/// measured in native quote
pub quote_position_native: I80F48,
/// Tracks what the position is to calculate average entry & break even price
pub base_entry_lots: i64,
pub quote_entry_native: i64,
pub quote_exit_native: i64,
/// Already settled funding
pub long_settled_funding: I80F48,
pub short_settled_funding: I80F48,
@ -180,7 +185,7 @@ impl std::fmt::Debug for PerpPositions {
.finish()
}
}
const_assert_eq!(size_of::<PerpPositions>(), 8 + 8 * 5 + 3 * 16 + 64);
const_assert_eq!(size_of::<PerpPositions>(), 8 + 8 * 8 + 3 * 16 + 64);
const_assert_eq!(size_of::<PerpPositions>() % 8, 0);
unsafe impl bytemuck::Pod for PerpPositions {}
@ -192,6 +197,9 @@ impl Default for PerpPositions {
market_index: PerpMarketIndex::MAX,
base_position_lots: 0,
quote_position_native: I80F48::ZERO,
base_entry_lots: 0,
quote_entry_native: 0,
quote_exit_native: 0,
bids_base_lots: 0,
asks_base_lots: 0,
taker_base_lots: 0,
@ -257,6 +265,67 @@ impl PerpPositions {
self.long_settled_funding = perp_market.long_funding;
self.short_settled_funding = perp_market.short_funding;
}
/// Update the quote entry position
pub fn change_quote_entry(&mut self, base_change: i64, quote_change: i64) {
if base_change == 0 {
return;
}
let old_position = self.base_position_lots;
let is_increasing = old_position == 0 || old_position.signum() == base_change.signum();
match is_increasing {
true => {
self.quote_entry_native = cm!(self.quote_entry_native + quote_change);
self.base_entry_lots = cm!(self.base_entry_lots + base_change);
}
false => {
let new_position = cm!(old_position + base_change);
self.quote_exit_native = cm!(self.quote_exit_native + quote_change);
let is_overflow = old_position.signum() == -new_position.signum();
if new_position == 0 {
self.quote_entry_native = 0;
self.quote_exit_native = 0;
self.base_entry_lots = 0;
}
if is_overflow {
self.quote_entry_native = cm!(((new_position as f64) * (quote_change as f64)
/ (base_change as f64))
.round()) as i64;
self.quote_exit_native = 0;
self.base_entry_lots = new_position;
}
}
}
}
/// Change the base and quote positions as the result of a trade
pub fn change_base_and_entry_positions(
&mut self,
perp_market: &mut PerpMarket,
base_change: i64,
quote_change: i64,
) {
self.change_quote_entry(base_change, quote_change);
self.change_base_position(perp_market, base_change);
}
/// Calculate the average entry price of the position
pub fn get_avg_entry_price(&self) -> I80F48 {
if self.base_entry_lots == 0 {
return I80F48::ZERO; // TODO: What should this actually return? Error? NaN?
}
(I80F48::from(self.quote_entry_native) / I80F48::from(self.base_entry_lots)).abs()
}
/// Calculate the break even price of the position
pub fn get_break_even_price(&self) -> I80F48 {
if self.base_position_lots == 0 {
return I80F48::ZERO; // TODO: What should this actually return? Error? NaN?
}
(I80F48::from(self.quote_entry_native + self.quote_exit_native)
/ I80F48::from(self.base_position_lots))
.abs()
}
}
#[zero_copy]
@ -305,3 +374,227 @@ macro_rules! account_seeds {
}
pub use account_seeds;
#[cfg(test)]
mod tests {
use crate::state::{OracleConfig, PerpMarket};
use anchor_lang::prelude::Pubkey;
use fixed::types::I80F48;
use rand::Rng;
use super::PerpPositions;
fn create_perp_position(base_pos: i64, quote_pos: i64, entry_pos: i64) -> PerpPositions {
let mut pos = PerpPositions::default();
pos.base_position_lots = base_pos;
pos.quote_position_native = I80F48::from(quote_pos);
pos.quote_entry_native = entry_pos;
pos.quote_exit_native = 0;
pos.base_entry_lots = base_pos;
pos
}
fn create_perp_market() -> PerpMarket {
return PerpMarket {
group: Pubkey::new_unique(),
base_token_index: 0,
perp_market_index: 0,
name: Default::default(),
oracle: Pubkey::new_unique(),
oracle_config: OracleConfig {
conf_filter: I80F48::ZERO,
},
bids: Pubkey::new_unique(),
asks: Pubkey::new_unique(),
event_queue: Pubkey::new_unique(),
quote_lot_size: 1,
base_lot_size: 1,
maint_asset_weight: I80F48::from(1),
init_asset_weight: I80F48::from(1),
maint_liab_weight: I80F48::from(1),
init_liab_weight: I80F48::from(1),
liquidation_fee: I80F48::ZERO,
maker_fee: I80F48::ZERO,
taker_fee: I80F48::ZERO,
min_funding: I80F48::ZERO,
max_funding: I80F48::ZERO,
impact_quantity: 0,
long_funding: I80F48::ZERO,
short_funding: I80F48::ZERO,
funding_last_updated: 0,
open_interest: 0,
seq_num: 0,
fees_accrued: I80F48::ZERO,
bump: 0,
base_token_decimals: 0,
reserved: [0; 128],
padding1: Default::default(),
padding2: Default::default(),
registration_time: 0,
};
}
#[test]
fn test_quote_entry_long_increasing_from_zero() {
let mut market = create_perp_market();
let mut pos = create_perp_position(0, 0, 0);
// Go long 10 @ 10
pos.change_base_and_entry_positions(&mut market, 10, -100);
assert_eq!(pos.quote_entry_native, -100);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(10));
}
#[test]
fn test_quote_entry_short_increasing_from_zero() {
let mut market = create_perp_market();
let mut pos = create_perp_position(0, 0, 0);
// Go short 10 @ 10
pos.change_base_and_entry_positions(&mut market, -10, 100);
assert_eq!(pos.quote_entry_native, 100);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(10));
}
#[test]
fn test_quote_entry_long_increasing_from_long() {
let mut market = create_perp_market();
let mut pos = create_perp_position(10, -100, -100);
// Go long 10 @ 30
pos.change_base_and_entry_positions(&mut market, 10, -300);
assert_eq!(pos.quote_entry_native, -400);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(20));
}
#[test]
fn test_quote_entry_short_increasing_from_short() {
let mut market = create_perp_market();
let mut pos = create_perp_position(-10, 100, 100);
// Go short 10 @ 10
pos.change_base_and_entry_positions(&mut market, -10, 300);
assert_eq!(pos.quote_entry_native, 400);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(20));
}
#[test]
fn test_quote_entry_long_decreasing_from_short() {
let mut market = create_perp_market();
let mut pos = create_perp_position(-10, 100, 100);
// Go long 5 @ 50
pos.change_base_and_entry_positions(&mut market, 5, 250);
assert_eq!(pos.quote_entry_native, 100);
assert_eq!(pos.base_entry_lots, -10);
assert_eq!(pos.quote_exit_native, 250);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(10)); // Entry price remains the same when decreasing
}
#[test]
fn test_quote_entry_short_decreasing_from_long() {
let mut market = create_perp_market();
let mut pos = create_perp_position(10, -100, -100);
// Go short 5 @ 50
pos.change_base_and_entry_positions(&mut market, -5, -250);
assert_eq!(pos.quote_entry_native, -100);
assert_eq!(pos.base_entry_lots, 10);
assert_eq!(pos.quote_exit_native, -250);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(10)); // Entry price remains the same when decreasing
}
#[test]
fn test_quote_entry_long_close_with_short() {
let mut market = create_perp_market();
let mut pos = create_perp_position(10, -100, -100);
// Go short 10 @ 50
pos.change_base_and_entry_positions(&mut market, -10, 250);
assert_eq!(pos.quote_entry_native, 0);
assert_eq!(pos.quote_exit_native, 0);
assert_eq!(pos.base_entry_lots, 0);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(0)); // Entry price zero when no position
}
#[test]
fn test_quote_entry_short_close_with_long() {
let mut market = create_perp_market();
let mut pos = create_perp_position(-10, 100, 100);
// Go long 10 @ 50
pos.change_base_and_entry_positions(&mut market, 10, -250);
assert_eq!(pos.quote_entry_native, 0);
assert_eq!(pos.quote_exit_native, 0);
assert_eq!(pos.base_entry_lots, 0);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(0)); // Entry price zero when no position
}
#[test]
fn test_quote_entry_long_close_short_with_overflow() {
let mut market = create_perp_market();
let mut pos = create_perp_position(10, -100, -100);
// Go short 15 @ 20
pos.change_base_and_entry_positions(&mut market, -15, 300);
assert_eq!(pos.quote_entry_native, 100);
assert_eq!(pos.quote_exit_native, 0);
assert_eq!(pos.base_entry_lots, -5);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(20)); // Entry price zero when no position
}
#[test]
fn test_quote_entry_short_close_long_with_overflow() {
let mut market = create_perp_market();
let mut pos = create_perp_position(-10, 100, 100);
// Go short 15 @ 20
pos.change_base_and_entry_positions(&mut market, 15, -300);
assert_eq!(pos.quote_entry_native, -100);
assert_eq!(pos.quote_exit_native, 0);
assert_eq!(pos.base_entry_lots, 5);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(20)); // Entry price zero when no position
}
#[test]
fn test_quote_entry_break_even_price() {
let mut market = create_perp_market();
let mut pos = create_perp_position(0, 0, 0);
// Buy 11 @ 10,000
pos.change_base_and_entry_positions(&mut market, 11, -11 * 10_000);
// Sell 1 @ 12,000
pos.change_base_and_entry_positions(&mut market, -1, 12_000);
assert_eq!(pos.quote_entry_native, -11 * 10_000);
assert_eq!(pos.quote_exit_native, 12_000);
assert_eq!(pos.base_entry_lots, 11);
assert_eq!(pos.base_position_lots, 10);
assert_eq!(pos.get_break_even_price(), I80F48::from(9_800)); // We made 2k on the trade, so we can sell our contract up to a loss of 200 each
}
#[test]
fn test_quote_entry_multiple_and_reversed_changes_return_entry_to_zero() {
let mut market = create_perp_market();
let mut pos = create_perp_position(0, 0, 0);
// Generate array of random trades
let mut rng = rand::thread_rng();
let mut trades: Vec<[i64; 2]> = Vec::with_capacity(500);
for _ in 0..trades.capacity() {
let qty: i64 = rng.gen_range(-1000..=1000);
let px: f64 = rng.gen_range(0.1..=100.0);
let quote: i64 = (-qty as f64 * px).round() as i64;
trades.push([qty, quote]);
}
// Apply all of the trades going forward
trades.iter().for_each(|[qty, quote]| {
pos.change_base_and_entry_positions(&mut market, *qty, *quote);
});
// base_position should be sum of all base quantities
assert_eq!(
pos.base_position_lots,
trades.iter().map(|[qty, _]| qty).sum::<i64>()
);
// Reverse out all the trades
trades.iter().for_each(|[qty, quote]| {
pos.change_base_and_entry_positions(&mut market, -*qty, -*quote);
});
// base position should be 0
assert_eq!(pos.base_position_lots, 0);
// quote entry position should be 0
assert_eq!(pos.quote_entry_native, 0);
// quote exit should be 0
assert_eq!(pos.quote_exit_native, 0);
// base entry lots should be 0
assert_eq!(pos.base_entry_lots, 0);
}
}

View File

@ -21,20 +21,20 @@ pub struct MintInfo {
// ABI: Clients rely on this being at offset 40
pub token_index: TokenIndex,
pub padding1: [u8; 6],
pub group_insurance_fund: u8,
pub padding1: [u8; 5],
pub mint: Pubkey,
pub banks: [Pubkey; MAX_BANKS],
pub vaults: [Pubkey; MAX_BANKS],
pub oracle: Pubkey,
pub registration_time: i64,
pub group_insurance_fund: u8,
pub reserved: [u8; 255],
pub reserved: [u8; 2560],
}
const_assert_eq!(
size_of::<MintInfo>(),
MAX_BANKS * 2 * 32 + 3 * 32 + 2 + 8 + 6 + 1 + 255
MAX_BANKS * 2 * 32 + 3 * 32 + 2 + 8 + 6 + 2560
);
const_assert_eq!(size_of::<MintInfo>() % 8, 0);

View File

@ -1083,7 +1083,10 @@ impl<'keypair> ClientInstruction for GroupCloseInstruction<'keypair> {
pub struct AccountCreateInstruction<'keypair> {
pub account_num: u32,
pub account_size: AccountSize,
pub token_count: u8,
pub serum3_count: u8,
pub perp_count: u8,
pub perp_oo_count: u8,
pub group: Pubkey,
pub owner: &'keypair Keypair,
pub payer: &'keypair Keypair,
@ -1099,7 +1102,10 @@ impl<'keypair> ClientInstruction for AccountCreateInstruction<'keypair> {
let program_id = mango_v4::id();
let instruction = mango_v4::instruction::AccountCreate {
account_num: self.account_num,
account_size: self.account_size,
token_count: self.token_count,
serum3_count: self.serum3_count,
perp_count: self.perp_count,
perp_oo_count: self.perp_oo_count,
name: "my_mango_account".to_string(),
};
@ -1136,6 +1142,10 @@ pub struct AccountExpandInstruction<'keypair> {
pub group: Pubkey,
pub owner: &'keypair Keypair,
pub payer: &'keypair Keypair,
pub token_count: u8,
pub serum3_count: u8,
pub perp_count: u8,
pub perp_oo_count: u8,
}
#[async_trait::async_trait(?Send)]
impl<'keypair> ClientInstruction for AccountExpandInstruction<'keypair> {
@ -1146,7 +1156,12 @@ impl<'keypair> ClientInstruction for AccountExpandInstruction<'keypair> {
_account_loader: impl ClientAccountLoader + 'async_trait,
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = mango_v4::instruction::AccountExpand {};
let instruction = mango_v4::instruction::AccountExpand {
token_count: self.token_count,
serum3_count: self.serum3_count,
perp_count: self.perp_count,
perp_oo_count: self.perp_oo_count,
};
let account = Pubkey::find_program_address(
&[
@ -2374,6 +2389,7 @@ impl ClientInstruction for PerpConsumeEventsInstruction {
}
pub struct PerpUpdateFundingInstruction {
pub group: Pubkey,
pub perp_market: Pubkey,
pub bids: Pubkey,
pub asks: Pubkey,
@ -2391,6 +2407,7 @@ impl ClientInstruction for PerpUpdateFundingInstruction {
let program_id = mango_v4::id();
let instruction = Self::Instruction {};
let accounts = Self::Accounts {
group: self.group,
perp_market: self.perp_market,
bids: self.bids,
asks: self.asks,
@ -2444,6 +2461,7 @@ impl ClientInstruction for TokenUpdateIndexAndRateInstruction {
let mint_info: MintInfo = loader.load(&self.mint_info).await.unwrap();
let accounts = Self::Accounts {
group: mint_info.group,
mint_info: self.mint_info,
oracle: mint_info.oracle,
instructions: solana_program::sysvar::instructions::id(),

View File

@ -44,7 +44,10 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 2,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,
@ -90,7 +93,10 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,
@ -353,7 +359,10 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 2,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,
@ -399,7 +408,10 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,

View File

@ -41,7 +41,10 @@ async fn test_basic() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Small,
token_count: 8,
serum3_count: 0,
perp_count: 0,
perp_oo_count: 0,
group,
owner,
payer,
@ -55,6 +58,10 @@ async fn test_basic() -> Result<(), TransportError> {
solana,
AccountExpandInstruction {
account_num: 0,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,

View File

@ -37,7 +37,10 @@ async fn test_delegate() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,

View File

@ -37,7 +37,10 @@ async fn test_health_compute_tokens() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,
@ -102,7 +105,10 @@ async fn test_health_compute_serum() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,
@ -214,7 +220,10 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,

View File

@ -42,7 +42,10 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 2,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,
@ -98,7 +101,10 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,
@ -253,7 +259,10 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 2,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,
@ -284,7 +293,10 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,

View File

@ -51,7 +51,10 @@ async fn test_margin_trade() -> Result<(), BanksClientError> {
solana,
AccountCreateInstruction {
account_num: 1,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,
@ -94,7 +97,10 @@ async fn test_margin_trade() -> Result<(), BanksClientError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,

View File

@ -36,7 +36,10 @@ async fn test_perp() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,
@ -50,7 +53,10 @@ async fn test_perp() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 1,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,

View File

@ -38,7 +38,10 @@ async fn test_position_lifetime() -> Result<()> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,
@ -52,7 +55,10 @@ async fn test_position_lifetime() -> Result<()> {
solana,
AccountCreateInstruction {
account_num: 1,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,

View File

@ -40,7 +40,10 @@ async fn test_serum() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,

View File

@ -36,7 +36,10 @@ async fn test_token_update_index_and_rate() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 0,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,
@ -64,7 +67,10 @@ async fn test_token_update_index_and_rate() -> Result<(), TransportError> {
solana,
AccountCreateInstruction {
account_num: 1,
account_size: AccountSize::Large,
token_count: 16,
serum3_count: 8,
perp_count: 8,
perp_oo_count: 8,
group,
owner,
payer,

View File

@ -63,7 +63,7 @@ export class Bank {
initLiabWeight: I80F48Dto;
liquidationFee: I80F48Dto;
dust: I80F48Dto;
flashLoanVaultInitial: BN;
flashLoanTokenAccountInitial: BN;
flashLoanApprovedAmount: BN;
tokenIndex: number;
mintDecimals: number;
@ -102,7 +102,7 @@ export class Bank {
obj.initLiabWeight,
obj.liquidationFee,
obj.dust,
obj.flashLoanVaultInitial,
obj.flashLoanTokenAccountInitial,
obj.flashLoanApprovedAmount,
obj.tokenIndex,
obj.mintDecimals,
@ -142,7 +142,7 @@ export class Bank {
initLiabWeight: I80F48Dto,
liquidationFee: I80F48Dto,
dust: I80F48Dto,
flashLoanVaultInitial: BN,
flashLoanTokenAccountInitial: BN,
flashLoanApprovedAmount: BN,
public tokenIndex: number,
public mintDecimals: number,

View File

@ -594,8 +594,3 @@ export class EquityDto {
tokens: { tokenIndex: number; value: I80F48Dto }[];
perps: { perpMarketIndex: number; value: I80F48Dto }[];
}
export class AccountSize {
static small = { small: {} };
static large = { large: {} };
}

View File

@ -30,11 +30,7 @@ import bs58 from 'bs58';
import { Bank, MintInfo } from './accounts/bank';
import { Group } from './accounts/group';
import { I80F48 } from './accounts/I80F48';
import {
AccountSize,
MangoAccount,
MangoAccountData,
} from './accounts/mangoAccount';
import { MangoAccount, MangoAccountData } from './accounts/mangoAccount';
import { StubOracle } from './accounts/oracle';
import { OrderType, PerpMarket, Side } from './accounts/perp';
import {
@ -236,6 +232,7 @@ export class MangoClient {
tokenName: string,
oracle: PublicKey,
oracleConfFilter: number,
groupInsuranceFund: boolean,
adjustmentFactor: number,
util0: number,
rate0: number,
@ -262,6 +259,7 @@ export class MangoClient {
val: I80F48.fromNumber(oracleConfFilter).getData(),
},
} as any, // future: nested custom types dont typecheck, fix if possible?
groupInsuranceFund,
{ adjustmentFactor, util0, rate0, util1, rate1, maxRate },
loanFeeRate,
loanOriginationFeeRate,
@ -467,12 +465,11 @@ export class MangoClient {
group: Group,
ownerPk: PublicKey,
accountNumber?: number,
accountSize?: AccountSize,
name?: string,
): Promise<MangoAccount> {
let mangoAccounts = await this.getMangoAccountsForOwner(group, ownerPk);
if (mangoAccounts.length === 0) {
await this.createMangoAccount(group, accountNumber, accountSize, name);
await this.createMangoAccount(group, accountNumber, name);
mangoAccounts = await this.getMangoAccountsForOwner(group, ownerPk);
}
return mangoAccounts[0];
@ -481,15 +478,10 @@ export class MangoClient {
public async createMangoAccount(
group: Group,
accountNumber?: number,
accountSize?: AccountSize,
name?: string,
): Promise<TransactionSignature> {
return await this.program.methods
.accountCreate(
accountNumber ?? 0,
accountSize ?? AccountSize.small,
name ?? '',
)
.accountCreate(accountNumber ?? 0, 8, 0, 0, 0, name ?? '')
.accounts({
group: group.publicKey,
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
@ -501,9 +493,13 @@ export class MangoClient {
public async expandMangoAccount(
group: Group,
account: MangoAccount,
tokenCount: number,
serum3Count: number,
perpCount: number,
perpOoCount: number,
): Promise<TransactionSignature> {
return await this.program.methods
.accountExpand()
.accountExpand(tokenCount, serum3Count, perpCount, perpOoCount)
.accounts({
group: group.publicKey,
account: account.publicKey,
@ -620,7 +616,7 @@ export class MangoClient {
mangoAccount: MangoAccount,
tokenName: string,
amount: number,
) {
): Promise<TransactionSignature> {
const bank = group.banksMap.get(tokenName)!;
const tokenAccountPk = await getAssociatedTokenAddress(
@ -696,7 +692,7 @@ export class MangoClient {
tokenName: string,
amount: number,
allowBorrow: boolean,
) {
): Promise<TransactionSignature> {
const bank = group.banksMap.get(tokenName)!;
const tokenAccountPk = await getAssociatedTokenAddress(
@ -737,7 +733,7 @@ export class MangoClient {
tokenName: string,
nativeAmount: number,
allowBorrow: boolean,
) {
): Promise<TransactionSignature> {
const bank = group.banksMap.get(tokenName)!;
const tokenAccountPk = await getAssociatedTokenAddress(
@ -1559,6 +1555,28 @@ export class MangoClient {
return this.program.provider.sendAndConfirm(tx);
}
async updateIndexAndRate(group: Group, tokenName: string) {
let bank = group.banksMap.get(tokenName)!;
let mintInfo = group.mintInfosMap.get(bank.tokenIndex)!;
await this.program.methods
.tokenUpdateIndexAndRate()
.accounts({
group: group.publicKey,
mintInfo: mintInfo.publicKey,
oracle: mintInfo.oracle,
instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
})
.remainingAccounts([
{
pubkey: bank.publicKey,
isWritable: true,
isSigner: false,
} as AccountMeta,
])
.rpc();
}
/// liquidations
async liqTokenWithToken(
@ -1688,7 +1706,7 @@ export class MangoClient {
}
}
const mintInfos = [...new Set(tokenIndices.sort())].map(
const mintInfos = [...new Set(tokenIndices)].map(
(tokenIndex) => group.mintInfosMap.get(tokenIndex)!,
);
healthRemainingAccounts.push(

View File

@ -504,6 +504,12 @@ export type MangoV4 = {
}
}
},
{
"name": "groupInsuranceFundOpt",
"type": {
"option": "bool"
}
},
{
"name": "interestRateParamsOpt",
"type": {
@ -738,6 +744,11 @@ export type MangoV4 = {
{
"name": "tokenUpdateIndexAndRate",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "mintInfo",
"isMut": false,
@ -815,10 +826,20 @@ export type MangoV4 = {
"type": "u32"
},
{
"name": "accountSize",
"type": {
"defined": "AccountSize"
}
"name": "tokenCount",
"type": "u8"
},
{
"name": "serum3Count",
"type": "u8"
},
{
"name": "perpCount",
"type": "u8"
},
{
"name": "perpOoCount",
"type": "u8"
},
{
"name": "name",
@ -855,7 +876,24 @@ export type MangoV4 = {
"isSigner": false
}
],
"args": []
"args": [
{
"name": "tokenCount",
"type": "u8"
},
{
"name": "serum3Count",
"type": "u8"
},
{
"name": "perpCount",
"type": "u8"
},
{
"name": "perpOoCount",
"type": "u8"
}
]
},
{
"name": "accountEdit",
@ -2572,6 +2610,11 @@ export type MangoV4 = {
{
"name": "perpUpdateFunding",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "perpMarket",
"isMut": true,
@ -2820,7 +2863,7 @@ export type MangoV4 = {
}
},
{
"name": "flashLoanVaultInitial",
"name": "flashLoanTokenAccountInitial",
"type": "u64"
},
{
@ -3970,6 +4013,21 @@ export type MangoV4 = {
"defined": "I80F48"
}
},
{
"name": "baseEntryLots",
"docs": [
"Tracks what the position is to calculate average entry & break even price"
],
"type": "i64"
},
{
"name": "quoteEntryNative",
"type": "i64"
},
{
"name": "quoteExitNative",
"type": "i64"
},
{
"name": "longSettledFunding",
"docs": [
@ -4277,20 +4335,6 @@ export type MangoV4 = {
]
}
},
{
"name": "AccountSize",
"type": {
"kind": "enum",
"variants": [
{
"name": "Small"
},
{
"name": "Large"
}
]
}
},
{
"name": "OracleType",
"type": {
@ -4440,6 +4484,11 @@ export type MangoV4 = {
{
"name": "PerpBalanceLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
@ -4490,6 +4539,11 @@ export type MangoV4 = {
{
"name": "TokenBalanceLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
@ -4523,39 +4577,13 @@ export type MangoV4 = {
]
},
{
"name": "MarginTradeLog",
"name": "FlashLoanLog",
"fields": [
{
"name": "mangoAccount",
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "tokenIndexes",
"type": {
"vec": "u16"
},
"index": false
},
{
"name": "preIndexedPositions",
"type": {
"vec": "i128"
},
"index": false
},
{
"name": "postIndexedPositions",
"type": {
"vec": "i128"
},
"index": false
}
]
},
{
"name": "FlashLoanLog",
"fields": [
{
"name": "mangoAccount",
"type": "publicKey",
@ -4575,6 +4603,11 @@ export type MangoV4 = {
{
"name": "WithdrawLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
@ -4605,6 +4638,11 @@ export type MangoV4 = {
{
"name": "DepositLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
@ -4830,6 +4868,11 @@ export type MangoV4 = {
{
"name": "LiquidateTokenAndTokenLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",
@ -5508,6 +5551,12 @@ export const IDL: MangoV4 = {
}
}
},
{
"name": "groupInsuranceFundOpt",
"type": {
"option": "bool"
}
},
{
"name": "interestRateParamsOpt",
"type": {
@ -5742,6 +5791,11 @@ export const IDL: MangoV4 = {
{
"name": "tokenUpdateIndexAndRate",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "mintInfo",
"isMut": false,
@ -5819,10 +5873,20 @@ export const IDL: MangoV4 = {
"type": "u32"
},
{
"name": "accountSize",
"type": {
"defined": "AccountSize"
}
"name": "tokenCount",
"type": "u8"
},
{
"name": "serum3Count",
"type": "u8"
},
{
"name": "perpCount",
"type": "u8"
},
{
"name": "perpOoCount",
"type": "u8"
},
{
"name": "name",
@ -5859,7 +5923,24 @@ export const IDL: MangoV4 = {
"isSigner": false
}
],
"args": []
"args": [
{
"name": "tokenCount",
"type": "u8"
},
{
"name": "serum3Count",
"type": "u8"
},
{
"name": "perpCount",
"type": "u8"
},
{
"name": "perpOoCount",
"type": "u8"
}
]
},
{
"name": "accountEdit",
@ -7576,6 +7657,11 @@ export const IDL: MangoV4 = {
{
"name": "perpUpdateFunding",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "perpMarket",
"isMut": true,
@ -7824,7 +7910,7 @@ export const IDL: MangoV4 = {
}
},
{
"name": "flashLoanVaultInitial",
"name": "flashLoanTokenAccountInitial",
"type": "u64"
},
{
@ -8974,6 +9060,21 @@ export const IDL: MangoV4 = {
"defined": "I80F48"
}
},
{
"name": "baseEntryLots",
"docs": [
"Tracks what the position is to calculate average entry & break even price"
],
"type": "i64"
},
{
"name": "quoteEntryNative",
"type": "i64"
},
{
"name": "quoteExitNative",
"type": "i64"
},
{
"name": "longSettledFunding",
"docs": [
@ -9281,20 +9382,6 @@ export const IDL: MangoV4 = {
]
}
},
{
"name": "AccountSize",
"type": {
"kind": "enum",
"variants": [
{
"name": "Small"
},
{
"name": "Large"
}
]
}
},
{
"name": "OracleType",
"type": {
@ -9444,6 +9531,11 @@ export const IDL: MangoV4 = {
{
"name": "PerpBalanceLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
@ -9494,6 +9586,11 @@ export const IDL: MangoV4 = {
{
"name": "TokenBalanceLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
@ -9527,39 +9624,13 @@ export const IDL: MangoV4 = {
]
},
{
"name": "MarginTradeLog",
"name": "FlashLoanLog",
"fields": [
{
"name": "mangoAccount",
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "tokenIndexes",
"type": {
"vec": "u16"
},
"index": false
},
{
"name": "preIndexedPositions",
"type": {
"vec": "i128"
},
"index": false
},
{
"name": "postIndexedPositions",
"type": {
"vec": "i128"
},
"index": false
}
]
},
{
"name": "FlashLoanLog",
"fields": [
{
"name": "mangoAccount",
"type": "publicKey",
@ -9579,6 +9650,11 @@ export const IDL: MangoV4 = {
{
"name": "WithdrawLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
@ -9609,6 +9685,11 @@ export const IDL: MangoV4 = {
{
"name": "DepositLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
@ -9834,6 +9915,11 @@ export const IDL: MangoV4 = {
{
"name": "LiquidateTokenAndTokenLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",

View File

@ -200,7 +200,12 @@ async function main() {
console.log(
`Editing group, setting existing admin as fastListingAdmin to be able to add MNGO truslessly...`,
);
await client.groupEdit(group, group.admin, group.admin);
let sig = await client.groupEdit(
group,
group.admin,
new PublicKey('Efhak3qj3MiyzgJr3cUUqXXz5wr3oYHt9sPzuqJf9eBN'),
);
console.log(`sig https://explorer.solana.com/tx/${sig}?cluster=devnet`);
console.log(`Registering MNGO...`);
const mngoDevnetMint = new PublicKey(DEVNET_MINTS.get('MNGO')!);
const mngoDevnetOracle = new PublicKey(DEVNET_ORACLES.get('MNGO')!);
@ -296,6 +301,7 @@ async function main() {
'USDC',
btcDevnetOracle,
0.1,
undefined,
0.01,
0.3,
0.08,
@ -323,6 +329,7 @@ async function main() {
'USDC',
usdcDevnetOracle.publicKey,
0.1,
undefined,
0.01,
0.4,
0.07,

View File

@ -107,9 +107,8 @@ async function main() {
group,
mangoAccount,
group.findBank(token.tokenIndex)!.name,
nativeFlooredNumber,
nativeFlooredNumber - 1 /* see comment in token_withdraw in program */,
false,
user,
);
}

View File

@ -92,6 +92,14 @@ async function main() {
console.log(mangoAccount.toString());
}
if (true) {
console.log(
`...expanding mango account to have serum3 and perp position slots`,
);
await client.expandMangoAccount(group, mangoAccount, 16, 8, 8, 8);
await mangoAccount.reload(client, group);
}
if (true) {
// deposit and withdraw