Merge branch 'dev'
This commit is contained in:
commit
31b71a285d
|
@ -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"
|
||||
|
|
|
@ -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
2
anchor
|
@ -1 +1 @@
|
|||
Subproject commit 2058b6461cb0de5af90b04eb8fae4225a368251e
|
||||
Subproject commit b52f23614601652a99ec6c27aec77bd327363b31
|
|
@ -283,4 +283,8 @@ describe('mango-v4', () => {
|
|||
1000,
|
||||
);
|
||||
});
|
||||
|
||||
it('update index and rate', async () => {
|
||||
envClient.updateIndexAndRate(group, 'USDC');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -1,4 +0,0 @@
|
|||
RPC_URL=https://mango.rpcpool.com/
|
||||
PAYER_KEYPAIR=~/.config/solana/mango-mainnet.json
|
||||
GROUP=grouppubkey
|
||||
MANGO_ACCOUNT_NAME=Account
|
|
@ -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"
|
||||
|
|
|
@ -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,27 +60,37 @@ 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 token_indices_clone = token_indices.clone();
|
||||
|
||||
let res = tokio::task::spawn_blocking(move || -> anyhow::Result<()> {
|
||||
let token = client.context.token(token_index);
|
||||
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 token_name = &token.name;
|
||||
let oracle = token.mint_info.oracle;
|
||||
|
||||
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 {
|
||||
group: token.mint_info.group,
|
||||
mint_info: token.mint_info_address,
|
||||
oracle,
|
||||
instructions: solana_program::sysvar::instructions::id(),
|
||||
|
@ -89,17 +110,16 @@ pub async fn loop_update_index_and_rate(mango_client: Arc<MangoClient>, token_in
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
ix.accounts.append(&mut banks);
|
||||
ix
|
||||
})
|
||||
.send()
|
||||
.map_err(prettify_client_error);
|
||||
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,
|
||||
|
|
|
@ -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,
|
||||
)?);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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>,
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,6 +902,7 @@ impl<
|
|||
// expand dynamic components by first moving existing positions, and then setting new ones to defaults
|
||||
|
||||
// perp oo
|
||||
if new_header.perp_oo_count() > old_header.perp_oo_count() {
|
||||
unsafe {
|
||||
sol_memmove(
|
||||
&mut dynamic[new_header.perp_oo_offset(0)],
|
||||
|
@ -948,8 +914,10 @@ impl<
|
|||
*get_helper_mut(dynamic, new_header.perp_oo_offset(i.into())) =
|
||||
PerpOpenOrders::default();
|
||||
}
|
||||
}
|
||||
|
||||
// perp positions
|
||||
if new_header.perp_count() > old_header.perp_count() {
|
||||
unsafe {
|
||||
sol_memmove(
|
||||
&mut dynamic[new_header.perp_offset(0)],
|
||||
|
@ -958,10 +926,13 @@ impl<
|
|||
);
|
||||
}
|
||||
for i in old_header.perp_count..new_perp_count {
|
||||
*get_helper_mut(dynamic, new_header.perp_offset(i.into())) = PerpPositions::default();
|
||||
*get_helper_mut(dynamic, new_header.perp_offset(i.into())) =
|
||||
PerpPositions::default();
|
||||
}
|
||||
}
|
||||
|
||||
// serum3 positions
|
||||
if new_header.serum3_count() > old_header.serum3_count() {
|
||||
unsafe {
|
||||
sol_memmove(
|
||||
&mut dynamic[new_header.serum3_offset(0)],
|
||||
|
@ -970,10 +941,13 @@ impl<
|
|||
);
|
||||
}
|
||||
for i in old_header.serum3_count..new_serum3_count {
|
||||
*get_helper_mut(dynamic, new_header.serum3_offset(i.into())) = Serum3Orders::default();
|
||||
*get_helper_mut(dynamic, new_header.serum3_offset(i.into())) =
|
||||
Serum3Orders::default();
|
||||
}
|
||||
}
|
||||
|
||||
// token positions
|
||||
if new_header.token_count() > old_header.token_count() {
|
||||
unsafe {
|
||||
sol_memmove(
|
||||
&mut dynamic[new_header.token_offset(0)],
|
||||
|
@ -982,7 +956,9 @@ impl<
|
|||
);
|
||||
}
|
||||
for i in old_header.token_count..new_token_count {
|
||||
*get_helper_mut(dynamic, new_header.token_offset(i.into())) = TokenPosition::default();
|
||||
*get_helper_mut(dynamic, new_header.token_offset(i.into())) =
|
||||
TokenPosition::default();
|
||||
}
|
||||
}
|
||||
|
||||
// update header
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: {} };
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue