Merge branch 'dev'

This commit is contained in:
microwavedcola1 2023-01-04 13:23:48 +01:00
commit 3c2a55bf07
80 changed files with 1589 additions and 514 deletions

1
Procfile Normal file
View File

@ -0,0 +1 @@
mm: node dist/cjs/src/scripts/mm/market-maker.js

View File

@ -1,12 +1,23 @@
### Development ## Development
- rust version 1.59.0 (9d1b2106e 2022-02-23) ### Dependencies
- solana-cli 1.9.13
- anchor-cli 0.24.2 - rust version 1.65.0
- solana-cli 1.14.9
- npm 8.1.2 - npm 8.1.2
- node v16.13.1 - node v16.13.1
Devnet deployment - 4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg ### Submodules
After cloning this repo you'll need to init and update its git submodules.
Consider setting the git option `submodule.recurse=true`.
### Deployments
- devnet: 4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg
- mainnet-beta: 4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg
### Notes
For testing latest program changes while developing, For testing latest program changes while developing,
just run below scripts in given order form any branch, just run below scripts in given order form any branch,

View File

@ -162,7 +162,7 @@ async fn main() -> Result<(), anyhow::Error> {
let owner = client::keypair_from_cli(&cmd.owner); let owner = client::keypair_from_cli(&cmd.owner);
let mint = client::pubkey_from_cli(&cmd.mint); let mint = client::pubkey_from_cli(&cmd.mint);
let client = MangoClient::new_for_existing_account(client, account, owner).await?; let client = MangoClient::new_for_existing_account(client, account, owner).await?;
let txsig = client.token_deposit(mint, cmd.amount).await?; let txsig = client.token_deposit(mint, cmd.amount, false).await?;
println!("{}", txsig); println!("{}", txsig);
} }
Command::JupiterSwap(cmd) => { Command::JupiterSwap(cmd) => {

View File

@ -302,7 +302,12 @@ impl MangoClient {
) )
} }
pub async fn token_deposit(&self, mint: Pubkey, amount: u64) -> anyhow::Result<Signature> { pub async fn token_deposit(
&self,
mint: Pubkey,
amount: u64,
reduce_only: bool,
) -> anyhow::Result<Signature> {
let token = self.context.token_by_mint(&mint)?; let token = self.context.token_by_mint(&mint)?;
let token_index = token.token_index; let token_index = token.token_index;
let mint_info = token.mint_info; let mint_info = token.mint_info;
@ -333,6 +338,7 @@ impl MangoClient {
}, },
data: anchor_lang::InstructionData::data(&mango_v4::instruction::TokenDeposit { data: anchor_lang::InstructionData::data(&mango_v4::instruction::TokenDeposit {
amount, amount,
reduce_only,
}), }),
}; };
self.send_and_confirm_owner_tx(vec![ix]).await self.send_and_confirm_owner_tx(vec![ix]).await

View File

@ -117,7 +117,7 @@ async fn ensure_deposit(mango_client: &Arc<MangoClient>) -> Result<(), anyhow::E
log::info!("Depositing {} {}", deposit_native, bank.name()); log::info!("Depositing {} {}", deposit_native, bank.name());
mango_client mango_client
.token_deposit(bank.mint, desired_balance.to_num()) .token_deposit(bank.mint, desired_balance.to_num(), false)
.await?; .await?;
} }

View File

@ -1,7 +1,7 @@
use anchor_lang::Discriminator; use anchor_lang::Discriminator;
use arrayref::array_ref; use arrayref::array_ref;
use mango_v4::state::{Bank, MangoAccount, MangoAccountRefWithHeader, MintInfo, PerpMarket}; use mango_v4::state::{Bank, MangoAccount, MangoAccountLoadedRef, MintInfo, PerpMarket};
use solana_sdk::account::{AccountSharedData, ReadableAccount}; use solana_sdk::account::{AccountSharedData, ReadableAccount};
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
@ -10,7 +10,7 @@ pub fn is_mango_account<'a>(
account: &'a AccountSharedData, account: &'a AccountSharedData,
program_id: &Pubkey, program_id: &Pubkey,
group_id: &Pubkey, group_id: &Pubkey,
) -> Option<MangoAccountRefWithHeader<'a>> { ) -> Option<MangoAccountLoadedRef<'a>> {
let data = account.data(); let data = account.data();
if account.owner() != program_id || data.len() < 8 { if account.owner() != program_id || data.len() < 8 {
return None; return None;
@ -21,7 +21,7 @@ pub fn is_mango_account<'a>(
return None; return None;
} }
if let Ok(mango_account) = MangoAccountRefWithHeader::from_bytes(&data[8..]) { if let Ok(mango_account) = MangoAccountLoadedRef::from_bytes(&data[8..]) {
if mango_account.fixed.group != *group_id { if mango_account.fixed.group != *group_id {
return None; return None;
} }

View File

@ -37,18 +37,13 @@
"@types/bs58": "^4.0.1", "@types/bs58": "^4.0.1",
"@types/chai": "^4.3.0", "@types/chai": "^4.3.0",
"@types/mocha": "^9.1.0", "@types/mocha": "^9.1.0",
"@types/node": "^14.14.37", "@types/node": "^18.11.18",
"@typescript-eslint/eslint-plugin": "^5.32.0", "@typescript-eslint/eslint-plugin": "^5.32.0",
"@typescript-eslint/parser": "^5.32.0", "@typescript-eslint/parser": "^5.32.0",
"binance-api-node": "^0.12.0",
"chai": "^4.3.4", "chai": "^4.3.4",
"cross-fetch": "^3.1.5",
"dotenv": "^16.0.3",
"eslint": "^7.28.0", "eslint": "^7.28.0",
"eslint-config-prettier": "^7.2.0", "eslint-config-prettier": "^7.2.0",
"ftx-api": "^1.1.13",
"mocha": "^9.1.3", "mocha": "^9.1.3",
"node-kraken-api": "^2.2.2",
"prettier": "^2.0.5", "prettier": "^2.0.5",
"ts-mocha": "^10.0.0", "ts-mocha": "^10.0.0",
"ts-node": "^9.1.1", "ts-node": "^9.1.1",
@ -70,7 +65,12 @@
"@solana/web3.js": "^1.63.1", "@solana/web3.js": "^1.63.1",
"@switchboard-xyz/sbv2-lite": "^0.1.6", "@switchboard-xyz/sbv2-lite": "^0.1.6",
"big.js": "^6.1.1", "big.js": "^6.1.1",
"bs58": "^5.0.0" "bs58": "^5.0.0",
"binance-api-node": "^0.12.0",
"node-kraken-api": "^2.2.2",
"ftx-api": "^1.1.13",
"cross-fetch": "^3.1.5",
"dotenv": "^16.0.3"
}, },
"resolutions": { "resolutions": {
"@project-serum/anchor/@solana/web3.js": "1.63.1" "@project-serum/anchor/@solana/web3.js": "1.63.1"

View File

@ -65,6 +65,10 @@ pub enum MangoError {
TokenPositionDoesNotExist, TokenPositionDoesNotExist,
#[msg("token deposits into accounts that are being liquidated must bring their health above the init threshold")] #[msg("token deposits into accounts that are being liquidated must bring their health above the init threshold")]
DepositsIntoLiquidatingMustRecover, DepositsIntoLiquidatingMustRecover,
#[msg("token is in reduce only mode")]
TokenInReduceOnlyMode,
#[msg("market is in reduce only mode")]
MarketInReduceOnlyMode,
} }
impl MangoError { impl MangoError {

View File

@ -14,7 +14,7 @@ pub struct AccountClose<'info> {
has_one = owner, has_one = owner,
close = sol_destination close = sol_destination
)] )]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>, pub owner: Signer<'info>,
#[account(mut)] #[account(mut)]
@ -25,7 +25,7 @@ pub struct AccountClose<'info> {
} }
pub fn account_close(ctx: Context<AccountClose>, force_close: bool) -> Result<()> { pub fn account_close(ctx: Context<AccountClose>, force_close: bool) -> Result<()> {
let account = ctx.accounts.account.load_mut()?; let account = ctx.accounts.account.load_full_mut()?;
if !ctx.accounts.group.load()?.is_testing() { if !ctx.accounts.group.load()?.is_testing() {
require!(!force_close, MangoError::SomeError); require!(!force_close, MangoError::SomeError);

View File

@ -16,7 +16,7 @@ pub struct AccountCreate<'info> {
payer = payer, payer = payer,
space = MangoAccount::space(token_count, serum3_count, perp_count, perp_oo_count)?, space = MangoAccount::space(token_count, serum3_count, perp_count, perp_oo_count)?,
)] )]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>, pub owner: Signer<'info>,
#[account(mut)] #[account(mut)]
@ -34,7 +34,7 @@ pub fn account_create(
perp_oo_count: u8, perp_oo_count: u8,
name: String, name: String,
) -> Result<()> { ) -> Result<()> {
let mut account = ctx.accounts.account.load_init()?; let mut account = ctx.accounts.account.load_full_init()?;
msg!( msg!(
"Initialized account with header version {}", "Initialized account with header version {}",

View File

@ -13,7 +13,7 @@ pub struct AccountEdit<'info> {
has_one = group, has_one = group,
has_one = owner has_one = owner
)] )]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>, pub owner: Signer<'info>,
} }
@ -28,7 +28,7 @@ pub fn account_edit(
MangoError::SomeError MangoError::SomeError
); );
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
// note: unchanged fields are inline, and match exact definition in create_account // note: unchanged fields are inline, and match exact definition in create_account
// please maintain, and don't remove, makes it easy to reason about which support modification by owner // please maintain, and don't remove, makes it easy to reason about which support modification by owner

View File

@ -12,7 +12,7 @@ pub struct AccountExpand<'info> {
has_one = group, has_one = group,
has_one = owner has_one = owner
)] )]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>, pub owner: Signer<'info>,
#[account(mut)] #[account(mut)]
@ -53,7 +53,7 @@ pub fn account_expand(
realloc_account.realloc(new_space, false)?; realloc_account.realloc(new_space, false)?;
// expand dynamic content, e.g. to grow token positions, we need to slide serum3orders further later, and so on.... // 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()?; let mut account = ctx.accounts.account.load_full_mut()?;
account.expand_dynamic_content(token_count, serum3_count, perp_count, perp_oo_count)?; account.expand_dynamic_content(token_count, serum3_count, perp_count, perp_oo_count)?;
Ok(()) Ok(())

View File

@ -6,13 +6,13 @@ pub struct ComputeAccountData<'info> {
pub group: AccountLoader<'info, Group>, pub group: AccountLoader<'info, Group>,
#[account(has_one = group)] #[account(has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
} }
pub fn compute_account_data(ctx: Context<ComputeAccountData>) -> Result<()> { pub fn compute_account_data(ctx: Context<ComputeAccountData>) -> Result<()> {
let group_pk = ctx.accounts.group.key(); let group_pk = ctx.accounts.group.key();
let account = ctx.accounts.account.load()?; let account = ctx.accounts.account.load_full()?;
let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, &group_pk)?; let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, &group_pk)?;

View File

@ -3,8 +3,7 @@ use crate::error::*;
use crate::group_seeds; use crate::group_seeds;
use crate::health::{new_fixed_order_account_retriever, new_health_cache, AccountRetriever}; use crate::health::{new_fixed_order_account_retriever, new_health_cache, AccountRetriever};
use crate::logs::{FlashLoanLog, FlashLoanTokenDetail, TokenBalanceLog}; use crate::logs::{FlashLoanLog, FlashLoanTokenDetail, TokenBalanceLog};
use crate::state::MangoAccount; use crate::state::*;
use crate::state::{AccountLoaderDynamic, Bank, Group, TokenIndex};
use crate::util::checked_math as cm; use crate::util::checked_math as cm;
use anchor_lang::prelude::*; use anchor_lang::prelude::*;
use anchor_lang::solana_program::sysvar::instructions as tx_instructions; use anchor_lang::solana_program::sysvar::instructions as tx_instructions;
@ -32,7 +31,7 @@ pub mod jupiter_mainnet_3 {
/// 4. the mango group /// 4. the mango group
#[derive(Accounts)] #[derive(Accounts)]
pub struct FlashLoanBegin<'info> { pub struct FlashLoanBegin<'info> {
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
// owner is checked at #1 // owner is checked at #1
pub owner: Signer<'info>, pub owner: Signer<'info>,
@ -55,7 +54,7 @@ pub struct FlashLoanBegin<'info> {
#[derive(Accounts)] #[derive(Accounts)]
pub struct FlashLoanEnd<'info> { pub struct FlashLoanEnd<'info> {
#[account(mut)] #[account(mut)]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
// owner is checked at #1 // owner is checked at #1
pub owner: Signer<'info>, pub owner: Signer<'info>,
@ -75,7 +74,7 @@ pub fn flash_loan_begin<'key, 'accounts, 'remaining, 'info>(
ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoanBegin<'info>>, ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoanBegin<'info>>,
loan_amounts: Vec<u64>, loan_amounts: Vec<u64>,
) -> Result<()> { ) -> Result<()> {
let account = ctx.accounts.account.load_mut()?; let account = ctx.accounts.account.load_full_mut()?;
// account constraint #1 // account constraint #1
require!( require!(
@ -239,7 +238,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoanEnd<'info>>, ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoanEnd<'info>>,
flash_loan_type: FlashLoanType, flash_loan_type: FlashLoanType,
) -> Result<()> { ) -> Result<()> {
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
// account constraint #1 // account constraint #1
require!( require!(
@ -377,8 +376,10 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
let mut token_loan_details = Vec::with_capacity(changes.len()); let mut token_loan_details = Vec::with_capacity(changes.len());
for (change, oracle_price) in changes.iter().zip(oracle_prices.iter()) { for (change, oracle_price) in changes.iter().zip(oracle_prices.iter()) {
let mut bank = health_ais[change.bank_index].load_mut::<Bank>()?; let mut bank = health_ais[change.bank_index].load_mut::<Bank>()?;
let position = account.token_position_mut_by_raw_index(change.raw_token_index); let position = account.token_position_mut_by_raw_index(change.raw_token_index);
let native = position.native(&bank); let native = position.native(&bank);
let approved_amount = I80F48::from(bank.flash_loan_approved_amount); let approved_amount = I80F48::from(bank.flash_loan_approved_amount);
let loan = if native.is_positive() { let loan = if native.is_positive() {
@ -387,8 +388,21 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
approved_amount approved_amount
}; };
let loan_origination_fee = cm!(loan * bank.loan_origination_fee_rate);
cm!(bank.collected_fees_native += loan_origination_fee);
let change_amount = cm!(change.amount - loan_origination_fee);
let native_after_change = cm!(native + change_amount);
if bank.is_reduce_only() {
require!(
(change_amount < 0 && native_after_change >= 0)
|| (change_amount > 0 && native_after_change < 1),
MangoError::TokenInReduceOnlyMode
);
}
// Enforce min vault to deposits ratio // Enforce min vault to deposits ratio
if loan > 0 { if native_after_change < 0 {
let vault_ai = vaults let vault_ai = vaults
.iter() .iter()
.find(|vault_ai| vault_ai.key == &bank.vault) .find(|vault_ai| vault_ai.key == &bank.vault)
@ -396,9 +410,6 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
bank.enforce_min_vault_to_deposits_ratio(vault_ai)?; bank.enforce_min_vault_to_deposits_ratio(vault_ai)?;
} }
let loan_origination_fee = cm!(loan * bank.loan_origination_fee_rate);
cm!(bank.collected_fees_native += loan_origination_fee);
let is_active = bank.change_without_fee( let is_active = bank.change_without_fee(
position, position,
cm!(change.amount - loan_origination_fee), cm!(change.amount - loan_origination_fee),

View File

@ -19,7 +19,7 @@ pub struct HealthRegionBegin<'info> {
pub instructions: UncheckedAccount<'info>, pub instructions: UncheckedAccount<'info>,
#[account(mut)] #[account(mut)]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
} }
/// Ends a health region. /// Ends a health region.
@ -28,7 +28,7 @@ pub struct HealthRegionBegin<'info> {
#[derive(Accounts)] #[derive(Accounts)]
pub struct HealthRegionEnd<'info> { pub struct HealthRegionEnd<'info> {
#[account(mut)] #[account(mut)]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
} }
pub fn health_region_begin<'key, 'accounts, 'remaining, 'info>( pub fn health_region_begin<'key, 'accounts, 'remaining, 'info>(
@ -69,7 +69,7 @@ pub fn health_region_begin<'key, 'accounts, 'remaining, 'info>(
); );
} }
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
require_msg!( require_msg!(
!account.fixed.is_in_health_region(), !account.fixed.is_in_health_region(),
"account must not already be health wrapped" "account must not already be health wrapped"
@ -91,7 +91,7 @@ pub fn health_region_begin<'key, 'accounts, 'remaining, 'info>(
pub fn health_region_end<'key, 'accounts, 'remaining, 'info>( pub fn health_region_end<'key, 'accounts, 'remaining, 'info>(
ctx: Context<'key, 'accounts, 'remaining, 'info, HealthRegionEnd<'info>>, ctx: Context<'key, 'accounts, 'remaining, 'info, HealthRegionEnd<'info>>,
) -> Result<()> { ) -> Result<()> {
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
require_msg!( require_msg!(
account.fixed.is_in_health_region(), account.fixed.is_in_health_region(),
"account must be health wrapped" "account must be health wrapped"

View File

@ -32,6 +32,7 @@ pub use serum3_cancel_order::*;
pub use serum3_close_open_orders::*; pub use serum3_close_open_orders::*;
pub use serum3_create_open_orders::*; pub use serum3_create_open_orders::*;
pub use serum3_deregister_market::*; pub use serum3_deregister_market::*;
pub use serum3_edit_market::*;
pub use serum3_liq_force_cancel_orders::*; pub use serum3_liq_force_cancel_orders::*;
pub use serum3_place_order::*; pub use serum3_place_order::*;
pub use serum3_register_market::*; pub use serum3_register_market::*;
@ -84,6 +85,7 @@ mod serum3_cancel_order;
mod serum3_close_open_orders; mod serum3_close_open_orders;
mod serum3_create_open_orders; mod serum3_create_open_orders;
mod serum3_deregister_market; mod serum3_deregister_market;
mod serum3_edit_market;
mod serum3_liq_force_cancel_orders; mod serum3_liq_force_cancel_orders;
mod serum3_place_order; mod serum3_place_order;
mod serum3_register_market; mod serum3_register_market;

View File

@ -1,14 +1,14 @@
use anchor_lang::prelude::*; use anchor_lang::prelude::*;
use crate::error::MangoError; use crate::error::MangoError;
use crate::state::{AccountLoaderDynamic, BookSide, Group, MangoAccount, Orderbook, PerpMarket}; use crate::state::{BookSide, Group, MangoAccountFixed, MangoAccountLoader, Orderbook, PerpMarket};
#[derive(Accounts)] #[derive(Accounts)]
pub struct PerpCancelAllOrders<'info> { pub struct PerpCancelAllOrders<'info> {
pub group: AccountLoader<'info, Group>, pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group)] #[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>, pub owner: Signer<'info>,
#[account( #[account(
@ -25,7 +25,7 @@ pub struct PerpCancelAllOrders<'info> {
} }
pub fn perp_cancel_all_orders(ctx: Context<PerpCancelAllOrders>, limit: u8) -> Result<()> { pub fn perp_cancel_all_orders(ctx: Context<PerpCancelAllOrders>, limit: u8) -> Result<()> {
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
require!( require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError MangoError::SomeError

View File

@ -2,7 +2,7 @@ use anchor_lang::prelude::*;
use crate::error::MangoError; use crate::error::MangoError;
use crate::state::{ use crate::state::{
AccountLoaderDynamic, BookSide, Group, MangoAccount, Orderbook, PerpMarket, Side, BookSide, Group, MangoAccountFixed, MangoAccountLoader, Orderbook, PerpMarket, Side,
}; };
#[derive(Accounts)] #[derive(Accounts)]
@ -10,7 +10,7 @@ pub struct PerpCancelAllOrdersBySide<'info> {
pub group: AccountLoader<'info, Group>, pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group)] #[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>, pub owner: Signer<'info>,
#[account( #[account(
@ -31,7 +31,7 @@ pub fn perp_cancel_all_orders_by_side(
side_option: Option<Side>, side_option: Option<Side>,
limit: u8, limit: u8,
) -> Result<()> { ) -> Result<()> {
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
require_keys_eq!(account.fixed.group, ctx.accounts.group.key()); require_keys_eq!(account.fixed.group, ctx.accounts.group.key());
require!( require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
@ -40,8 +40,8 @@ pub fn perp_cancel_all_orders_by_side(
let mut perp_market = ctx.accounts.perp_market.load_mut()?; let mut perp_market = ctx.accounts.perp_market.load_mut()?;
let mut book = Orderbook { let mut book = Orderbook {
bids: ctx.accounts.bids.load_init()?, bids: ctx.accounts.bids.load_mut()?,
asks: ctx.accounts.asks.load_init()?, asks: ctx.accounts.asks.load_mut()?,
}; };
book.cancel_all_orders( book.cancel_all_orders(

View File

@ -1,14 +1,14 @@
use anchor_lang::prelude::*; use anchor_lang::prelude::*;
use crate::error::*; use crate::error::*;
use crate::state::{AccountLoaderDynamic, BookSide, Group, MangoAccount, Orderbook, PerpMarket}; use crate::state::{BookSide, Group, MangoAccountFixed, MangoAccountLoader, Orderbook, PerpMarket};
#[derive(Accounts)] #[derive(Accounts)]
pub struct PerpCancelOrder<'info> { pub struct PerpCancelOrder<'info> {
pub group: AccountLoader<'info, Group>, pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group)] #[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>, pub owner: Signer<'info>,
#[account( #[account(
@ -25,7 +25,7 @@ pub struct PerpCancelOrder<'info> {
} }
pub fn perp_cancel_order(ctx: Context<PerpCancelOrder>, order_id: u128) -> Result<()> { pub fn perp_cancel_order(ctx: Context<PerpCancelOrder>, order_id: u128) -> Result<()> {
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
require!( require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError MangoError::SomeError

View File

@ -1,14 +1,14 @@
use anchor_lang::prelude::*; use anchor_lang::prelude::*;
use crate::error::*; use crate::error::*;
use crate::state::{AccountLoaderDynamic, BookSide, Group, MangoAccount, Orderbook, PerpMarket}; use crate::state::{BookSide, Group, MangoAccountFixed, MangoAccountLoader, Orderbook, PerpMarket};
#[derive(Accounts)] #[derive(Accounts)]
pub struct PerpCancelOrderByClientOrderId<'info> { pub struct PerpCancelOrderByClientOrderId<'info> {
pub group: AccountLoader<'info, Group>, pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group)] #[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>, pub owner: Signer<'info>,
#[account( #[account(
@ -28,7 +28,7 @@ pub fn perp_cancel_order_by_client_order_id(
ctx: Context<PerpCancelOrderByClientOrderId>, ctx: Context<PerpCancelOrderByClientOrderId>,
client_order_id: u64, client_order_id: u64,
) -> Result<()> { ) -> Result<()> {
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
require!( require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError MangoError::SomeError

View File

@ -2,7 +2,7 @@ use anchor_lang::prelude::*;
use bytemuck::cast_ref; use bytemuck::cast_ref;
use crate::error::MangoError; use crate::error::MangoError;
use crate::state::{AccountLoaderDynamic, EventQueue, MangoAccount}; use crate::state::{EventQueue, MangoAccountFixed, MangoAccountLoader};
use crate::state::{EventType, FillEvent, Group, OutEvent, PerpMarket}; use crate::state::{EventType, FillEvent, Group, OutEvent, PerpMarket};
use crate::logs::{emit_perp_balances, FillLog}; use crate::logs::{emit_perp_balances, FillLog};
@ -55,9 +55,9 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
continue; continue;
} }
let mal: AccountLoaderDynamic<MangoAccount> = let mal: AccountLoader<MangoAccountFixed> =
AccountLoaderDynamic::try_from(ai)?; AccountLoader::try_from(ai)?;
let mut ma = mal.load_mut()?; let mut ma = mal.load_full_mut()?;
ma.execute_perp_maker( ma.execute_perp_maker(
perp_market.perp_market_index, perp_market.perp_market_index,
&mut perp_market, &mut perp_market,
@ -90,9 +90,9 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
continue; continue;
} }
let mal: AccountLoaderDynamic<MangoAccount> = let mal: AccountLoader<MangoAccountFixed> =
AccountLoaderDynamic::try_from(ai)?; AccountLoader::try_from(ai)?;
let mut maker = mal.load_mut()?; let mut maker = mal.load_full_mut()?;
match mango_account_ais.iter().find(|ai| ai.key == &fill.taker) { match mango_account_ais.iter().find(|ai| ai.key == &fill.taker) {
None => { None => {
@ -106,9 +106,9 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
continue; continue;
} }
let mal: AccountLoaderDynamic<MangoAccount> = let mal: AccountLoader<MangoAccountFixed> =
AccountLoaderDynamic::try_from(ai)?; AccountLoader::try_from(ai)?;
let mut taker = mal.load_mut()?; let mut taker = mal.load_full_mut()?;
maker.execute_perp_maker( maker.execute_perp_maker(
perp_market.perp_market_index, perp_market.perp_market_index,
@ -174,9 +174,8 @@ pub fn perp_consume_events(ctx: Context<PerpConsumeEvents>, limit: usize) -> Res
continue; continue;
} }
let mal: AccountLoaderDynamic<MangoAccount> = let mal: AccountLoader<MangoAccountFixed> = AccountLoader::try_from(ai)?;
AccountLoaderDynamic::try_from(ai)?; let mut ma = mal.load_full_mut()?;
let mut ma = mal.load_mut()?;
ma.remove_perp_order(out.owner_slot as usize, out.quantity)?; ma.remove_perp_order(out.owner_slot as usize, out.quantity)?;
} }

View File

@ -130,7 +130,8 @@ pub fn perp_create_market(
settle_pnl_limit_factor, settle_pnl_limit_factor,
padding3: Default::default(), padding3: Default::default(),
settle_pnl_limit_window_size_ts, settle_pnl_limit_window_size_ts,
reserved: [0; 1944], reduce_only: 0,
reserved: [0; 1943],
}; };
let oracle_price = let oracle_price =

View File

@ -12,7 +12,7 @@ pub struct PerpDeactivatePosition<'info> {
has_one = group has_one = group
// owner is checked at #1 // owner is checked at #1
)] )]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>, pub owner: Signer<'info>,
#[account(has_one = group)] #[account(has_one = group)]
@ -20,7 +20,7 @@ pub struct PerpDeactivatePosition<'info> {
} }
pub fn perp_deactivate_position(ctx: Context<PerpDeactivatePosition>) -> Result<()> { pub fn perp_deactivate_position(ctx: Context<PerpDeactivatePosition>) -> Result<()> {
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
// account constraint #1 // account constraint #1
require!( require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),

View File

@ -49,6 +49,7 @@ pub fn perp_edit_market(
stable_price_growth_limit_opt: Option<f32>, stable_price_growth_limit_opt: Option<f32>,
settle_pnl_limit_factor_opt: Option<f32>, settle_pnl_limit_factor_opt: Option<f32>,
settle_pnl_limit_window_size_ts_opt: Option<u64>, settle_pnl_limit_window_size_ts_opt: Option<u64>,
reduce_only_opt: Option<bool>,
) -> Result<()> { ) -> Result<()> {
let mut perp_market = ctx.accounts.perp_market.load_mut()?; let mut perp_market = ctx.accounts.perp_market.load_mut()?;
@ -171,6 +172,10 @@ pub fn perp_edit_market(
perp_market.settle_pnl_limit_window_size_ts = settle_pnl_limit_window_size_ts; perp_market.settle_pnl_limit_window_size_ts = settle_pnl_limit_window_size_ts;
} }
if let Some(reduce_only) = reduce_only_opt {
perp_market.reduce_only = if reduce_only { 1 } else { 0 };
};
emit!(PerpMarketMetaDataLog { emit!(PerpMarketMetaDataLog {
mango_group: ctx.accounts.group.key(), mango_group: ctx.accounts.group.key(),
perp_market: ctx.accounts.perp_market.key(), perp_market: ctx.accounts.perp_market.key(),

View File

@ -28,14 +28,14 @@ pub struct PerpLiqBankruptcy<'info> {
has_one = group has_one = group
// liqor_owner is checked at #1 // liqor_owner is checked at #1
)] )]
pub liqor: AccountLoaderDynamic<'info, MangoAccount>, pub liqor: AccountLoader<'info, MangoAccountFixed>,
pub liqor_owner: Signer<'info>, pub liqor_owner: Signer<'info>,
#[account( #[account(
mut, mut,
has_one = group has_one = group
)] )]
pub liqee: AccountLoaderDynamic<'info, MangoAccount>, pub liqee: AccountLoader<'info, MangoAccountFixed>,
#[account( #[account(
mut, mut,
@ -78,7 +78,7 @@ pub fn perp_liq_bankruptcy(ctx: Context<PerpLiqBankruptcy>, max_liab_transfer: u
let group = ctx.accounts.group.load()?; let group = ctx.accounts.group.load()?;
let group_pk = &ctx.accounts.group.key(); let group_pk = &ctx.accounts.group.key();
let mut liqor = ctx.accounts.liqor.load_mut()?; let mut liqor = ctx.accounts.liqor.load_full_mut()?;
// account constraint #1 // account constraint #1
require!( require!(
liqor liqor
@ -88,7 +88,7 @@ pub fn perp_liq_bankruptcy(ctx: Context<PerpLiqBankruptcy>, max_liab_transfer: u
); );
require!(!liqor.fixed.being_liquidated(), MangoError::BeingLiquidated); require!(!liqor.fixed.being_liquidated(), MangoError::BeingLiquidated);
let mut liqee = ctx.accounts.liqee.load_mut()?; let mut liqee = ctx.accounts.liqee.load_full_mut()?;
let mut liqee_health_cache = { let mut liqee_health_cache = {
let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk)?; let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk)?;
new_health_cache(&liqee.borrow(), &account_retriever) new_health_cache(&liqee.borrow(), &account_retriever)

View File

@ -24,11 +24,11 @@ pub struct PerpLiqBasePosition<'info> {
has_one = group has_one = group
// liqor_owner is checked at #1 // liqor_owner is checked at #1
)] )]
pub liqor: AccountLoaderDynamic<'info, MangoAccount>, pub liqor: AccountLoader<'info, MangoAccountFixed>,
pub liqor_owner: Signer<'info>, pub liqor_owner: Signer<'info>,
#[account(mut, has_one = group)] #[account(mut, has_one = group)]
pub liqee: AccountLoaderDynamic<'info, MangoAccount>, pub liqee: AccountLoader<'info, MangoAccountFixed>,
} }
pub fn perp_liq_base_position( pub fn perp_liq_base_position(
@ -37,7 +37,7 @@ pub fn perp_liq_base_position(
) -> Result<()> { ) -> Result<()> {
let group_pk = &ctx.accounts.group.key(); let group_pk = &ctx.accounts.group.key();
let mut liqor = ctx.accounts.liqor.load_mut()?; let mut liqor = ctx.accounts.liqor.load_full_mut()?;
// account constraint #1 // account constraint #1
require!( require!(
liqor liqor
@ -47,7 +47,7 @@ pub fn perp_liq_base_position(
); );
require!(!liqor.fixed.being_liquidated(), MangoError::BeingLiquidated); require!(!liqor.fixed.being_liquidated(), MangoError::BeingLiquidated);
let mut liqee = ctx.accounts.liqee.load_mut()?; let mut liqee = ctx.accounts.liqee.load_full_mut()?;
// Initial liqee health check // Initial liqee health check
let mut liqee_health_cache = { let mut liqee_health_cache = {

View File

@ -10,7 +10,7 @@ pub struct PerpLiqForceCancelOrders<'info> {
pub group: AccountLoader<'info, Group>, pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group)] #[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
#[account( #[account(
mut, mut,
@ -29,7 +29,7 @@ pub fn perp_liq_force_cancel_orders(
ctx: Context<PerpLiqForceCancelOrders>, ctx: Context<PerpLiqForceCancelOrders>,
limit: u8, limit: u8,
) -> Result<()> { ) -> Result<()> {
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
// //
// Check liqee health if liquidation is allowed // Check liqee health if liquidation is allowed

View File

@ -3,9 +3,10 @@ use anchor_lang::prelude::*;
use crate::accounts_zerocopy::*; use crate::accounts_zerocopy::*;
use crate::error::*; use crate::error::*;
use crate::health::{new_fixed_order_account_retriever, new_health_cache}; use crate::health::{new_fixed_order_account_retriever, new_health_cache};
use crate::state::MangoAccount; use crate::state::Side;
use crate::state::{ use crate::state::{
AccountLoaderDynamic, BookSide, EventQueue, Group, Order, Orderbook, PerpMarket, BookSide, EventQueue, Group, MangoAccountFixed, MangoAccountLoader, Order, Orderbook,
PerpMarket,
}; };
#[derive(Accounts)] #[derive(Accounts)]
@ -13,7 +14,7 @@ pub struct PerpPlaceOrder<'info> {
pub group: AccountLoader<'info, Group>, pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group)] #[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>, pub owner: Signer<'info>,
#[account( #[account(
@ -38,7 +39,7 @@ pub struct PerpPlaceOrder<'info> {
// TODO // TODO
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn perp_place_order(ctx: Context<PerpPlaceOrder>, order: Order, limit: u8) -> Result<()> { pub fn perp_place_order(ctx: Context<PerpPlaceOrder>, mut order: Order, limit: u8) -> Result<()> {
require_gte!(order.max_base_lots, 0); require_gte!(order.max_base_lots, 0);
require_gte!(order.max_quote_lots, 0); require_gte!(order.max_quote_lots, 0);
@ -64,7 +65,7 @@ pub fn perp_place_order(ctx: Context<PerpPlaceOrder>, order: Order, limit: u8) -
perp_market.update_funding_and_stable_price(&book, oracle_price, now_ts)?; perp_market.update_funding_and_stable_price(&book, oracle_price, now_ts)?;
} }
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
require!( require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError MangoError::SomeError
@ -109,8 +110,35 @@ pub fn perp_place_order(ctx: Context<PerpPlaceOrder>, order: Order, limit: u8) -
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap(); let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
// TODO apply reduce_only flag to compute final base_lots, also process event queue let pp = account.perp_position(perp_market_index)?;
require!(order.reduce_only == false, MangoError::SomeError); let effective_pos = pp.effective_base_position_lots();
let max_base_lots = if order.reduce_only || perp_market.is_reduce_only() {
if (order.side == Side::Bid && effective_pos >= 0)
|| (order.side == Side::Ask && effective_pos <= 0)
{
0
} else if order.side == Side::Bid {
// ignores open asks
(effective_pos + pp.bids_base_lots)
.min(0)
.abs()
.min(order.max_base_lots)
} else {
// ignores open bids
(effective_pos - pp.asks_base_lots)
.max(0)
.min(order.max_base_lots)
}
} else {
order.max_base_lots
};
if perp_market.is_reduce_only() {
require!(
order.reduce_only || max_base_lots == order.max_base_lots,
MangoError::MarketInReduceOnlyMode
)
};
order.max_base_lots = max_base_lots;
book.new_order( book.new_order(
order, order,

View File

@ -6,8 +6,7 @@ use crate::accounts_zerocopy::*;
use crate::error::*; use crate::error::*;
use crate::health::{compute_health, new_fixed_order_account_retriever, HealthType}; use crate::health::{compute_health, new_fixed_order_account_retriever, HealthType};
use crate::state::Bank; use crate::state::Bank;
use crate::state::MangoAccount; use crate::state::{Group, MangoAccountFixed, MangoAccountLoader, PerpMarket};
use crate::state::{AccountLoaderDynamic, Group, PerpMarket};
use crate::logs::{emit_perp_balances, PerpSettleFeesLog, TokenBalanceLog}; use crate::logs::{emit_perp_balances, PerpSettleFeesLog, TokenBalanceLog};
@ -20,7 +19,7 @@ pub struct PerpSettleFees<'info> {
// This account MUST have a loss // This account MUST have a loss
#[account(mut, has_one = group)] #[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
/// CHECK: Oracle can have different account types, constrained by address in perp_market /// CHECK: Oracle can have different account types, constrained by address in perp_market
pub oracle: UncheckedAccount<'info>, pub oracle: UncheckedAccount<'info>,
@ -40,7 +39,7 @@ pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: u64) ->
MangoError::MaxSettleAmountMustBeGreaterThanZero MangoError::MaxSettleAmountMustBeGreaterThanZero
); );
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
let mut bank = ctx.accounts.settle_bank.load_mut()?; let mut bank = ctx.accounts.settle_bank.load_mut()?;
let mut perp_market = ctx.accounts.perp_market.load_mut()?; let mut perp_market = ctx.accounts.perp_market.load_mut()?;
@ -105,7 +104,7 @@ pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: u64) ->
let token_position = account let token_position = account
.token_position_mut(perp_market.settle_token_index)? .token_position_mut(perp_market.settle_token_index)?
.0; .0;
bank.withdraw_with_fee( bank.withdraw_without_fee(
token_position, token_position,
settlement, settlement,
Clock::get()?.unix_timestamp.try_into().unwrap(), Clock::get()?.unix_timestamp.try_into().unwrap(),

View File

@ -7,8 +7,7 @@ use crate::error::*;
use crate::health::{new_health_cache, HealthType, ScanningAccountRetriever}; use crate::health::{new_health_cache, HealthType, ScanningAccountRetriever};
use crate::logs::{emit_perp_balances, PerpSettlePnlLog, TokenBalanceLog}; use crate::logs::{emit_perp_balances, PerpSettlePnlLog, TokenBalanceLog};
use crate::state::Bank; use crate::state::Bank;
use crate::state::MangoAccount; use crate::state::{Group, MangoAccountFixed, MangoAccountLoader, PerpMarket};
use crate::state::{AccountLoaderDynamic, Group, PerpMarket};
#[derive(Accounts)] #[derive(Accounts)]
pub struct PerpSettlePnl<'info> { pub struct PerpSettlePnl<'info> {
@ -19,7 +18,7 @@ pub struct PerpSettlePnl<'info> {
has_one = group, has_one = group,
// settler_owner is checked at #1 // settler_owner is checked at #1
)] )]
pub settler: AccountLoaderDynamic<'info, MangoAccount>, pub settler: AccountLoader<'info, MangoAccountFixed>,
pub settler_owner: Signer<'info>, pub settler_owner: Signer<'info>,
#[account(has_one = group, has_one = oracle)] #[account(has_one = group, has_one = oracle)]
@ -27,10 +26,10 @@ pub struct PerpSettlePnl<'info> {
// This account MUST be profitable // This account MUST be profitable
#[account(mut, has_one = group)] #[account(mut, has_one = group)]
pub account_a: AccountLoaderDynamic<'info, MangoAccount>, pub account_a: AccountLoader<'info, MangoAccountFixed>,
// This account MUST have a loss // This account MUST have a loss
#[account(mut, has_one = group)] #[account(mut, has_one = group)]
pub account_b: AccountLoaderDynamic<'info, MangoAccount>, pub account_b: AccountLoader<'info, MangoAccountFixed>,
/// CHECK: Oracle can have different account types, constrained by address in perp_market /// CHECK: Oracle can have different account types, constrained by address in perp_market
pub oracle: UncheckedAccount<'info>, pub oracle: UncheckedAccount<'info>,
@ -58,8 +57,8 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
) )
}; };
let mut account_a = ctx.accounts.account_a.load_mut()?; let mut account_a = ctx.accounts.account_a.load_full_mut()?;
let mut account_b = ctx.accounts.account_b.load_mut()?; let mut account_b = ctx.accounts.account_b.load_full_mut()?;
// check positions exist, for nicer error messages // check positions exist, for nicer error messages
{ {
@ -199,7 +198,10 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
let a_token_position = account_a.token_position_mut(settle_token_index)?.0; let a_token_position = account_a.token_position_mut(settle_token_index)?.0;
let b_token_position = account_b.token_position_mut(settle_token_index)?.0; let b_token_position = account_b.token_position_mut(settle_token_index)?.0;
bank.deposit(a_token_position, cm!(settlement - fee), now_ts)?; bank.deposit(a_token_position, cm!(settlement - fee), now_ts)?;
bank.withdraw_with_fee(b_token_position, settlement, now_ts, oracle_price)?; // Don't charge loan origination fees on borrows created via settling:
// Even small loan origination fees could accumulate if a perp position is
// settled back and forth repeatedly.
bank.withdraw_without_fee(b_token_position, settlement, now_ts, oracle_price)?;
emit!(TokenBalanceLog { emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(), mango_group: ctx.accounts.group.key(),
@ -223,7 +225,7 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
drop(account_a); drop(account_a);
drop(account_b); drop(account_b);
let mut settler = ctx.accounts.settler.load_mut()?; let mut settler = ctx.accounts.settler.load_full_mut()?;
// account constraint #1 // account constraint #1
require!( require!(
settler settler

View File

@ -14,7 +14,7 @@ pub struct Serum3CancelAllOrders<'info> {
has_one = group has_one = group
// owner is checked at #1 // owner is checked at #1
)] )]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>, pub owner: Signer<'info>,
#[account(mut)] #[account(mut)]
@ -51,7 +51,7 @@ pub fn serum3_cancel_all_orders(ctx: Context<Serum3CancelAllOrders>, limit: u8)
// Validation // Validation
// //
{ {
let account = ctx.accounts.account.load()?; let account = ctx.accounts.account.load_full()?;
// account constraint #1 // account constraint #1
require!( require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),

View File

@ -20,7 +20,7 @@ pub struct Serum3CancelOrder<'info> {
has_one = group has_one = group
// owner is checked at #1 // owner is checked at #1
)] )]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>, pub owner: Signer<'info>,
#[account(mut)] #[account(mut)]
@ -63,7 +63,7 @@ pub fn serum3_cancel_order(
// Validation // Validation
// //
{ {
let account = ctx.accounts.account.load()?; let account = ctx.accounts.account.load_full()?;
// account constraint #1 // account constraint #1
require!( require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),

View File

@ -12,7 +12,7 @@ pub struct Serum3CloseOpenOrders<'info> {
has_one = group has_one = group
// owner is checked at #1 // owner is checked at #1
)] )]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>, pub owner: Signer<'info>,
#[account( #[account(
@ -39,7 +39,7 @@ pub fn serum3_close_open_orders(ctx: Context<Serum3CloseOpenOrders>) -> Result<(
// //
// Validation // Validation
// //
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
// account constraint #1 // account constraint #1
require!( require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),

View File

@ -12,7 +12,7 @@ pub struct Serum3CreateOpenOrders<'info> {
has_one = group has_one = group
// owner is checked at #1 // owner is checked at #1
)] )]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>, pub owner: Signer<'info>,
#[account( #[account(
@ -51,7 +51,7 @@ pub fn serum3_create_open_orders(ctx: Context<Serum3CreateOpenOrders>) -> Result
let serum_market = ctx.accounts.serum_market.load()?; let serum_market = ctx.accounts.serum_market.load()?;
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
// account constraint #1 // account constraint #1
require!( require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),

View File

@ -0,0 +1,30 @@
use anchor_lang::prelude::*;
use crate::state::*;
#[derive(Accounts)]
#[instruction(market_index: Serum3MarketIndex)]
pub struct Serum3EditMarket<'info> {
#[account(
has_one = admin,
constraint = group.load()?.serum3_supported()
)]
pub group: AccountLoader<'info, Group>,
pub admin: Signer<'info>,
#[account(
mut,
has_one = group
)]
pub market: AccountLoader<'info, Serum3Market>,
}
pub fn serum3_edit_market(
ctx: Context<Serum3EditMarket>,
reduce_only_opt: Option<bool>,
) -> Result<()> {
if let Some(reduce_only) = reduce_only_opt {
ctx.accounts.market.load_mut()?.reduce_only = u8::from(reduce_only);
};
Ok(())
}

View File

@ -17,7 +17,7 @@ pub struct Serum3LiqForceCancelOrders<'info> {
pub group: AccountLoader<'info, Group>, pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group)] #[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
#[account(mut)] #[account(mut)]
/// CHECK: Validated inline by checking against the pubkey stored in the account at #2 /// CHECK: Validated inline by checking against the pubkey stored in the account at #2
@ -77,7 +77,7 @@ pub fn serum3_liq_force_cancel_orders(
// //
let serum_market = ctx.accounts.serum_market.load()?; let serum_market = ctx.accounts.serum_market.load()?;
{ {
let account = ctx.accounts.account.load()?; let account = ctx.accounts.account.load_full()?;
// Validate open_orders #2 // Validate open_orders #2
require!( require!(
@ -113,7 +113,7 @@ pub fn serum3_liq_force_cancel_orders(
// Check liqee health if liquidation is allowed // Check liqee health if liquidation is allowed
// //
let mut health_cache = { let mut health_cache = {
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
let retriever = let retriever =
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?; new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
let health_cache = let health_cache =
@ -146,7 +146,7 @@ pub fn serum3_liq_force_cancel_orders(
let before_oo = { let before_oo = {
let open_orders = load_open_orders_ref(ctx.accounts.open_orders.as_ref())?; let open_orders = load_open_orders_ref(ctx.accounts.open_orders.as_ref())?;
let before_oo = OpenOrdersSlim::from_oo(&open_orders); let before_oo = OpenOrdersSlim::from_oo(&open_orders);
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
let mut base_bank = ctx.accounts.base_bank.load_mut()?; let mut base_bank = ctx.accounts.base_bank.load_mut()?;
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?; let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
charge_loan_origination_fees( charge_loan_origination_fees(
@ -208,7 +208,7 @@ pub fn serum3_liq_force_cancel_orders(
require_gte!(after_quote_vault, before_quote_vault); require_gte!(after_quote_vault, before_quote_vault);
// Credit the difference in vault balances to the user's account // Credit the difference in vault balances to the user's account
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
let mut base_bank = ctx.accounts.base_bank.load_mut()?; let mut base_bank = ctx.accounts.base_bank.load_mut()?;
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?; let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
apply_vault_difference( apply_vault_difference(

View File

@ -142,7 +142,7 @@ pub struct Serum3PlaceOrder<'info> {
has_one = group has_one = group
// owner is checked at #1 // owner is checked at #1
)] )]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>, pub owner: Signer<'info>,
#[account(mut)] #[account(mut)]
@ -212,12 +212,16 @@ pub fn serum3_place_order(
limit: u16, limit: u16,
) -> Result<()> { ) -> Result<()> {
let serum_market = ctx.accounts.serum_market.load()?; let serum_market = ctx.accounts.serum_market.load()?;
require!(
!serum_market.is_reduce_only(),
MangoError::MarketInReduceOnlyMode
);
// //
// Validation // Validation
// //
{ {
let account = ctx.accounts.account.load()?; let account = ctx.accounts.account.load_full()?;
// account constraint #1 // account constraint #1
require!( require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
@ -246,7 +250,7 @@ pub fn serum3_place_order(
// //
// Pre-health computation // Pre-health computation
// //
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
let pre_health_opt = if !account.fixed.is_in_health_region() { let pre_health_opt = if !account.fixed.is_in_health_region() {
let retriever = let retriever =
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?; new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;

View File

@ -78,15 +78,16 @@ pub fn serum3_register_market(
let mut serum_market = ctx.accounts.serum_market.load_init()?; let mut serum_market = ctx.accounts.serum_market.load_init()?;
*serum_market = Serum3Market { *serum_market = Serum3Market {
name: fill_from_str(&name)?,
group: ctx.accounts.group.key(), group: ctx.accounts.group.key(),
base_token_index: base_bank.token_index,
quote_token_index: quote_bank.token_index,
reduce_only: 0,
padding1: Default::default(),
name: fill_from_str(&name)?,
serum_program: ctx.accounts.serum_program.key(), serum_program: ctx.accounts.serum_program.key(),
serum_market_external: ctx.accounts.serum_market_external.key(), serum_market_external: ctx.accounts.serum_market_external.key(),
market_index, market_index,
base_token_index: base_bank.token_index,
quote_token_index: quote_bank.token_index,
bump: *ctx.bumps.get("serum_market").ok_or(MangoError::SomeError)?, bump: *ctx.bumps.get("serum_market").ok_or(MangoError::SomeError)?,
padding1: Default::default(),
padding2: Default::default(), padding2: Default::default(),
registration_time: Clock::get()?.unix_timestamp.try_into().unwrap(), registration_time: Clock::get()?.unix_timestamp.try_into().unwrap(),
reserved: [0; 128], reserved: [0; 128],

View File

@ -20,7 +20,7 @@ pub struct Serum3SettleFunds<'info> {
has_one = group has_one = group
// owner is checked at #1 // owner is checked at #1
)] )]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>, pub owner: Signer<'info>,
#[account(mut)] #[account(mut)]
@ -76,7 +76,7 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
// Validation // Validation
// //
{ {
let account = ctx.accounts.account.load()?; let account = ctx.accounts.account.load_full()?;
// account constraint #1 // account constraint #1
require!( require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
@ -119,7 +119,7 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
{ {
let open_orders = load_open_orders_ref(ctx.accounts.open_orders.as_ref())?; let open_orders = load_open_orders_ref(ctx.accounts.open_orders.as_ref())?;
let before_oo = OpenOrdersSlim::from_oo(&open_orders); let before_oo = OpenOrdersSlim::from_oo(&open_orders);
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
let mut base_bank = ctx.accounts.base_bank.load_mut()?; let mut base_bank = ctx.accounts.base_bank.load_mut()?;
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?; let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
charge_loan_origination_fees( charge_loan_origination_fees(
@ -155,7 +155,7 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
require_gte!(after_quote_vault, before_quote_vault); require_gte!(after_quote_vault, before_quote_vault);
// Credit the difference in vault balances to the user's account // Credit the difference in vault balances to the user's account
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
let mut base_bank = ctx.accounts.base_bank.load_mut()?; let mut base_bank = ctx.accounts.base_bank.load_mut()?;
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?; let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
apply_vault_difference( apply_vault_difference(

View File

@ -18,7 +18,7 @@ pub struct TokenDepositIntoExisting<'info> {
pub group: AccountLoader<'info, Group>, pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group)] #[account(mut, has_one = group)]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
#[account( #[account(
mut, mut,
@ -48,7 +48,7 @@ pub struct TokenDeposit<'info> {
pub group: AccountLoader<'info, Group>, pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group, has_one = owner)] #[account(mut, has_one = group, has_one = owner)]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>, pub owner: Signer<'info>,
#[account( #[account(
@ -76,7 +76,7 @@ pub struct TokenDeposit<'info> {
struct DepositCommon<'a, 'info> { struct DepositCommon<'a, 'info> {
pub group: &'a AccountLoader<'info, Group>, pub group: &'a AccountLoader<'info, Group>,
pub account: &'a AccountLoaderDynamic<'info, MangoAccount>, pub account: &'a AccountLoader<'info, MangoAccountFixed>,
pub bank: &'a AccountLoader<'info, Bank>, pub bank: &'a AccountLoader<'info, Bank>,
pub vault: &'a Account<'info, TokenAccount>, pub vault: &'a Account<'info, TokenAccount>,
pub oracle: &'a UncheckedAccount<'info>, pub oracle: &'a UncheckedAccount<'info>,
@ -100,20 +100,44 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
&self, &self,
remaining_accounts: &[AccountInfo], remaining_accounts: &[AccountInfo],
amount: u64, amount: u64,
reduce_only: bool,
allow_token_account_closure: bool, allow_token_account_closure: bool,
) -> Result<()> { ) -> Result<()> {
require_msg!(amount > 0, "deposit amount must be positive"); require_msg!(amount > 0, "deposit amount must be positive");
let token_index = self.bank.load()?.token_index; let mut bank = self.bank.load_mut()?;
let token_index = bank.token_index;
let amount_i80f48 = {
// Get the account's position for that token index
let account = self.account.load_full()?;
let position = account.token_position(token_index)?;
let amount_i80f48 = if reduce_only || bank.is_reduce_only() {
position
.native(&bank)
.min(I80F48::ZERO)
.abs()
.ceil()
.min(I80F48::from(amount))
} else {
I80F48::from(amount)
};
if bank.is_reduce_only() {
require!(
reduce_only || amount_i80f48 == I80F48::from(amount),
MangoError::TokenInReduceOnlyMode
);
}
amount_i80f48
};
// Get the account's position for that token index // Get the account's position for that token index
let mut account = self.account.load_mut()?; let mut account = self.account.load_full_mut()?;
let (position, raw_token_index) = account.token_position_mut(token_index)?; let (position, raw_token_index) = account.token_position_mut(token_index)?;
let amount_i80f48 = I80F48::from(amount);
let position_is_active = { let position_is_active = {
let mut bank = self.bank.load_mut()?;
bank.deposit( bank.deposit(
position, position,
amount_i80f48, amount_i80f48,
@ -122,10 +146,9 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
}; };
// Transfer the actual tokens // Transfer the actual tokens
token::transfer(self.transfer_ctx(), amount)?; token::transfer(self.transfer_ctx(), amount_i80f48.to_num::<u64>())?;
let indexed_position = position.indexed_position; let indexed_position = position.indexed_position;
let bank = self.bank.load()?;
let oracle_price = bank.oracle_price( let oracle_price = bank.oracle_price(
&AccountInfoRef::borrow(self.oracle.as_ref())?, &AccountInfoRef::borrow(self.oracle.as_ref())?,
None, // staleness checked in health None, // staleness checked in health
@ -143,6 +166,7 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
deposit_index: bank.deposit_index.to_bits(), deposit_index: bank.deposit_index.to_bits(),
borrow_index: bank.borrow_index.to_bits(), borrow_index: bank.borrow_index.to_bits(),
}); });
drop(bank);
// //
// Health computation // Health computation
@ -187,10 +211,10 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
} }
} }
pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> { pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64, reduce_only: bool) -> Result<()> {
{ {
let token_index = ctx.accounts.bank.load()?.token_index; let token_index = ctx.accounts.bank.load()?.token_index;
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
account.ensure_token_position(token_index)?; account.ensure_token_position(token_index)?;
} }
@ -204,12 +228,13 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
token_authority: &ctx.accounts.token_authority, token_authority: &ctx.accounts.token_authority,
token_program: &ctx.accounts.token_program, token_program: &ctx.accounts.token_program,
} }
.deposit_into_existing(ctx.remaining_accounts, amount, true) .deposit_into_existing(ctx.remaining_accounts, amount, reduce_only, true)
} }
pub fn token_deposit_into_existing( pub fn token_deposit_into_existing(
ctx: Context<TokenDepositIntoExisting>, ctx: Context<TokenDepositIntoExisting>,
amount: u64, amount: u64,
reduce_only: bool,
) -> Result<()> { ) -> Result<()> {
DepositCommon { DepositCommon {
group: &ctx.accounts.group, group: &ctx.accounts.group,
@ -221,5 +246,5 @@ pub fn token_deposit_into_existing(
token_authority: &ctx.accounts.token_authority, token_authority: &ctx.accounts.token_authority,
token_program: &ctx.accounts.token_program, token_program: &ctx.accounts.token_program,
} }
.deposit_into_existing(ctx.remaining_accounts, amount, false) .deposit_into_existing(ctx.remaining_accounts, amount, reduce_only, false)
} }

View File

@ -56,6 +56,7 @@ pub fn token_edit(
deposit_weight_scale_start_quote_opt: Option<f64>, deposit_weight_scale_start_quote_opt: Option<f64>,
reset_stable_price: bool, reset_stable_price: bool,
reset_net_borrow_limit: bool, reset_net_borrow_limit: bool,
reduce_only_opt: Option<bool>,
) -> Result<()> { ) -> Result<()> {
let mut mint_info = ctx.accounts.mint_info.load_mut()?; let mut mint_info = ctx.accounts.mint_info.load_mut()?;
mint_info.verify_banks_ais(ctx.remaining_accounts)?; mint_info.verify_banks_ais(ctx.remaining_accounts)?;
@ -170,6 +171,10 @@ pub fn token_edit(
bank.deposit_weight_scale_start_quote = deposit_weight_scale_start_quote; bank.deposit_weight_scale_start_quote = deposit_weight_scale_start_quote;
} }
if let Some(reduce_only) = reduce_only_opt {
bank.reduce_only = if reduce_only { 1 } else { 0 };
};
// unchanged - // unchanged -
// dust // dust
// flash_loan_token_account_initial // flash_loan_token_account_initial

View File

@ -30,14 +30,14 @@ pub struct TokenLiqBankruptcy<'info> {
has_one = group has_one = group
// liqor_owner is checked at #1 // liqor_owner is checked at #1
)] )]
pub liqor: AccountLoaderDynamic<'info, MangoAccount>, pub liqor: AccountLoader<'info, MangoAccountFixed>,
pub liqor_owner: Signer<'info>, pub liqor_owner: Signer<'info>,
#[account( #[account(
mut, mut,
has_one = group has_one = group
)] )]
pub liqee: AccountLoaderDynamic<'info, MangoAccount>, pub liqee: AccountLoader<'info, MangoAccountFixed>,
#[account( #[account(
has_one = group, has_one = group,
@ -81,7 +81,7 @@ pub fn token_liq_bankruptcy(
let (bank_ais, health_ais) = &ctx.remaining_accounts.split_at(liab_mint_info.num_banks()); let (bank_ais, health_ais) = &ctx.remaining_accounts.split_at(liab_mint_info.num_banks());
liab_mint_info.verify_banks_ais(bank_ais)?; liab_mint_info.verify_banks_ais(bank_ais)?;
let mut liqor = ctx.accounts.liqor.load_mut()?; let mut liqor = ctx.accounts.liqor.load_full_mut()?;
// account constraint #1 // account constraint #1
require!( require!(
liqor liqor
@ -93,7 +93,7 @@ pub fn token_liq_bankruptcy(
let mut account_retriever = ScanningAccountRetriever::new(health_ais, group_pk)?; let mut account_retriever = ScanningAccountRetriever::new(health_ais, group_pk)?;
let mut liqee = ctx.accounts.liqee.load_mut()?; let mut liqee = ctx.accounts.liqee.load_full_mut()?;
let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever) let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever)
.context("create liqee health cache")?; .context("create liqee health cache")?;
require!( require!(

View File

@ -20,14 +20,14 @@ pub struct TokenLiqWithToken<'info> {
has_one = group has_one = group
// liqor_owner is checked at #1 // liqor_owner is checked at #1
)] )]
pub liqor: AccountLoaderDynamic<'info, MangoAccount>, pub liqor: AccountLoader<'info, MangoAccountFixed>,
pub liqor_owner: Signer<'info>, pub liqor_owner: Signer<'info>,
#[account( #[account(
mut, mut,
has_one = group has_one = group
)] )]
pub liqee: AccountLoaderDynamic<'info, MangoAccount>, pub liqee: AccountLoader<'info, MangoAccountFixed>,
} }
pub fn token_liq_with_token( pub fn token_liq_with_token(
@ -42,7 +42,7 @@ pub fn token_liq_with_token(
let mut account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk) let mut account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk)
.context("create account retriever")?; .context("create account retriever")?;
let mut liqor = ctx.accounts.liqor.load_mut()?; let mut liqor = ctx.accounts.liqor.load_full_mut()?;
// account constraint #1 // account constraint #1
require!( require!(
liqor liqor
@ -52,7 +52,7 @@ pub fn token_liq_with_token(
); );
require!(!liqor.fixed.being_liquidated(), MangoError::BeingLiquidated); require!(!liqor.fixed.being_liquidated(), MangoError::BeingLiquidated);
let mut liqee = ctx.accounts.liqee.load_mut()?; let mut liqee = ctx.accounts.liqee.load_full_mut()?;
// Initial liqee health check // Initial liqee health check
let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever) let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever)

View File

@ -150,7 +150,8 @@ pub fn token_register(
net_borrows_in_window: 0, net_borrows_in_window: 0,
borrow_weight_scale_start_quote: f64::MAX, borrow_weight_scale_start_quote: f64::MAX,
deposit_weight_scale_start_quote: f64::MAX, deposit_weight_scale_start_quote: f64::MAX,
reserved: [0; 2120], reduce_only: 0,
reserved: [0; 2119],
}; };
require_gt!(bank.max_rate, MINIMUM_MAX_RATE); require_gt!(bank.max_rate, MINIMUM_MAX_RATE);

View File

@ -82,6 +82,12 @@ pub fn token_register_trustless(
mint: ctx.accounts.mint.key(), mint: ctx.accounts.mint.key(),
vault: ctx.accounts.vault.key(), vault: ctx.accounts.vault.key(),
oracle: ctx.accounts.oracle.key(), oracle: ctx.accounts.oracle.key(),
oracle_config: OracleConfig {
conf_filter: I80F48::from_num(0.10),
max_staleness_slots: -1,
reserved: [0; 72],
},
stable_price_model: StablePriceModel::default(),
deposit_index: INDEX_START, deposit_index: INDEX_START,
borrow_index: INDEX_START, borrow_index: INDEX_START,
indexed_deposits: I80F48::ZERO, indexed_deposits: I80F48::ZERO,
@ -111,12 +117,6 @@ pub fn token_register_trustless(
bump: *ctx.bumps.get("bank").ok_or(MangoError::SomeError)?, bump: *ctx.bumps.get("bank").ok_or(MangoError::SomeError)?,
mint_decimals: ctx.accounts.mint.decimals, mint_decimals: ctx.accounts.mint.decimals,
bank_num: 0, bank_num: 0,
oracle_config: OracleConfig {
conf_filter: I80F48::from_num(0.10),
max_staleness_slots: -1,
reserved: [0; 72],
},
stable_price_model: StablePriceModel::default(),
min_vault_to_deposits_ratio: 0.2, min_vault_to_deposits_ratio: 0.2,
net_borrow_limit_window_size_ts, net_borrow_limit_window_size_ts,
last_net_borrows_window_start_ts: now_ts / net_borrow_limit_window_size_ts last_net_borrows_window_start_ts: now_ts / net_borrow_limit_window_size_ts
@ -125,7 +125,8 @@ pub fn token_register_trustless(
net_borrows_in_window: 0, net_borrows_in_window: 0,
borrow_weight_scale_start_quote: f64::MAX, borrow_weight_scale_start_quote: f64::MAX,
deposit_weight_scale_start_quote: f64::MAX, deposit_weight_scale_start_quote: f64::MAX,
reserved: [0; 2120], reduce_only: 0,
reserved: [0; 2119],
}; };
require_gt!(bank.max_rate, MINIMUM_MAX_RATE); require_gt!(bank.max_rate, MINIMUM_MAX_RATE);

View File

@ -18,7 +18,7 @@ pub struct TokenWithdraw<'info> {
pub group: AccountLoader<'info, Group>, pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group, has_one = owner)] #[account(mut, has_one = group, has_one = owner)]
pub account: AccountLoaderDynamic<'info, MangoAccount>, pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>, pub owner: Signer<'info>,
#[account( #[account(
@ -62,7 +62,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
let token_index = ctx.accounts.bank.load()?.token_index; let token_index = ctx.accounts.bank.load()?.token_index;
// Create the account's position for that token index // Create the account's position for that token index
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_full_mut()?;
let (_, raw_token_index, _) = account.ensure_token_position(token_index)?; let (_, raw_token_index, _) = account.ensure_token_position(token_index)?;
// Health check _after_ the token position is guaranteed to exist // Health check _after_ the token position is guaranteed to exist
@ -96,6 +96,9 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
let is_borrow = amount > native_position; let is_borrow = amount > native_position;
require!(allow_borrow || !is_borrow, MangoError::SomeError); require!(allow_borrow || !is_borrow, MangoError::SomeError);
if bank.is_reduce_only() {
require!(!is_borrow, MangoError::TokenInReduceOnlyMode);
}
let amount_i80f48 = I80F48::from(amount); let amount_i80f48 = I80F48::from(amount);

View File

@ -130,6 +130,7 @@ pub mod mango_v4 {
deposit_weight_scale_start_quote_opt: Option<f64>, deposit_weight_scale_start_quote_opt: Option<f64>,
reset_stable_price: bool, reset_stable_price: bool,
reset_net_borrow_limit: bool, reset_net_borrow_limit: bool,
reduce_only_opt: Option<bool>,
) -> Result<()> { ) -> Result<()> {
instructions::token_edit( instructions::token_edit(
ctx, ctx,
@ -154,6 +155,7 @@ pub mod mango_v4 {
deposit_weight_scale_start_quote_opt, deposit_weight_scale_start_quote_opt,
reset_stable_price, reset_stable_price,
reset_net_borrow_limit, reset_net_borrow_limit,
reduce_only_opt,
) )
} }
@ -235,15 +237,16 @@ pub mod mango_v4 {
instructions::stub_oracle_set(ctx, price) instructions::stub_oracle_set(ctx, price)
} }
pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> { pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64, reduce_only: bool) -> Result<()> {
instructions::token_deposit(ctx, amount) instructions::token_deposit(ctx, amount, reduce_only)
} }
pub fn token_deposit_into_existing( pub fn token_deposit_into_existing(
ctx: Context<TokenDepositIntoExisting>, ctx: Context<TokenDepositIntoExisting>,
amount: u64, amount: u64,
reduce_only: bool,
) -> Result<()> { ) -> Result<()> {
instructions::token_deposit_into_existing(ctx, amount) instructions::token_deposit_into_existing(ctx, amount, reduce_only)
} }
pub fn token_withdraw( pub fn token_withdraw(
@ -292,6 +295,13 @@ pub mod mango_v4 {
instructions::serum3_register_market(ctx, market_index, name) instructions::serum3_register_market(ctx, market_index, name)
} }
pub fn serum3_edit_market(
ctx: Context<Serum3EditMarket>,
reduce_only_opt: Option<bool>,
) -> Result<()> {
instructions::serum3_edit_market(ctx, reduce_only_opt)
}
// note: // note:
// pub fn serum3_edit_market - doesn't exist since a mango serum3 market only contains the properties // pub fn serum3_edit_market - doesn't exist since a mango serum3 market only contains the properties
// registered base and quote token pairs, and serum3 external market its pointing to, and none of them // registered base and quote token pairs, and serum3 external market its pointing to, and none of them
@ -491,6 +501,7 @@ pub mod mango_v4 {
stable_price_growth_limit_opt: Option<f32>, stable_price_growth_limit_opt: Option<f32>,
settle_pnl_limit_factor_opt: Option<f32>, settle_pnl_limit_factor_opt: Option<f32>,
settle_pnl_limit_window_size_ts: Option<u64>, settle_pnl_limit_window_size_ts: Option<u64>,
reduce_only_opt: Option<bool>,
) -> Result<()> { ) -> Result<()> {
instructions::perp_edit_market( instructions::perp_edit_market(
ctx, ctx,
@ -518,6 +529,7 @@ pub mod mango_v4 {
stable_price_growth_limit_opt, stable_price_growth_limit_opt,
settle_pnl_limit_factor_opt, settle_pnl_limit_factor_opt,
settle_pnl_limit_window_size_ts, settle_pnl_limit_window_size_ts,
reduce_only_opt,
) )
} }

View File

@ -127,8 +127,10 @@ pub struct Bank {
/// See scaled_init_asset_weight(). /// See scaled_init_asset_weight().
pub deposit_weight_scale_start_quote: f64, pub deposit_weight_scale_start_quote: f64,
pub reduce_only: u8,
#[derivative(Debug = "ignore")] #[derivative(Debug = "ignore")]
pub reserved: [u8; 2120], pub reserved: [u8; 2119],
} }
const_assert_eq!( const_assert_eq!(
size_of::<Bank>(), size_of::<Bank>(),
@ -155,7 +157,8 @@ const_assert_eq!(
+ 8 * 4 + 8 * 4
+ 8 + 8
+ 8 + 8
+ 2120 + 1
+ 2119
); );
const_assert_eq!(size_of::<Bank>(), 3064); const_assert_eq!(size_of::<Bank>(), 3064);
const_assert_eq!(size_of::<Bank>() % 8, 0); const_assert_eq!(size_of::<Bank>() % 8, 0);
@ -215,7 +218,8 @@ impl Bank {
net_borrows_in_window: 0, net_borrows_in_window: 0,
borrow_weight_scale_start_quote: f64::MAX, borrow_weight_scale_start_quote: f64::MAX,
deposit_weight_scale_start_quote: f64::MAX, deposit_weight_scale_start_quote: f64::MAX,
reserved: [0; 2120], reduce_only: 0,
reserved: [0; 2119],
} }
} }
@ -225,6 +229,10 @@ impl Bank {
.trim_matches(char::from(0)) .trim_matches(char::from(0))
} }
pub fn is_reduce_only(&self) -> bool {
self.reduce_only == 1
}
#[inline(always)] #[inline(always)]
pub fn native_borrows(&self) -> I80F48 { pub fn native_borrows(&self) -> I80F48 {
cm!(self.borrow_index * self.indexed_borrows) cm!(self.borrow_index * self.indexed_borrows)

View File

@ -1,25 +1,15 @@
use std::cell::{Ref, RefMut}; use std::cell::{Ref, RefMut};
use std::marker::PhantomData;
use std::mem::size_of;
use anchor_lang::prelude::*; use anchor_lang::prelude::*;
use anchor_lang::Discriminator;
use arrayref::array_ref;
// Header is created by scanning and parsing dynamic portion of the account /// Header is created by scanning and parsing the dynamic portion of the account.
// Header stores useful information e.g. offsets to easily seek into dynamic content /// This stores useful information e.g. offsets to easily seek into dynamic content.
pub trait DynamicHeader: Sized { pub trait DynamicHeader: Sized {
// build header by scanning and parsing dynamic portion of the account /// Builds header by scanning and parsing the dynamic portion of the account.
fn from_bytes(data: &[u8]) -> Result<Self>; fn from_bytes(dynamic_data: &[u8]) -> Result<Self>;
// initialize a header on a new account, if necessary /// initializes a header on the dynamic portion of a new account
fn initialize(data: &mut [u8]) -> Result<()>; fn initialize(dynamic_data: &mut [u8]) -> Result<()>;
}
pub trait DynamicAccountType: Owner + Discriminator {
type Header: DynamicHeader;
type Fixed: bytemuck::Pod;
} }
#[derive(Clone)] #[derive(Clone)]
@ -29,19 +19,6 @@ pub struct DynamicAccount<Header, Fixed, Dynamic> {
pub dynamic: Dynamic, pub dynamic: Dynamic,
} }
pub type DynamicAccountValue<D> =
DynamicAccount<<D as DynamicAccountType>::Header, <D as DynamicAccountType>::Fixed, Vec<u8>>;
pub type DynamicAccountRef<'a, D> = DynamicAccount<
&'a <D as DynamicAccountType>::Header,
&'a <D as DynamicAccountType>::Fixed,
&'a [u8],
>;
pub type DynamicAccountRefMut<'a, D> = DynamicAccount<
&'a mut <D as DynamicAccountType>::Header,
&'a mut <D as DynamicAccountType>::Fixed,
&'a mut [u8],
>;
// Want to generalize over: // Want to generalize over:
// - T (which is Borrow<T>) // - T (which is Borrow<T>)
// - &T (which is Borrow<T> and Deref<Target=T>) // - &T (which is Borrow<T> and Deref<Target=T>)
@ -113,199 +90,3 @@ impl<T: Sized> DerefOrBorrowMut<[T]> for Vec<T> {
self self
} }
} }
pub struct AccountLoaderDynamic<'info, D: DynamicAccountType> {
/// CHECK: is checked below
acc_info: AccountInfo<'info>,
phantom1: PhantomData<&'info D>,
}
impl<'info, D: DynamicAccountType> AccountLoaderDynamic<'info, D> {
pub fn try_from(acc_info: &AccountInfo<'info>) -> Result<Self> {
if acc_info.owner != &D::owner() {
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
.with_pubkeys((*acc_info.owner, D::owner())));
}
let data = acc_info.try_borrow_data()?;
if data.len() < D::discriminator().len() {
return Err(ErrorCode::AccountDiscriminatorNotFound.into());
}
let disc_bytes = array_ref![data, 0, 8];
if disc_bytes != &D::discriminator() {
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
}
Ok(Self {
acc_info: acc_info.clone(),
phantom1: PhantomData,
})
}
pub fn try_from_unchecked(acc_info: &AccountInfo<'info>) -> Result<Self> {
if acc_info.owner != &D::owner() {
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
.with_pubkeys((*acc_info.owner, D::owner())));
}
Ok(Self {
acc_info: acc_info.clone(),
phantom1: PhantomData,
})
}
/// Returns a Ref to the account data structure for reading.
pub fn load_fixed(&self) -> Result<Ref<D::Fixed>> {
let data = self.acc_info.try_borrow_data()?;
let fixed = Ref::map(data, |d| {
bytemuck::from_bytes(&d[8..8 + size_of::<D::Fixed>()])
});
Ok(fixed)
}
#[allow(clippy::type_complexity)]
/// Returns a Ref to the account data structure for reading.
pub fn load(&self) -> Result<DynamicAccount<D::Header, Ref<D::Fixed>, Ref<[u8]>>> {
let data = self.acc_info.try_borrow_data()?;
let header = D::Header::from_bytes(&data[8 + size_of::<D::Fixed>()..])?;
let (_, data) = Ref::map_split(data, |d| d.split_at(8));
let (fixed_bytes, dynamic) = Ref::map_split(data, |d| d.split_at(size_of::<D::Fixed>()));
Ok(DynamicAccount {
header,
fixed: Ref::map(fixed_bytes, |b| bytemuck::from_bytes(b)),
dynamic,
})
}
#[allow(clippy::type_complexity)]
pub fn load_init(&self) -> Result<DynamicAccount<D::Header, RefMut<D::Fixed>, RefMut<[u8]>>> {
if !self.acc_info.is_writable {
return Err(ErrorCode::AccountNotMutable.into());
}
let mut data = self.acc_info.try_borrow_mut_data()?;
let mut disc_bytes = [0u8; 8];
disc_bytes.copy_from_slice(&data[..8]);
let discriminator = u64::from_le_bytes(disc_bytes);
if discriminator != 0 {
return Err(ErrorCode::AccountDiscriminatorAlreadySet.into());
}
let disc_bytes: &mut [u8] = &mut data[0..8];
disc_bytes.copy_from_slice(bytemuck::bytes_of(&(D::discriminator())));
D::Header::initialize(&mut data[8 + size_of::<D::Fixed>()..])?;
drop(data);
self.load_mut()
}
/// Returns a Ref to the account data structure for reading.
#[allow(clippy::type_complexity)]
pub fn load_mut(&self) -> Result<DynamicAccount<D::Header, RefMut<D::Fixed>, RefMut<[u8]>>> {
if !self.acc_info.is_writable {
return Err(ErrorCode::AccountNotMutable.into());
}
let data = self.acc_info.try_borrow_mut_data()?;
let header = D::Header::from_bytes(&data[8 + size_of::<D::Fixed>()..])?;
let (_, data) = RefMut::map_split(data, |d| d.split_at_mut(8));
let (fixed_bytes, dynamic) =
RefMut::map_split(data, |d| d.split_at_mut(size_of::<D::Fixed>()));
Ok(DynamicAccount {
header,
fixed: RefMut::map(fixed_bytes, |b| bytemuck::from_bytes_mut(b)),
dynamic,
})
}
}
impl<'info, D: DynamicAccountType> anchor_lang::Accounts<'info> for AccountLoaderDynamic<'info, D> {
#[inline(never)]
fn try_accounts(
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut std::collections::BTreeMap<String, u8>,
_reallocs: &mut std::collections::BTreeSet<Pubkey>,
) -> Result<Self> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];
let l = AccountLoaderDynamic::try_from(account)?;
Ok(l)
}
}
impl<'info, D: DynamicAccountType> anchor_lang::AccountsExit<'info>
for AccountLoaderDynamic<'info, D>
{
fn exit(&self, _program_id: &Pubkey) -> Result<()> {
// Normally anchor writes the discriminator again here, but I don't see why
let data = self.acc_info.try_borrow_data()?;
if data.len() < D::discriminator().len() {
return Err(ErrorCode::AccountDiscriminatorNotFound.into());
}
let disc_bytes = array_ref![data, 0, 8];
if disc_bytes != &D::discriminator() {
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
}
Ok(())
}
}
impl<'info, D: DynamicAccountType> anchor_lang::AccountsClose<'info>
for AccountLoaderDynamic<'info, D>
{
fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> {
close(self.to_account_info(), sol_destination)
}
}
impl<'info, D: DynamicAccountType> anchor_lang::ToAccountMetas for AccountLoaderDynamic<'info, D> {
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
let is_signer = is_signer.unwrap_or(self.acc_info.is_signer);
let meta = match self.acc_info.is_writable {
false => AccountMeta::new_readonly(*self.acc_info.key, is_signer),
true => AccountMeta::new(*self.acc_info.key, is_signer),
};
vec![meta]
}
}
impl<'info, D: DynamicAccountType> AsRef<AccountInfo<'info>> for AccountLoaderDynamic<'info, D> {
fn as_ref(&self) -> &AccountInfo<'info> {
&self.acc_info
}
}
impl<'info, D: DynamicAccountType> anchor_lang::ToAccountInfos<'info>
for AccountLoaderDynamic<'info, D>
{
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
vec![self.acc_info.clone()]
}
}
impl<'info, D: DynamicAccountType> anchor_lang::Key for AccountLoaderDynamic<'info, D> {
fn key(&self) -> Pubkey {
*self.acc_info.key
}
}
// https://github.com/coral-xyz/anchor/blob/master/lang/src/common.rs#L8
fn close<'info>(info: AccountInfo<'info>, sol_destination: AccountInfo<'info>) -> Result<()> {
// Transfer tokens from the account to the sol_destination.
let dest_starting_lamports = sol_destination.lamports();
**sol_destination.lamports.borrow_mut() =
dest_starting_lamports.checked_add(info.lamports()).unwrap();
**info.lamports.borrow_mut() = 0;
// Mark the account discriminator as closed.
let mut data = info.try_borrow_mut_data()?;
let dst: &mut [u8] = &mut data;
dst[0..8].copy_from_slice(&[255, 255, 255, 255, 255, 255, 255, 255]);
Ok(())
}

View File

@ -1,6 +1,8 @@
use std::cell::{Ref, RefMut};
use std::mem::size_of; use std::mem::size_of;
use anchor_lang::prelude::*; use anchor_lang::prelude::*;
use anchor_lang::Discriminator;
use arrayref::array_ref; use arrayref::array_ref;
use fixed::types::I80F48; use fixed::types::I80F48;
@ -248,11 +250,20 @@ impl MangoAccountFixed {
} }
} }
impl DynamicAccountType for MangoAccount { impl Owner for MangoAccountFixed {
type Header = MangoAccountDynamicHeader; fn owner() -> Pubkey {
type Fixed = MangoAccountFixed; MangoAccount::owner()
}
} }
impl Discriminator for MangoAccountFixed {
fn discriminator() -> [u8; 8] {
MangoAccount::discriminator()
}
}
impl anchor_lang::ZeroCopy for MangoAccountFixed {}
#[derive(Clone)] #[derive(Clone)]
pub struct MangoAccountDynamicHeader { pub struct MangoAccountDynamicHeader {
pub token_count: u8, pub token_count: u8,
@ -262,34 +273,34 @@ pub struct MangoAccountDynamicHeader {
} }
impl DynamicHeader for MangoAccountDynamicHeader { impl DynamicHeader for MangoAccountDynamicHeader {
fn from_bytes(data: &[u8]) -> Result<Self> { fn from_bytes(dynamic_data: &[u8]) -> Result<Self> {
let header_version = u8::from_le_bytes(*array_ref![data, 0, size_of::<u8>()]); let header_version = u8::from_le_bytes(*array_ref![dynamic_data, 0, size_of::<u8>()]);
match header_version { match header_version {
1 => { 1 => {
let token_count = u8::try_from(BorshVecLength::from_le_bytes(*array_ref![ let token_count = u8::try_from(BorshVecLength::from_le_bytes(*array_ref![
data, dynamic_data,
MangoAccount::dynamic_token_vec_offset(), MangoAccount::dynamic_token_vec_offset(),
BORSH_VEC_SIZE_BYTES BORSH_VEC_SIZE_BYTES
])) ]))
.unwrap(); .unwrap();
let serum3_count = u8::try_from(BorshVecLength::from_le_bytes(*array_ref![ let serum3_count = u8::try_from(BorshVecLength::from_le_bytes(*array_ref![
data, dynamic_data,
MangoAccount::dynamic_serum3_vec_offset(token_count), MangoAccount::dynamic_serum3_vec_offset(token_count),
BORSH_VEC_SIZE_BYTES BORSH_VEC_SIZE_BYTES
])) ]))
.unwrap(); .unwrap();
let perp_count = u8::try_from(BorshVecLength::from_le_bytes(*array_ref![ let perp_count = u8::try_from(BorshVecLength::from_le_bytes(*array_ref![
data, dynamic_data,
MangoAccount::dynamic_perp_vec_offset(token_count, serum3_count), MangoAccount::dynamic_perp_vec_offset(token_count, serum3_count),
BORSH_VEC_SIZE_BYTES BORSH_VEC_SIZE_BYTES
])) ]))
.unwrap(); .unwrap();
let perp_oo_count = u8::try_from(BorshVecLength::from_le_bytes(*array_ref![ let perp_oo_count = u8::try_from(BorshVecLength::from_le_bytes(*array_ref![
data, dynamic_data,
MangoAccount::dynamic_perp_oo_vec_offset(token_count, serum3_count, perp_count), MangoAccount::dynamic_perp_oo_vec_offset(token_count, serum3_count, perp_count),
BORSH_VEC_SIZE_BYTES BORSH_VEC_SIZE_BYTES
])) ]))
@ -306,8 +317,8 @@ impl DynamicHeader for MangoAccountDynamicHeader {
} }
} }
fn initialize(data: &mut [u8]) -> Result<()> { fn initialize(dynamic_data: &mut [u8]) -> Result<()> {
let dst: &mut [u8] = &mut data[0..1]; let dst: &mut [u8] = &mut dynamic_data[0..1];
dst.copy_from_slice(&DEFAULT_MANGO_ACCOUNT_VERSION.to_le_bytes()); dst.copy_from_slice(&DEFAULT_MANGO_ACCOUNT_VERSION.to_le_bytes());
Ok(()) Ok(())
} }
@ -368,11 +379,25 @@ impl MangoAccountDynamicHeader {
} }
} }
pub type MangoAccountValue = DynamicAccountValue<MangoAccount>; /// Fully owned MangoAccount, useful for tests
pub type MangoAccountRef<'a> = DynamicAccountRef<'a, MangoAccount>; pub type MangoAccountValue = DynamicAccount<MangoAccountDynamicHeader, MangoAccountFixed, Vec<u8>>;
pub type MangoAccountRefMut<'a> = DynamicAccountRefMut<'a, MangoAccount>;
pub type MangoAccountRefWithHeader<'a> = /// Full reference type, useful for borrows
pub type MangoAccountRef<'a> =
DynamicAccount<&'a MangoAccountDynamicHeader, &'a MangoAccountFixed, &'a [u8]>;
/// Full reference type, useful for borrows
pub type MangoAccountRefMut<'a> =
DynamicAccount<&'a mut MangoAccountDynamicHeader, &'a mut MangoAccountFixed, &'a mut [u8]>;
/// Useful when loading from bytes
pub type MangoAccountLoadedRef<'a> =
DynamicAccount<MangoAccountDynamicHeader, &'a MangoAccountFixed, &'a [u8]>; DynamicAccount<MangoAccountDynamicHeader, &'a MangoAccountFixed, &'a [u8]>;
/// Useful when loading from RefCell, like from AccountInfo
pub type MangoAccountLoadedRefCell<'a> =
DynamicAccount<MangoAccountDynamicHeader, Ref<'a, MangoAccountFixed>, Ref<'a, [u8]>>;
/// Useful when loading from RefCell, like from AccountInfo
pub type MangoAccountLoadedRefCellMut<'a> =
DynamicAccount<MangoAccountDynamicHeader, RefMut<'a, MangoAccountFixed>, RefMut<'a, [u8]>>;
impl MangoAccountValue { impl MangoAccountValue {
// bytes without discriminator // bytes without discriminator
@ -386,7 +411,7 @@ impl MangoAccountValue {
} }
} }
impl<'a> MangoAccountRefWithHeader<'a> { impl<'a> MangoAccountLoadedRef<'a> {
// bytes without discriminator // bytes without discriminator
pub fn from_bytes(bytes: &'a [u8]) -> Result<Self> { pub fn from_bytes(bytes: &'a [u8]) -> Result<Self> {
let (fixed, dynamic) = bytes.split_at(size_of::<MangoAccountFixed>()); let (fixed, dynamic) = bytes.split_at(size_of::<MangoAccountFixed>());
@ -399,7 +424,7 @@ impl<'a> MangoAccountRefWithHeader<'a> {
} }
// This generic impl covers MangoAccountRef, MangoAccountRefMut and other // This generic impl covers MangoAccountRef, MangoAccountRefMut and other
// DynamicAccountValue variants that allow read access. // DynamicAccount variants that allow read access.
impl< impl<
Header: DerefOrBorrow<MangoAccountDynamicHeader>, Header: DerefOrBorrow<MangoAccountDynamicHeader>,
Fixed: DerefOrBorrow<MangoAccountFixed>, Fixed: DerefOrBorrow<MangoAccountFixed>,
@ -541,8 +566,8 @@ impl<
self.fixed().being_liquidated() self.fixed().being_liquidated()
} }
pub fn borrow(&self) -> DynamicAccountRef<MangoAccount> { pub fn borrow(&self) -> MangoAccountRef {
DynamicAccount { MangoAccountRef {
header: self.header(), header: self.header(),
fixed: self.fixed(), fixed: self.fixed(),
dynamic: self.dynamic(), dynamic: self.dynamic(),
@ -566,8 +591,8 @@ impl<
self.dynamic.deref_or_borrow_mut() self.dynamic.deref_or_borrow_mut()
} }
pub fn borrow_mut(&mut self) -> DynamicAccountRefMut<MangoAccount> { pub fn borrow_mut(&mut self) -> MangoAccountRefMut {
DynamicAccount { MangoAccountRefMut {
header: self.header.deref_or_borrow_mut(), header: self.header.deref_or_borrow_mut(),
fixed: self.fixed.deref_or_borrow_mut(), fixed: self.fixed.deref_or_borrow_mut(),
dynamic: self.dynamic.deref_or_borrow_mut(), dynamic: self.dynamic.deref_or_borrow_mut(),
@ -1086,6 +1111,65 @@ impl<
} }
} }
/// Trait to allow a AccountLoader<MangoAccountFixed> to create an accessor for the full account.
pub trait MangoAccountLoader<'a> {
fn load_full(self) -> Result<MangoAccountLoadedRefCell<'a>>;
fn load_full_mut(self) -> Result<MangoAccountLoadedRefCellMut<'a>>;
fn load_full_init(self) -> Result<MangoAccountLoadedRefCellMut<'a>>;
}
impl<'a, 'info: 'a> MangoAccountLoader<'a> for &'a AccountLoader<'info, MangoAccountFixed> {
fn load_full(self) -> Result<MangoAccountLoadedRefCell<'a>> {
// Error checking
self.load()?;
let data = self.as_ref().try_borrow_data()?;
let header =
MangoAccountDynamicHeader::from_bytes(&data[8 + size_of::<MangoAccountFixed>()..])?;
let (_, data) = Ref::map_split(data, |d| d.split_at(8));
let (fixed_bytes, dynamic) =
Ref::map_split(data, |d| d.split_at(size_of::<MangoAccountFixed>()));
Ok(MangoAccountLoadedRefCell {
header,
fixed: Ref::map(fixed_bytes, |b| bytemuck::from_bytes(b)),
dynamic,
})
}
fn load_full_mut(self) -> Result<MangoAccountLoadedRefCellMut<'a>> {
// Error checking
self.load_mut()?;
let data = self.as_ref().try_borrow_mut_data()?;
let header =
MangoAccountDynamicHeader::from_bytes(&data[8 + size_of::<MangoAccountFixed>()..])?;
let (_, data) = RefMut::map_split(data, |d| d.split_at_mut(8));
let (fixed_bytes, dynamic) =
RefMut::map_split(data, |d| d.split_at_mut(size_of::<MangoAccountFixed>()));
Ok(MangoAccountLoadedRefCellMut {
header,
fixed: RefMut::map(fixed_bytes, |b| bytemuck::from_bytes_mut(b)),
dynamic,
})
}
fn load_full_init(self) -> Result<MangoAccountLoadedRefCellMut<'a>> {
// Error checking
self.load_init()?;
{
let mut data = self.as_ref().try_borrow_mut_data()?;
let disc_bytes: &mut [u8] = &mut data[0..8];
disc_bytes.copy_from_slice(bytemuck::bytes_of(&(MangoAccount::discriminator())));
MangoAccountDynamicHeader::initialize(&mut data[8 + size_of::<MangoAccountFixed>()..])?;
}
self.load_full_mut()
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -284,6 +284,11 @@ impl PerpPosition {
self.base_position_lots self.base_position_lots
} }
// This takes into account base lots from unprocessed events, but not anything from open orders
pub fn effective_base_position_lots(&self) -> i64 {
self.base_position_lots + self.taker_base_lots
}
pub fn quote_position_native(&self) -> I80F48 { pub fn quote_position_native(&self) -> I80F48 {
self.quote_position_native self.quote_position_native
} }
@ -439,7 +444,10 @@ impl PerpPosition {
pub fn update_settle_limit(&mut self, market: &PerpMarket, now_ts: u64) { pub fn update_settle_limit(&mut self, market: &PerpMarket, now_ts: u64) {
assert_eq!(self.market_index, market.perp_market_index); assert_eq!(self.market_index, market.perp_market_index);
let window_size = market.settle_pnl_limit_window_size_ts; let window_size = market.settle_pnl_limit_window_size_ts;
let new_window = now_ts >= cm!((self.settle_pnl_limit_window + 1) as u64 * window_size); let window_start = cm!(self.settle_pnl_limit_window as u64 * window_size);
let window_end = cm!(window_start + window_size);
// now_ts < window_start can happen when window size is changed on the market
let new_window = now_ts >= window_end || now_ts < window_start;
if new_window { if new_window {
self.settle_pnl_limit_window = cm!(now_ts / window_size).try_into().unwrap(); self.settle_pnl_limit_window = cm!(now_ts / window_size).try_into().unwrap();
self.settle_pnl_limit_settled_in_current_window_native = 0; self.settle_pnl_limit_settled_in_current_window_native = 0;
@ -855,6 +863,34 @@ mod tests {
assert_eq!(pos.realized_pnl_native, I80F48::from(0)); assert_eq!(pos.realized_pnl_native, I80F48::from(0));
} }
#[test]
fn test_perp_settle_limit_window() {
let mut market = PerpMarket::default_for_tests();
let mut pos = create_perp_position(&market, 100, -50);
market.settle_pnl_limit_window_size_ts = 100;
pos.settle_pnl_limit_settled_in_current_window_native = 10;
pos.update_settle_limit(&market, 505);
assert_eq!(pos.settle_pnl_limit_settled_in_current_window_native, 0);
assert_eq!(pos.settle_pnl_limit_window, 5);
pos.settle_pnl_limit_settled_in_current_window_native = 10;
pos.update_settle_limit(&market, 550);
assert_eq!(pos.settle_pnl_limit_settled_in_current_window_native, 10);
assert_eq!(pos.settle_pnl_limit_window, 5);
pos.settle_pnl_limit_settled_in_current_window_native = 10;
pos.update_settle_limit(&market, 600);
assert_eq!(pos.settle_pnl_limit_settled_in_current_window_native, 0);
assert_eq!(pos.settle_pnl_limit_window, 6);
market.settle_pnl_limit_window_size_ts = 400;
pos.update_settle_limit(&market, 605);
assert_eq!(pos.settle_pnl_limit_settled_in_current_window_native, 0);
assert_eq!(pos.settle_pnl_limit_window, 1);
}
#[test] #[test]
fn test_perp_settle_limit() { fn test_perp_settle_limit() {
let market = PerpMarket::default_for_tests(); let market = PerpMarket::default_for_tests();

View File

@ -105,7 +105,9 @@ pub struct PerpMarket {
/// Window size in seconds for the perp settlement limit /// Window size in seconds for the perp settlement limit
pub settle_pnl_limit_window_size_ts: u64, pub settle_pnl_limit_window_size_ts: u64,
pub reserved: [u8; 1944], pub reduce_only: u8,
pub reserved: [u8; 1943],
} }
const_assert_eq!( const_assert_eq!(
@ -138,7 +140,8 @@ const_assert_eq!(
+ 4 * 3 + 4 * 3
+ 8 + 8
+ 8 + 8
+ 1944 + 1
+ 1943
); );
const_assert_eq!(size_of::<PerpMarket>(), 2808); const_assert_eq!(size_of::<PerpMarket>(), 2808);
const_assert_eq!(size_of::<PerpMarket>() % 8, 0); const_assert_eq!(size_of::<PerpMarket>() % 8, 0);
@ -150,6 +153,10 @@ impl PerpMarket {
.trim_matches(char::from(0)) .trim_matches(char::from(0))
} }
pub fn is_reduce_only(&self) -> bool {
self.reduce_only == 1
}
pub fn elligible_for_group_insurance_fund(&self) -> bool { pub fn elligible_for_group_insurance_fund(&self) -> bool {
self.group_insurance_fund == 1 self.group_insurance_fund == 1
} }
@ -306,6 +313,8 @@ impl PerpMarket {
perp_market_index: 0, perp_market_index: 0,
trusted_market: 0, trusted_market: 0,
group_insurance_fund: 0, group_insurance_fund: 0,
bump: 0,
base_decimals: 0,
name: Default::default(), name: Default::default(),
bids: Pubkey::new_unique(), bids: Pubkey::new_unique(),
asks: Pubkey::new_unique(), asks: Pubkey::new_unique(),
@ -325,8 +334,6 @@ impl PerpMarket {
init_liab_weight: I80F48::from(1), init_liab_weight: I80F48::from(1),
open_interest: 0, open_interest: 0,
seq_num: 0, seq_num: 0,
bump: 0,
base_decimals: 0,
registration_time: 0, registration_time: 0,
min_funding: I80F48::ZERO, min_funding: I80F48::ZERO,
max_funding: I80F48::ZERO, max_funding: I80F48::ZERO,
@ -343,10 +350,11 @@ impl PerpMarket {
settle_fee_flat: 0.0, settle_fee_flat: 0.0,
settle_fee_amount_threshold: 0.0, settle_fee_amount_threshold: 0.0,
settle_fee_fraction_low_health: 0.0, settle_fee_fraction_low_health: 0.0,
padding3: Default::default(),
settle_pnl_limit_factor: 0.2, settle_pnl_limit_factor: 0.2,
padding3: Default::default(),
settle_pnl_limit_window_size_ts: 24 * 60 * 60, settle_pnl_limit_window_size_ts: 24 * 60 * 60,
reserved: [0; 1944], reduce_only: 0,
reserved: [0; 1943],
} }
} }
} }

View File

@ -15,7 +15,8 @@ pub struct Serum3Market {
pub base_token_index: TokenIndex, pub base_token_index: TokenIndex,
// ABI: Clients rely on this being at offset 42 // ABI: Clients rely on this being at offset 42
pub quote_token_index: TokenIndex, pub quote_token_index: TokenIndex,
pub padding1: [u8; 4], pub reduce_only: u8,
pub padding1: [u8; 3],
pub name: [u8; 16], pub name: [u8; 16],
pub serum_program: Pubkey, pub serum_program: Pubkey,
pub serum_market_external: Pubkey, pub serum_market_external: Pubkey,
@ -32,7 +33,7 @@ pub struct Serum3Market {
} }
const_assert_eq!( const_assert_eq!(
size_of::<Serum3Market>(), size_of::<Serum3Market>(),
32 + 2 + 2 + 4 + 16 + 2 * 32 + 2 + 1 + 5 + 8 + 128 32 + 2 + 2 + 1 + 3 + 16 + 2 * 32 + 2 + 1 + 5 + 8 + 128
); );
const_assert_eq!(size_of::<Serum3Market>(), 264); const_assert_eq!(size_of::<Serum3Market>(), 264);
const_assert_eq!(size_of::<Serum3Market>() % 8, 0); const_assert_eq!(size_of::<Serum3Market>() % 8, 0);
@ -43,6 +44,10 @@ impl Serum3Market {
.unwrap() .unwrap()
.trim_matches(char::from(0)) .trim_matches(char::from(0))
} }
pub fn is_reduce_only(&self) -> bool {
self.reduce_only == 1
}
} }
#[account(zero_copy)] #[account(zero_copy)]

View File

@ -617,7 +617,7 @@ impl ClientInstruction for TokenWithdrawInstruction {
pub struct TokenDepositInstruction { pub struct TokenDepositInstruction {
pub amount: u64, pub amount: u64,
pub reduce_only: bool,
pub account: Pubkey, pub account: Pubkey,
pub owner: TestKeypair, pub owner: TestKeypair,
pub token_account: Pubkey, pub token_account: Pubkey,
@ -635,6 +635,7 @@ impl ClientInstruction for TokenDepositInstruction {
let program_id = mango_v4::id(); let program_id = mango_v4::id();
let instruction = Self::Instruction { let instruction = Self::Instruction {
amount: self.amount, amount: self.amount,
reduce_only: self.reduce_only,
}; };
// load account so we know its mint // load account so we know its mint
@ -688,7 +689,7 @@ impl ClientInstruction for TokenDepositInstruction {
pub struct TokenDepositIntoExistingInstruction { pub struct TokenDepositIntoExistingInstruction {
pub amount: u64, pub amount: u64,
pub reduce_only: bool,
pub account: Pubkey, pub account: Pubkey,
pub token_account: Pubkey, pub token_account: Pubkey,
pub token_authority: TestKeypair, pub token_authority: TestKeypair,
@ -705,6 +706,7 @@ impl ClientInstruction for TokenDepositIntoExistingInstruction {
let program_id = mango_v4::id(); let program_id = mango_v4::id();
let instruction = Self::Instruction { let instruction = Self::Instruction {
amount: self.amount, amount: self.amount,
reduce_only: self.reduce_only,
}; };
// load account so we know its mint // load account so we know its mint
@ -1083,6 +1085,7 @@ impl ClientInstruction for TokenResetStablePriceModel {
deposit_weight_scale_start_quote_opt: None, deposit_weight_scale_start_quote_opt: None,
reset_stable_price: true, reset_stable_price: true,
reset_net_borrow_limit: false, reset_net_borrow_limit: false,
reduce_only_opt: None,
}; };
let accounts = Self::Accounts { let accounts = Self::Accounts {
@ -1160,6 +1163,82 @@ impl ClientInstruction for TokenResetNetBorrows {
deposit_weight_scale_start_quote_opt: None, deposit_weight_scale_start_quote_opt: None,
reset_stable_price: false, reset_stable_price: false,
reset_net_borrow_limit: true, reset_net_borrow_limit: true,
reduce_only_opt: None,
};
let accounts = Self::Accounts {
group: self.group,
admin: self.admin.pubkey(),
mint_info: mint_info_key,
oracle: mint_info.oracle,
};
let mut instruction = make_instruction(program_id, &accounts, instruction);
instruction
.accounts
.extend(mint_info.banks().iter().map(|&k| AccountMeta {
pubkey: k,
is_signer: false,
is_writable: true,
}));
(accounts, instruction)
}
fn signers(&self) -> Vec<TestKeypair> {
vec![self.admin]
}
}
pub struct TokenMakeReduceOnly {
pub group: Pubkey,
pub admin: TestKeypair,
pub mint: Pubkey,
}
#[async_trait::async_trait(?Send)]
impl ClientInstruction for TokenMakeReduceOnly {
type Accounts = mango_v4::accounts::TokenEdit;
type Instruction = mango_v4::instruction::TokenEdit;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let mint_info_key = Pubkey::find_program_address(
&[
b"MintInfo".as_ref(),
self.group.as_ref(),
self.mint.as_ref(),
],
&program_id,
)
.0;
let mint_info: MintInfo = account_loader.load(&mint_info_key).await.unwrap();
let instruction = Self::Instruction {
oracle_opt: None,
oracle_config_opt: None,
group_insurance_fund_opt: None,
interest_rate_params_opt: None,
loan_fee_rate_opt: None,
loan_origination_fee_rate_opt: None,
maint_asset_weight_opt: None,
init_asset_weight_opt: None,
maint_liab_weight_opt: None,
init_liab_weight_opt: None,
liquidation_fee_opt: None,
stable_price_delay_interval_seconds_opt: None,
stable_price_delay_growth_limit_opt: None,
stable_price_growth_limit_opt: None,
min_vault_to_deposits_ratio_opt: None,
net_borrow_limit_per_window_quote_opt: None,
net_borrow_limit_window_size_ts_opt: None,
borrow_weight_scale_start_quote_opt: None,
deposit_weight_scale_start_quote_opt: None,
reset_stable_price: false,
reset_net_borrow_limit: false,
reduce_only_opt: Some(true),
}; };
let accounts = Self::Accounts { let accounts = Self::Accounts {
@ -2570,6 +2649,69 @@ impl ClientInstruction for PerpResetStablePriceModel {
stable_price_growth_limit_opt: None, stable_price_growth_limit_opt: None,
settle_pnl_limit_factor_opt: None, settle_pnl_limit_factor_opt: None,
settle_pnl_limit_window_size_ts: None, settle_pnl_limit_window_size_ts: None,
reduce_only_opt: None,
};
let accounts = Self::Accounts {
group: self.group,
admin: self.admin.pubkey(),
perp_market: self.perp_market,
oracle: perp_market.oracle,
};
let instruction = make_instruction(program_id, &accounts, instruction);
(accounts, instruction)
}
fn signers(&self) -> Vec<TestKeypair> {
vec![self.admin]
}
}
pub struct PerpMakeReduceOnly {
pub group: Pubkey,
pub admin: TestKeypair,
pub perp_market: Pubkey,
}
#[async_trait::async_trait(?Send)]
impl ClientInstruction for PerpMakeReduceOnly {
type Accounts = mango_v4::accounts::PerpEditMarket;
type Instruction = mango_v4::instruction::PerpEditMarket;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let perp_market: PerpMarket = account_loader.load(&self.perp_market).await.unwrap();
let instruction = Self::Instruction {
oracle_opt: None,
oracle_config_opt: None,
base_decimals_opt: None,
maint_asset_weight_opt: None,
init_asset_weight_opt: None,
maint_liab_weight_opt: None,
init_liab_weight_opt: None,
liquidation_fee_opt: None,
maker_fee_opt: None,
taker_fee_opt: None,
min_funding_opt: None,
max_funding_opt: None,
impact_quantity_opt: None,
group_insurance_fund_opt: None,
trusted_market_opt: None,
fee_penalty_opt: None,
settle_fee_flat_opt: None,
settle_fee_amount_threshold_opt: None,
settle_fee_fraction_low_health_opt: None,
stable_price_delay_interval_seconds_opt: None,
stable_price_delay_growth_limit_opt: None,
stable_price_growth_limit_opt: None,
settle_pnl_limit_factor_opt: None,
settle_pnl_limit_window_size_ts: None,
reduce_only_opt: Some(true),
}; };
let accounts = Self::Accounts { let accounts = Self::Accounts {
@ -2666,6 +2808,7 @@ pub struct PerpPlaceOrderInstruction {
pub price_lots: i64, pub price_lots: i64,
pub max_base_lots: i64, pub max_base_lots: i64,
pub max_quote_lots: i64, pub max_quote_lots: i64,
pub reduce_only: bool,
pub client_order_id: u64, pub client_order_id: u64,
} }
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
@ -2684,7 +2827,7 @@ impl ClientInstruction for PerpPlaceOrderInstruction {
max_quote_lots: self.max_quote_lots, max_quote_lots: self.max_quote_lots,
client_order_id: self.client_order_id, client_order_id: self.client_order_id,
order_type: PlaceOrderType::Limit, order_type: PlaceOrderType::Limit,
reduce_only: false, reduce_only: self.reduce_only,
expiry_timestamp: 0, expiry_timestamp: 0,
limit: 10, limit: 10,
}; };

View File

@ -179,6 +179,7 @@ pub async fn create_funded_account(
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: amounts, amount: amounts,
reduce_only: false,
account, account,
owner, owner,
token_account: payer.token_accounts[mint.index], token_account: payer.token_accounts[mint.index],

View File

@ -61,6 +61,7 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: 20, amount: 20,
reduce_only: false,
account: vault_account, account: vault_account,
owner, owner,
token_account: payer_mint_accounts[0], token_account: payer_mint_accounts[0],
@ -97,6 +98,7 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: deposit1_amount, amount: deposit1_amount,
reduce_only: false,
account, account,
owner, owner,
token_account: payer_mint_accounts[2], token_account: payer_mint_accounts[2],
@ -110,6 +112,7 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: deposit2_amount, amount: deposit2_amount,
reduce_only: false,
account, account,
owner, owner,
token_account: payer_mint_accounts[3], token_account: payer_mint_accounts[3],
@ -350,6 +353,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: vault_amount, amount: vault_amount,
reduce_only: false,
account: vault_account, account: vault_account,
owner, owner,
token_account, token_account,
@ -367,6 +371,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: 20, amount: 20,
reduce_only: false,
account: vault_account, account: vault_account,
owner, owner,
token_account: payer_mint_accounts[0], token_account: payer_mint_accounts[0],
@ -403,6 +408,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: deposit1_amount, amount: deposit1_amount,
reduce_only: false,
account, account,
owner, owner,
token_account: payer_mint_accounts[2], token_account: payer_mint_accounts[2],
@ -416,6 +422,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: deposit2_amount, amount: deposit2_amount,
reduce_only: false,
account, account,
owner, owner,
token_account: payer_mint_accounts[3], token_account: payer_mint_accounts[3],

View File

@ -105,6 +105,7 @@ async fn test_basic() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: deposit_amount, amount: deposit_amount,
reduce_only: false,
account, account,
owner, owner,
token_account: payer_mint0_account, token_account: payer_mint0_account,

View File

@ -146,6 +146,7 @@ async fn test_health_compute_serum() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: 10, amount: 10,
reduce_only: false,
account, account,
owner, owner,
token_account: payer_mint_accounts[0], token_account: payer_mint_accounts[0],
@ -252,6 +253,7 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0, client_order_id: 0,
}, },
) )
@ -262,6 +264,7 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: 10, amount: 10,
reduce_only: false,
account, account,
owner, owner,
token_account: payer_mint_accounts[0], token_account: payer_mint_accounts[0],

View File

@ -64,6 +64,7 @@ async fn test_health_wrap() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: 1, amount: 1,
reduce_only: false,
account, account,
owner, owner,
token_account: payer_mint_accounts[0], token_account: payer_mint_accounts[0],
@ -91,6 +92,7 @@ async fn test_health_wrap() -> Result<(), TransportError> {
.await; .await;
tx.add_instruction(TokenDepositInstruction { tx.add_instruction(TokenDepositInstruction {
amount: repay_amount, amount: repay_amount,
reduce_only: false,
account, account,
owner, owner,
token_account: payer_mint_accounts[1], token_account: payer_mint_accounts[1],

View File

@ -95,6 +95,7 @@ async fn test_liq_perps_force_cancel() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: 1, amount: 1,
reduce_only: false,
account, account,
owner, owner,
token_account: payer_mint_accounts[1], token_account: payer_mint_accounts[1],
@ -119,6 +120,7 @@ async fn test_liq_perps_force_cancel() -> Result<(), TransportError> {
// health was 1000 * 0.6 = 600; this order is -14*100*(1.4-1) = -560 // health was 1000 * 0.6 = 600; this order is -14*100*(1.4-1) = -560
max_base_lots: 14, max_base_lots: 14,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0, client_order_id: 0,
}, },
) )
@ -318,6 +320,7 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
price_lots, price_lots,
max_base_lots: 20, max_base_lots: 20,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0, client_order_id: 0,
}, },
) )
@ -333,6 +336,7 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
price_lots, price_lots,
max_base_lots: 20, max_base_lots: 20,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0, client_order_id: 0,
}, },
) )
@ -610,6 +614,7 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: 1, amount: 1,
reduce_only: false,
account: account_1, account: account_1,
owner, owner,
token_account: payer_mint_accounts[1], token_account: payer_mint_accounts[1],

View File

@ -223,6 +223,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: 100000, amount: 100000,
reduce_only: false,
account: vault_account, account: vault_account,
owner, owner,
token_account, token_account,
@ -260,6 +261,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: deposit1_amount, amount: deposit1_amount,
reduce_only: false,
account, account,
owner, owner,
token_account: payer_mint_accounts[2], token_account: payer_mint_accounts[2],
@ -273,6 +275,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: deposit2_amount, amount: deposit2_amount,
reduce_only: false,
account, account,
owner, owner,
token_account: payer_mint_accounts[3], token_account: payer_mint_accounts[3],

View File

@ -88,6 +88,7 @@ async fn test_margin_trade() -> Result<(), BanksClientError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: deposit_amount_initial, amount: deposit_amount_initial,
reduce_only: false,
account, account,
owner, owner,
token_account: payer_mint0_account, token_account: payer_mint0_account,

View File

@ -109,6 +109,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0, client_order_id: 0,
}, },
) )
@ -150,6 +151,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 1, client_order_id: 1,
}, },
) )
@ -184,6 +186,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 2, client_order_id: 2,
}, },
) )
@ -217,6 +220,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 4, client_order_id: 4,
}, },
) )
@ -250,6 +254,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 5, client_order_id: 5,
}, },
) )
@ -267,6 +272,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 6, client_order_id: 6,
}, },
) )
@ -328,6 +334,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 7, client_order_id: 7,
}, },
) )
@ -345,6 +352,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 8, client_order_id: 8,
}, },
) )
@ -609,6 +617,7 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 6, client_order_id: 6,
}, },
) )
@ -691,6 +700,7 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 60, client_order_id: 60,
}, },
) )
@ -720,6 +730,7 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 2, max_base_lots: 2,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 61, client_order_id: 61,
}, },
) )
@ -771,6 +782,7 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
price_lots: price_lots + 2, price_lots: price_lots + 2,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 62, client_order_id: 62,
}, },
) )
@ -800,6 +812,7 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
price_lots: price_lots + 3, price_lots: price_lots + 3,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 63, client_order_id: 63,
}, },
) )

View File

@ -139,6 +139,7 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0, client_order_id: 0,
}, },
) )
@ -155,6 +156,7 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0, client_order_id: 0,
}, },
) )
@ -613,6 +615,7 @@ async fn test_perp_settle_pnl_fees() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0, client_order_id: 0,
}, },
) )
@ -629,6 +632,7 @@ async fn test_perp_settle_pnl_fees() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0, client_order_id: 0,
}, },
) )
@ -889,6 +893,7 @@ async fn test_perp_pnl_settle_limit() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0, client_order_id: 0,
}, },
) )
@ -905,6 +910,7 @@ async fn test_perp_pnl_settle_limit() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0, client_order_id: 0,
}, },
) )

View File

@ -79,6 +79,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: deposit_amount, amount: deposit_amount,
reduce_only: false,
account: account_0, account: account_0,
owner, owner,
token_account: payer_mint_accounts[0], token_account: payer_mint_accounts[0],
@ -93,6 +94,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: deposit_amount, amount: deposit_amount,
reduce_only: false,
account: account_0, account: account_0,
owner, owner,
token_account: payer_mint_accounts[1], token_account: payer_mint_accounts[1],
@ -111,6 +113,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: deposit_amount, amount: deposit_amount,
reduce_only: false,
account: account_1, account: account_1,
owner, owner,
token_account: payer_mint_accounts[0], token_account: payer_mint_accounts[0],
@ -125,6 +128,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: deposit_amount, amount: deposit_amount,
reduce_only: false,
account: account_1, account: account_1,
owner, owner,
token_account: payer_mint_accounts[1], token_account: payer_mint_accounts[1],
@ -214,6 +218,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0, client_order_id: 0,
}, },
) )
@ -230,6 +235,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
price_lots, price_lots,
max_base_lots: 1, max_base_lots: 1,
max_quote_lots: i64::MAX, max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0, client_order_id: 0,
}, },
) )

View File

@ -78,6 +78,7 @@ async fn test_position_lifetime() -> Result<()> {
solana, solana,
TokenDepositIntoExistingInstruction { TokenDepositIntoExistingInstruction {
amount: deposit_amount, amount: deposit_amount,
reduce_only: false,
account, account,
token_account: payer_mint_accounts[0], token_account: payer_mint_accounts[0],
token_authority: payer, token_authority: payer,
@ -93,6 +94,7 @@ async fn test_position_lifetime() -> Result<()> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: deposit_amount, amount: deposit_amount,
reduce_only: false,
account, account,
owner, owner,
token_account: payer_token, token_account: payer_token,
@ -109,6 +111,7 @@ async fn test_position_lifetime() -> Result<()> {
solana, solana,
TokenDepositIntoExistingInstruction { TokenDepositIntoExistingInstruction {
amount: deposit_amount, amount: deposit_amount,
reduce_only: false,
account, account,
token_account: payer_mint_accounts[0], token_account: payer_mint_accounts[0],
token_authority: payer, token_authority: payer,
@ -160,6 +163,7 @@ async fn test_position_lifetime() -> Result<()> {
solana, solana,
TokenDepositInstruction { TokenDepositInstruction {
amount: collateral_amount, amount: collateral_amount,
reduce_only: false,
account, account,
owner, owner,
token_account: payer_mint_accounts[0], token_account: payer_mint_accounts[0],
@ -197,6 +201,7 @@ async fn test_position_lifetime() -> Result<()> {
TokenDepositInstruction { TokenDepositInstruction {
// deposit withdraw amount + some more to cover loan origination fees // deposit withdraw amount + some more to cover loan origination fees
amount: borrow_amount + 2, amount: borrow_amount + 2,
reduce_only: false,
account, account,
owner, owner,
token_account: payer_mint_accounts[1], token_account: payer_mint_accounts[1],

View File

@ -0,0 +1,610 @@
#![cfg(feature = "test-bpf")]
use fixed::types::I80F48;
use mango_setup::*;
use mango_v4::state::{Bank, MangoAccount, PerpMarket, Side};
use program_test::*;
use solana_program_test::*;
use solana_sdk::transport::TransportError;
mod program_test;
#[tokio::test]
async fn test_reduce_only_token() -> Result<(), TransportError> {
let context = TestContext::new().await;
let solana = &context.solana.clone();
let admin = TestKeypair::new();
let owner = context.users[0].key;
let payer = context.users[1].key;
let mints = &context.mints[0..=2];
let payer_mint_accounts = &context.users[1].token_accounts[0..=2];
let initial_token_deposit = 10_000;
//
// SETUP: Create a group and an account
//
let GroupWithTokens { group, tokens, .. } = GroupWithTokensConfig {
admin,
payer,
mints: mints.to_vec(),
..GroupWithTokensConfig::default()
}
.create(solana)
.await;
//
// SETUP: Prepare accounts
//
let account_0 = create_funded_account(
&solana,
group,
owner,
0,
&context.users[1],
&mints[0..=2],
initial_token_deposit,
0,
)
.await;
let account_1 = create_funded_account(
&solana,
group,
owner,
1,
&context.users[1],
&mints[0..=1],
initial_token_deposit,
0,
)
.await;
// make token reduce only
send_tx(
solana,
TokenMakeReduceOnly {
admin,
group,
mint: mints[0].pubkey,
},
)
.await
.unwrap();
//
// Withdraw deposits
//
// deposit without reduce_only should fail
let res = send_tx(
solana,
TokenDepositInstruction {
amount: 10,
reduce_only: false,
account: account_0,
owner,
token_account: payer_mint_accounts[0],
token_authority: payer,
bank_index: 0,
},
)
.await;
assert!(res.is_err());
// deposit with reduce_only should pass with no effect
send_tx(
solana,
TokenDepositInstruction {
amount: 10,
reduce_only: true,
account: account_0,
owner,
token_account: payer_mint_accounts[0],
token_authority: payer,
bank_index: 0,
},
)
.await
.unwrap();
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
let bank = solana.get_account::<Bank>(tokens[0].bank).await;
let native = mango_account_0.tokens[0].native(&bank);
assert_eq!(native.to_num::<u64>(), initial_token_deposit);
// withdraw all should pass
send_tx(
solana,
TokenWithdrawInstruction {
amount: initial_token_deposit,
allow_borrow: false,
account: account_0,
owner,
token_account: payer_mint_accounts[0],
bank_index: 0,
},
)
.await
.unwrap();
// borrowing should fail
let res = send_tx(
solana,
TokenWithdrawInstruction {
amount: 1,
allow_borrow: true,
account: account_0,
owner,
token_account: payer_mint_accounts[0],
bank_index: 0,
},
)
.await;
assert!(res.is_err());
//
// Repay borrows
//
send_tx(
solana,
TokenWithdrawInstruction {
amount: initial_token_deposit / 2,
allow_borrow: true,
account: account_1,
owner,
token_account: payer_mint_accounts[2],
bank_index: 0,
},
)
.await
.unwrap();
// make token reduce only
send_tx(
solana,
TokenMakeReduceOnly {
admin,
group,
mint: mints[2].pubkey,
},
)
.await
.unwrap();
send_tx(
solana,
TokenDepositInstruction {
amount: initial_token_deposit,
reduce_only: true,
account: account_1,
owner,
token_account: payer_mint_accounts[2],
token_authority: payer,
bank_index: 0,
},
)
.await
.unwrap();
Ok(())
}
#[tokio::test]
async fn test_perp_reduce_only() -> Result<(), TransportError> {
let context = TestContext::new().await;
let solana = &context.solana.clone();
let admin = TestKeypair::new();
let owner = context.users[0].key;
let payer = context.users[1].key;
let mints = &context.mints[0..=2];
let initial_token_deposit = 1000_000;
//
// SETUP: Create a group and an account
//
let GroupWithTokens { group, tokens, .. } = GroupWithTokensConfig {
admin,
payer,
mints: mints.to_vec(),
..GroupWithTokensConfig::default()
}
.create(solana)
.await;
let account_0 = create_funded_account(
&solana,
group,
owner,
0,
&context.users[1],
&mints[0..1],
initial_token_deposit,
0,
)
.await;
let account_1 = create_funded_account(
&solana,
group,
owner,
1,
&context.users[1],
&mints[0..1],
initial_token_deposit * 100, // Fund 100x, so that this is not the bound for what account_0 can settle
0,
)
.await;
//
// TEST: Create a perp market
//
let mango_v4::accounts::PerpCreateMarket { perp_market, .. } = send_tx(
solana,
PerpCreateMarketInstruction {
group,
admin,
payer,
perp_market_index: 0,
quote_lot_size: 10,
base_lot_size: 100,
maint_asset_weight: 0.975,
init_asset_weight: 0.95,
maint_liab_weight: 1.025,
init_liab_weight: 1.05,
liquidation_fee: 0.012,
maker_fee: 0.0002,
taker_fee: 0.000,
settle_pnl_limit_factor: -1.,
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &tokens[1]).await
},
)
.await
.unwrap();
let price_lots = {
let perp_market = solana.get_account::<PerpMarket>(perp_market).await;
perp_market.native_price_to_lot(I80F48::from(1000))
};
// Set the initial oracle price
set_perp_stub_oracle_price(solana, group, perp_market, &tokens[1], admin, 1000.0).await;
//
// Place orders and create a position
//
send_tx(
solana,
PerpPlaceOrderInstruction {
account: account_0,
perp_market,
owner,
side: Side::Bid,
price_lots,
max_base_lots: 2,
max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0,
},
)
.await
.unwrap();
send_tx(
solana,
PerpPlaceOrderInstruction {
account: account_1,
perp_market,
owner,
side: Side::Ask,
price_lots,
max_base_lots: 2,
max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0,
},
)
.await
.unwrap();
send_tx(
solana,
PerpConsumeEventsInstruction {
perp_market,
mango_accounts: vec![account_0, account_1],
},
)
.await
.unwrap();
// account_0 - place a new bid
// when user has a long, and market is in reduce only,
// to reduce incoming asks to reduce position, we ignore existing bids
send_tx(
solana,
PerpPlaceOrderInstruction {
account: account_0,
perp_market,
owner,
side: Side::Bid,
price_lots: price_lots / 2,
max_base_lots: 1,
max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0,
},
)
.await
.unwrap();
// account_1 - place a new ask
// when user has a short, and market is in reduce only,
// to reduce incoming bids to reduce position, we ignore existing asks
send_tx(
solana,
PerpPlaceOrderInstruction {
account: account_1,
perp_market,
owner,
side: Side::Ask,
price_lots,
max_base_lots: 1,
max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0,
},
)
.await
.unwrap();
//
// Make market reduce only
//
send_tx(
solana,
PerpMakeReduceOnly {
group,
admin,
perp_market,
},
)
.await
.unwrap();
// account_0 - place a new bid with reduce only false should fail
let res = send_tx(
solana,
PerpPlaceOrderInstruction {
account: account_0,
perp_market,
owner,
side: Side::Bid,
price_lots,
max_base_lots: 1,
max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0,
},
)
.await;
assert!(res.is_err());
// account_0 - place a new bid with reduce only true should do nothing
send_tx(
solana,
PerpPlaceOrderInstruction {
account: account_0,
perp_market,
owner,
side: Side::Bid,
price_lots,
max_base_lots: 1,
max_quote_lots: i64::MAX,
reduce_only: true,
client_order_id: 0,
},
)
.await
.unwrap();
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
assert_eq!(mango_account_0.perps[0].bids_base_lots, 1);
// account_0 - place a new ask should pass
send_tx(
solana,
PerpPlaceOrderInstruction {
account: account_0,
perp_market,
owner,
side: Side::Ask,
price_lots,
max_base_lots: 1,
max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0,
},
)
.await
.unwrap();
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
assert_eq!(mango_account_0.perps[0].asks_base_lots, 1);
// account_0 - place a new ask should pass
send_tx(
solana,
PerpPlaceOrderInstruction {
account: account_0,
perp_market,
owner,
side: Side::Ask,
price_lots,
max_base_lots: 1,
max_quote_lots: i64::MAX,
reduce_only: true,
client_order_id: 0,
},
)
.await
.unwrap();
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
assert_eq!(mango_account_0.perps[0].asks_base_lots, 2);
// account_0 - place a new ask should fail if not reduce_only
let res = send_tx(
solana,
PerpPlaceOrderInstruction {
account: account_0,
perp_market,
owner,
side: Side::Ask,
price_lots,
max_base_lots: 1,
max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0,
},
)
.await;
assert!(res.is_err());
// account_0 - place a new ask should pass but have no effect
send_tx(
solana,
PerpPlaceOrderInstruction {
account: account_0,
perp_market,
owner,
side: Side::Ask,
price_lots,
max_base_lots: 1,
max_quote_lots: i64::MAX,
reduce_only: true,
client_order_id: 0,
},
)
.await
.unwrap();
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
assert_eq!(mango_account_0.perps[0].asks_base_lots, 2);
// account_1 - place a new ask with reduce only false should fail
let res = send_tx(
solana,
PerpPlaceOrderInstruction {
account: account_1,
perp_market,
owner,
side: Side::Ask,
price_lots,
max_base_lots: 1,
max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0,
},
)
.await;
assert!(res.is_err());
// account_1 - place a new ask with reduce only true should pass
send_tx(
solana,
PerpPlaceOrderInstruction {
account: account_1,
perp_market,
owner,
side: Side::Ask,
price_lots,
max_base_lots: 1,
max_quote_lots: i64::MAX,
reduce_only: true,
client_order_id: 0,
},
)
.await
.unwrap();
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
assert_eq!(mango_account_1.perps[0].asks_base_lots, 1);
// account_1 - place a new bid should pass
send_tx(
solana,
PerpPlaceOrderInstruction {
account: account_1,
perp_market,
owner,
side: Side::Bid,
price_lots: price_lots / 2,
max_base_lots: 1,
max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0,
},
)
.await
.unwrap();
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
assert_eq!(mango_account_1.perps[0].bids_base_lots, 1);
// account_1 - place a new bid should pass
send_tx(
solana,
PerpPlaceOrderInstruction {
account: account_1,
perp_market,
owner,
side: Side::Bid,
price_lots: price_lots / 2,
max_base_lots: 1,
max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0,
},
)
.await
.unwrap();
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
assert_eq!(mango_account_1.perps[0].bids_base_lots, 2);
// account_1 - place a new bid should fail if reduce only is false
let res = send_tx(
solana,
PerpPlaceOrderInstruction {
account: account_1,
perp_market,
owner,
side: Side::Bid,
price_lots: price_lots / 2,
max_base_lots: 1,
max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 0,
},
)
.await;
assert!(res.is_err());
// account_1 - place a new bid should pass but have no effect
send_tx(
solana,
PerpPlaceOrderInstruction {
account: account_1,
perp_market,
owner,
side: Side::Bid,
price_lots: price_lots / 2,
max_base_lots: 1,
max_quote_lots: i64::MAX,
reduce_only: true,
client_order_id: 0,
},
)
.await
.unwrap();
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
assert_eq!(mango_account_1.perps[0].bids_base_lots, 2);
Ok(())
}

View File

@ -113,6 +113,7 @@ export class Bank implements BankForHealth {
netBorrowsInWindow: BN; netBorrowsInWindow: BN;
borrowWeightScaleStartQuote: number; borrowWeightScaleStartQuote: number;
depositWeightScaleStartQuote: number; depositWeightScaleStartQuote: number;
reduceOnly: number;
}, },
): Bank { ): Bank {
return new Bank( return new Bank(
@ -158,6 +159,7 @@ export class Bank implements BankForHealth {
obj.netBorrowsInWindow, obj.netBorrowsInWindow,
obj.borrowWeightScaleStartQuote, obj.borrowWeightScaleStartQuote,
obj.depositWeightScaleStartQuote, obj.depositWeightScaleStartQuote,
obj.reduceOnly == 1,
); );
} }
@ -204,6 +206,7 @@ export class Bank implements BankForHealth {
public netBorrowsInWindow: BN, public netBorrowsInWindow: BN,
public borrowWeightScaleStartQuote: number, public borrowWeightScaleStartQuote: number,
public depositWeightScaleStartQuote: number, public depositWeightScaleStartQuote: number,
public reduceOnly: boolean,
) { ) {
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0]; this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
this.oracleConfig = { this.oracleConfig = {

View File

@ -94,6 +94,7 @@ export class PerpMarket {
settleFeeFractionLowHealth: number; settleFeeFractionLowHealth: number;
settlePnlLimitFactor: number; settlePnlLimitFactor: number;
settlePnlLimitWindowSizeTs: BN; settlePnlLimitWindowSizeTs: BN;
reduceOnly: number;
}, },
): PerpMarket { ): PerpMarket {
return new PerpMarket( return new PerpMarket(
@ -137,6 +138,7 @@ export class PerpMarket {
obj.settleFeeFractionLowHealth, obj.settleFeeFractionLowHealth,
obj.settlePnlLimitFactor, obj.settlePnlLimitFactor,
obj.settlePnlLimitWindowSizeTs, obj.settlePnlLimitWindowSizeTs,
obj.reduceOnly == 1,
); );
} }
@ -181,6 +183,7 @@ export class PerpMarket {
public settleFeeFractionLowHealth: number, public settleFeeFractionLowHealth: number,
settlePnlLimitFactor: number, settlePnlLimitFactor: number,
settlePnlLimitWindowSizeTs: BN, settlePnlLimitWindowSizeTs: BN,
public reduceOnly: boolean,
) { ) {
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0]; this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
this.oracleConfig = { this.oracleConfig = {

View File

@ -24,6 +24,7 @@ export class Serum3Market {
serumMarketExternal: PublicKey; serumMarketExternal: PublicKey;
marketIndex: number; marketIndex: number;
registrationTime: BN; registrationTime: BN;
reduceOnly: number;
}, },
): Serum3Market { ): Serum3Market {
return new Serum3Market( return new Serum3Market(
@ -36,6 +37,7 @@ export class Serum3Market {
obj.serumMarketExternal, obj.serumMarketExternal,
obj.marketIndex as MarketIndex, obj.marketIndex as MarketIndex,
obj.registrationTime, obj.registrationTime,
obj.reduceOnly == 1,
); );
} }
@ -49,6 +51,7 @@ export class Serum3Market {
public serumMarketExternal: PublicKey, public serumMarketExternal: PublicKey,
public marketIndex: MarketIndex, public marketIndex: MarketIndex,
public registrationTime: BN, public registrationTime: BN,
public reduceOnly: boolean,
) { ) {
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0]; this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
} }

View File

@ -316,6 +316,7 @@ export class MangoClient {
depositWeightScaleStartQuote: number | null, depositWeightScaleStartQuote: number | null,
resetStablePrice: boolean | null, resetStablePrice: boolean | null,
resetNetBorrowLimit: boolean | null, resetNetBorrowLimit: boolean | null,
reduceOnly: boolean | null,
): Promise<TransactionSignature> { ): Promise<TransactionSignature> {
const bank = group.getFirstBankByMint(mintPk); const bank = group.getFirstBankByMint(mintPk);
const mintInfo = group.mintInfosMapByTokenIndex.get(bank.tokenIndex)!; const mintInfo = group.mintInfosMapByTokenIndex.get(bank.tokenIndex)!;
@ -347,6 +348,7 @@ export class MangoClient {
depositWeightScaleStartQuote, depositWeightScaleStartQuote,
resetStablePrice ?? false, resetStablePrice ?? false,
resetNetBorrowLimit ?? false, resetNetBorrowLimit ?? false,
reduceOnly,
) )
.accounts({ .accounts({
group: group.publicKey, group: group.publicKey,
@ -802,6 +804,7 @@ export class MangoClient {
mangoAccount: MangoAccount, mangoAccount: MangoAccount,
mintPk: PublicKey, mintPk: PublicKey,
amount: number, amount: number,
reduceOnly = false,
): Promise<TransactionSignature> { ): Promise<TransactionSignature> {
const decimals = group.getMintDecimals(mintPk); const decimals = group.getMintDecimals(mintPk);
const nativeAmount = toNative(amount, decimals); const nativeAmount = toNative(amount, decimals);
@ -810,6 +813,7 @@ export class MangoClient {
mangoAccount, mangoAccount,
mintPk, mintPk,
nativeAmount, nativeAmount,
reduceOnly,
); );
} }
@ -818,6 +822,7 @@ export class MangoClient {
mangoAccount: MangoAccount, mangoAccount: MangoAccount,
mintPk: PublicKey, mintPk: PublicKey,
nativeAmount: BN, nativeAmount: BN,
reduceOnly = false,
): Promise<TransactionSignature> { ): Promise<TransactionSignature> {
const bank = group.getFirstBankByMint(mintPk); const bank = group.getFirstBankByMint(mintPk);
@ -868,7 +873,7 @@ export class MangoClient {
); );
const ix = await this.program.methods const ix = await this.program.methods
.tokenDeposit(new BN(nativeAmount)) .tokenDeposit(new BN(nativeAmount), reduceOnly)
.accounts({ .accounts({
group: group.publicKey, group: group.publicKey,
account: mangoAccount.publicKey, account: mangoAccount.publicKey,
@ -1571,6 +1576,7 @@ export class MangoClient {
stablePriceGrowthLimit: number | null, stablePriceGrowthLimit: number | null,
settlePnlLimitFactor: number | null, settlePnlLimitFactor: number | null,
settlePnlLimitWindowSize: number | null, settlePnlLimitWindowSize: number | null,
reduceOnly: boolean | null,
): Promise<TransactionSignature> { ): Promise<TransactionSignature> {
const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex); const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex);
@ -1602,6 +1608,7 @@ export class MangoClient {
settlePnlLimitWindowSize !== null settlePnlLimitWindowSize !== null
? new BN(settlePnlLimitWindowSize) ? new BN(settlePnlLimitWindowSize)
: null, : null,
reduceOnly,
) )
.accounts({ .accounts({
group: group.publicKey, group: group.publicKey,

View File

@ -644,6 +644,12 @@ export type MangoV4 = {
{ {
"name": "resetNetBorrowLimit", "name": "resetNetBorrowLimit",
"type": "bool" "type": "bool"
},
{
"name": "reduceOnlyOpt",
"type": {
"option": "bool"
}
} }
] ]
}, },
@ -1200,6 +1206,10 @@ export type MangoV4 = {
{ {
"name": "amount", "name": "amount",
"type": "u64" "type": "u64"
},
{
"name": "reduceOnly",
"type": "bool"
} }
] ]
}, },
@ -1251,6 +1261,10 @@ export type MangoV4 = {
{ {
"name": "amount", "name": "amount",
"type": "u64" "type": "u64"
},
{
"name": "reduceOnly",
"type": "bool"
} }
] ]
}, },
@ -1511,6 +1525,34 @@ export type MangoV4 = {
} }
] ]
}, },
{
"name": "serum3EditMarket",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "admin",
"isMut": false,
"isSigner": true
},
{
"name": "market",
"isMut": true,
"isSigner": false
}
],
"args": [
{
"name": "reduceOnlyOpt",
"type": {
"option": "bool"
}
}
]
},
{ {
"name": "serum3DeregisterMarket", "name": "serum3DeregisterMarket",
"accounts": [ "accounts": [
@ -2660,6 +2702,12 @@ export type MangoV4 = {
"type": { "type": {
"option": "u64" "option": "u64"
} }
},
{
"name": "reduceOnlyOpt",
"type": {
"option": "bool"
}
} }
] ]
}, },
@ -3736,12 +3784,16 @@ export type MangoV4 = {
], ],
"type": "f64" "type": "f64"
}, },
{
"name": "reduceOnly",
"type": "u8"
},
{ {
"name": "reserved", "name": "reserved",
"type": { "type": {
"array": [ "array": [
"u8", "u8",
2120 2119
] ]
} }
} }
@ -4430,12 +4482,16 @@ export type MangoV4 = {
], ],
"type": "u64" "type": "u64"
}, },
{
"name": "reduceOnly",
"type": "u8"
},
{ {
"name": "reserved", "name": "reserved",
"type": { "type": {
"array": [ "array": [
"u8", "u8",
1944 1943
] ]
} }
} }
@ -4459,12 +4515,16 @@ export type MangoV4 = {
"name": "quoteTokenIndex", "name": "quoteTokenIndex",
"type": "u16" "type": "u16"
}, },
{
"name": "reduceOnly",
"type": "u8"
},
{ {
"name": "padding1", "name": "padding1",
"type": { "type": {
"array": [ "array": [
"u8", "u8",
4 3
] ]
} }
}, },
@ -7403,6 +7463,21 @@ export type MangoV4 = {
"code": 6028, "code": 6028,
"name": "TokenPositionDoesNotExist", "name": "TokenPositionDoesNotExist",
"msg": "token position does not exist" "msg": "token position does not exist"
},
{
"code": 6029,
"name": "DepositsIntoLiquidatingMustRecover",
"msg": "token deposits into accounts that are being liquidated must bring their health above the init threshold"
},
{
"code": 6030,
"name": "TokenInReduceOnlyMode",
"msg": "token is in reduce only mode"
},
{
"code": 6031,
"name": "MarketInReduceOnlyMode",
"msg": "market is in reduce only mode"
} }
] ]
}; };
@ -8053,6 +8128,12 @@ export const IDL: MangoV4 = {
{ {
"name": "resetNetBorrowLimit", "name": "resetNetBorrowLimit",
"type": "bool" "type": "bool"
},
{
"name": "reduceOnlyOpt",
"type": {
"option": "bool"
}
} }
] ]
}, },
@ -8609,6 +8690,10 @@ export const IDL: MangoV4 = {
{ {
"name": "amount", "name": "amount",
"type": "u64" "type": "u64"
},
{
"name": "reduceOnly",
"type": "bool"
} }
] ]
}, },
@ -8660,6 +8745,10 @@ export const IDL: MangoV4 = {
{ {
"name": "amount", "name": "amount",
"type": "u64" "type": "u64"
},
{
"name": "reduceOnly",
"type": "bool"
} }
] ]
}, },
@ -8920,6 +9009,34 @@ export const IDL: MangoV4 = {
} }
] ]
}, },
{
"name": "serum3EditMarket",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "admin",
"isMut": false,
"isSigner": true
},
{
"name": "market",
"isMut": true,
"isSigner": false
}
],
"args": [
{
"name": "reduceOnlyOpt",
"type": {
"option": "bool"
}
}
]
},
{ {
"name": "serum3DeregisterMarket", "name": "serum3DeregisterMarket",
"accounts": [ "accounts": [
@ -10069,6 +10186,12 @@ export const IDL: MangoV4 = {
"type": { "type": {
"option": "u64" "option": "u64"
} }
},
{
"name": "reduceOnlyOpt",
"type": {
"option": "bool"
}
} }
] ]
}, },
@ -11145,12 +11268,16 @@ export const IDL: MangoV4 = {
], ],
"type": "f64" "type": "f64"
}, },
{
"name": "reduceOnly",
"type": "u8"
},
{ {
"name": "reserved", "name": "reserved",
"type": { "type": {
"array": [ "array": [
"u8", "u8",
2120 2119
] ]
} }
} }
@ -11839,12 +11966,16 @@ export const IDL: MangoV4 = {
], ],
"type": "u64" "type": "u64"
}, },
{
"name": "reduceOnly",
"type": "u8"
},
{ {
"name": "reserved", "name": "reserved",
"type": { "type": {
"array": [ "array": [
"u8", "u8",
1944 1943
] ]
} }
} }
@ -11868,12 +11999,16 @@ export const IDL: MangoV4 = {
"name": "quoteTokenIndex", "name": "quoteTokenIndex",
"type": "u16" "type": "u16"
}, },
{
"name": "reduceOnly",
"type": "u8"
},
{ {
"name": "padding1", "name": "padding1",
"type": { "type": {
"array": [ "array": [
"u8", "u8",
4 3
] ]
} }
}, },
@ -14812,6 +14947,21 @@ export const IDL: MangoV4 = {
"code": 6028, "code": 6028,
"name": "TokenPositionDoesNotExist", "name": "TokenPositionDoesNotExist",
"msg": "token position does not exist" "msg": "token position does not exist"
},
{
"code": 6029,
"name": "DepositsIntoLiquidatingMustRecover",
"msg": "token deposits into accounts that are being liquidated must bring their health above the init threshold"
},
{
"code": 6030,
"name": "TokenInReduceOnlyMode",
"msg": "token is in reduce only mode"
},
{
"code": 6031,
"name": "MarketInReduceOnlyMode",
"msg": "market is in reduce only mode"
} }
] ]
}; };

View File

@ -1,9 +1,13 @@
import { AnchorProvider, Wallet } from '@project-serum/anchor'; import { AnchorProvider, Wallet } from '@project-serum/anchor';
import { import {
AddressLookupTableProgram, AddressLookupTableProgram,
ComputeBudgetProgram,
Connection, Connection,
Keypair, Keypair,
PublicKey, PublicKey,
SYSVAR_INSTRUCTIONS_PUBKEY,
SYSVAR_RENT_PUBKEY,
SystemProgram,
} from '@solana/web3.js'; } from '@solana/web3.js';
import fs from 'fs'; import fs from 'fs';
import { TokenIndex } from '../accounts/bank'; import { TokenIndex } from '../accounts/bank';
@ -14,8 +18,13 @@ import {
Serum3Side, Serum3Side,
} from '../accounts/serum3'; } from '../accounts/serum3';
import { MangoClient } from '../client'; import { MangoClient } from '../client';
import { MANGO_V4_ID } from '../constants'; import { MANGO_V4_ID, OPENBOOK_PROGRAM_ID } from '../constants';
import { buildVersionedTx, toNative } from '../utils'; import { buildVersionedTx, toNative } from '../utils';
import {
TOKEN_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID,
NATIVE_MINT,
} from '@solana/spl-token';
const GROUP_NUM = Number(process.env.GROUP_NUM || 0); const GROUP_NUM = Number(process.env.GROUP_NUM || 0);
@ -44,8 +53,11 @@ const MAINNET_SERUM3_MARKETS = new Map([
['SOL/USDC', '8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'], ['SOL/USDC', '8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'],
]); ]);
const { MB_CLUSTER_URL, MB_PAYER_KEYPAIR, MB_USER_KEYPAIR, MB_USER4_KEYPAIR } = const {
process.env; MB_CLUSTER_URL,
MB_PAYER_KEYPAIR,
MB_PAYER3_KEYPAIR: MB_PAYER2_KEYPAIR,
} = process.env;
const MIN_VAULT_TO_DEPOSITS_RATIO = 0.2; const MIN_VAULT_TO_DEPOSITS_RATIO = 0.2;
const NET_BORROWS_WINDOW_SIZE_TS = 24 * 60 * 60; const NET_BORROWS_WINDOW_SIZE_TS = 24 * 60 * 60;
@ -67,7 +79,7 @@ const defaultInterestRate = {
async function buildAdminClient(): Promise<[MangoClient, Keypair]> { async function buildAdminClient(): Promise<[MangoClient, Keypair]> {
const admin = Keypair.fromSecretKey( const admin = Keypair.fromSecretKey(
Buffer.from(JSON.parse(fs.readFileSync(MB_PAYER_KEYPAIR!, 'utf-8'))), Buffer.from(JSON.parse(fs.readFileSync(MB_PAYER2_KEYPAIR!, 'utf-8'))),
); );
const options = AnchorProvider.defaultOptions(); const options = AnchorProvider.defaultOptions();
@ -76,17 +88,17 @@ async function buildAdminClient(): Promise<[MangoClient, Keypair]> {
const adminWallet = new Wallet(admin); const adminWallet = new Wallet(admin);
console.log(`Admin ${adminWallet.publicKey.toBase58()}`); console.log(`Admin ${adminWallet.publicKey.toBase58()}`);
const adminProvider = new AnchorProvider(connection, adminWallet, options); const adminProvider = new AnchorProvider(connection, adminWallet, options);
return [
await MangoClient.connect( const client = await MangoClient.connect(
adminProvider, adminProvider,
'mainnet-beta', 'mainnet-beta',
MANGO_V4_ID['mainnet-beta'], MANGO_V4_ID['mainnet-beta'],
{ {
idsSource: 'get-program-accounts', idsSource: 'get-program-accounts',
}, },
), );
admin,
]; return [client, admin];
} }
async function buildUserClient( async function buildUserClient(
@ -107,11 +119,11 @@ async function buildUserClient(
MANGO_V4_ID['mainnet-beta'], MANGO_V4_ID['mainnet-beta'],
); );
const admin = Keypair.fromSecretKey( const creator = Keypair.fromSecretKey(
Buffer.from(JSON.parse(fs.readFileSync(MB_PAYER_KEYPAIR!, 'utf-8'))), Buffer.from(JSON.parse(fs.readFileSync(MB_PAYER_KEYPAIR!, 'utf-8'))),
); );
console.log(`Admin ${admin.publicKey.toBase58()}`); console.log(`Creator ${creator.publicKey.toBase58()}`);
const group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM); const group = await client.getGroupForCreator(creator.publicKey, GROUP_NUM);
return [client, group, user]; return [client, group, user];
} }
@ -436,56 +448,16 @@ async function registerPerpMarkets() {
); );
} }
async function main() {
try {
// await createGroup();
// await changeAdmin();
} catch (error) {
console.log(error);
}
try {
// await registerTokens();
} catch (error) {
console.log(error);
}
try {
// await registerSerum3Markets();
} catch (error) {
console.log(error);
}
try {
// await registerPerpMarkets();
} catch (error) {
console.log(error);
}
try {
// await createUser(MB_USER_KEYPAIR!);
// depositForUser(MB_USER_KEYPAIR!);
} catch (error) {
console.log(error);
}
try {
} catch (error) {}
}
try {
main();
} catch (error) {
console.log(error);
}
////////////////////////////////////////////////////////////
/// UNUSED /////////////////////////////////////////////////
////////////////////////////////////////////////////////////
async function createAndPopulateAlt() { async function createAndPopulateAlt() {
const result = await buildAdminClient(); const result = await buildAdminClient();
const client = result[0]; const client = result[0];
const admin = result[1]; const admin = result[1];
const group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM); const creator = Keypair.fromSecretKey(
Buffer.from(JSON.parse(fs.readFileSync(MB_PAYER_KEYPAIR!, 'utf-8'))),
);
console.log(`Creator ${creator.publicKey.toBase58()}`);
const group = await client.getGroupForCreator(creator.publicKey, GROUP_NUM);
const connection = client.program.provider.connection; const connection = client.program.provider.connection;
@ -591,15 +563,75 @@ async function createAndPopulateAlt() {
} }
console.log(`ALT: extending using mango v4 relevant public keys`); console.log(`ALT: extending using mango v4 relevant public keys`);
await extendTable(bankAddresses); await extendTable(bankAddresses);
await extendTable([OPENBOOK_PROGRAM_ID['mainnet-beta']]);
await extendTable(serum3MarketAddresses); await extendTable(serum3MarketAddresses);
await extendTable(serum3ExternalMarketAddresses); await extendTable(serum3ExternalMarketAddresses);
await extendTable(perpMarketAddresses);
// TODO: dont extend for perps atm
// await extendTable(perpMarketAddresses);
// Well known addressess
await extendTable([
SystemProgram.programId,
SYSVAR_RENT_PUBKEY,
TOKEN_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID,
NATIVE_MINT,
SYSVAR_INSTRUCTIONS_PUBKEY,
ComputeBudgetProgram.programId,
]);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
} }
async function main() {
try {
// await createGroup();
// await changeAdmin();
} catch (error) {
console.log(error);
}
try {
// await registerTokens();
} catch (error) {
console.log(error);
}
try {
// await registerSerum3Markets();
} catch (error) {
console.log(error);
}
try {
// await registerPerpMarkets();
} catch (error) {
console.log(error);
}
try {
// await createUser(MB_USER_KEYPAIR!);
// depositForUser(MB_USER_KEYPAIR!);
} catch (error) {
console.log(error);
}
try {
createAndPopulateAlt();
} catch (error) {}
}
try {
main();
} catch (error) {
console.log(error);
}
////////////////////////////////////////////////////////////
/// UNUSED /////////////////////////////////////////////////
////////////////////////////////////////////////////////////
async function expandMangoAccount(userKeypair: string) { async function expandMangoAccount(userKeypair: string) {
const result = await buildUserClient(userKeypair); const result = await buildUserClient(userKeypair);
const client = result[0]; const client = result[0];

View File

@ -66,6 +66,7 @@ async function editPerpMarket(perpMarketName: string) {
null, null,
null, null,
null, null,
null,
); );
console.log('Tx Successful:', signature); console.log('Tx Successful:', signature);

View File

@ -123,6 +123,7 @@ async function main() {
null, null,
true, true,
null, null,
null,
); );
} }
async function setPerpPrice( async function setPerpPrice(
@ -158,6 +159,7 @@ async function main() {
null, null,
null, null,
null, null,
null,
); );
} }
@ -262,6 +264,7 @@ async function main() {
null, null,
null, null,
null, null,
null,
); );
try { try {
// At a price of $1/ui-SOL we can buy 0.1 ui-SOL for the 100k native-USDC we have. // At a price of $1/ui-SOL we can buy 0.1 ui-SOL for the 100k native-USDC we have.
@ -304,6 +307,7 @@ async function main() {
null, null,
null, null,
null, null,
null,
); );
} }
} }

View File

@ -10,9 +10,7 @@ import { MANGO_V4_ID } from '../constants';
// //
const GROUP_NUM = Number(process.env.GROUP_NUM || 200); const GROUP_NUM = Number(process.env.GROUP_NUM || 200);
const CLUSTER_URL = const CLUSTER_URL = process.env.CLUSTER_URL;
process.env.CLUSTER_URL ||
'https://mango.rpcpool.com/946ef7337da3f5b8d3e4a34e7f88';
const MANGO_MAINNET_PAYER_KEYPAIR = const MANGO_MAINNET_PAYER_KEYPAIR =
process.env.MANGO_MAINNET_PAYER_KEYPAIR || ''; process.env.MANGO_MAINNET_PAYER_KEYPAIR || '';

View File

@ -28,6 +28,7 @@ import {
makeInitSequenceEnforcerAccountIx, makeInitSequenceEnforcerAccountIx,
seqEnforcerProgramIds, seqEnforcerProgramIds,
} from './sequence-enforcer-util'; } from './sequence-enforcer-util';
import * as defaultParams from './params/default.json';
// Future // Future
// * use async nodejs logging // * use async nodejs logging
@ -229,7 +230,11 @@ async function fullMarketMaker() {
const options = AnchorProvider.defaultOptions(); const options = AnchorProvider.defaultOptions();
const connection = new Connection(CLUSTER_URL!, options); const connection = new Connection(CLUSTER_URL!, options);
const user = Keypair.fromSecretKey( const user = Keypair.fromSecretKey(
Buffer.from(JSON.parse(fs.readFileSync(USER_KEYPAIR!, 'utf-8'))), Buffer.from(
JSON.parse(
process.env.KEYPAIR || fs.readFileSync(USER_KEYPAIR!, 'utf-8'),
),
),
); );
const userWallet = new Wallet(user); const userWallet = new Wallet(user);
const userProvider = new AnchorProvider(connection, userWallet, options); const userProvider = new AnchorProvider(connection, userWallet, options);

View File

@ -5,7 +5,7 @@
"assets": { "assets": {
"BTC": { "BTC": {
"perp": { "perp": {
"sizePerc": 0.05, "sizePerc": 0.75,
"leanCoeff": 0.00025, "leanCoeff": 0.00025,
"bias": 0.0, "bias": 0.0,
"requoteThresh": 0.0002, "requoteThresh": 0.0002,
@ -13,17 +13,6 @@
"spammerCharge": 2, "spammerCharge": 2,
"krakenCode": "XXBTZUSD" "krakenCode": "XXBTZUSD"
} }
},
"MNGO": {
"perp": {
"sizePerc": 0.05,
"leanCoeff": 0.00025,
"bias": 0.0,
"requoteThresh": 0.0002,
"takeSpammers": true,
"spammerCharge": 2,
"krakenCode": "MNGOUSD"
}
} }
} }
} }

View File

@ -3,7 +3,8 @@
"esModuleInterop": true, "esModuleInterop": true,
"moduleResolution": "node", "moduleResolution": "node",
"lib": [ "lib": [
"es2019" "es2019",
"dom"
], ],
"outDir": "./dist", "outDir": "./dist",
"resolveJsonModule": true, "resolveJsonModule": true,

View File

@ -675,10 +675,10 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240"
integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==
"@types/node@^14.14.37": "@types/node@^18.11.18":
version "14.18.12" version "18.11.18"
resolved "https://registry.npmjs.org/@types/node/-/node-14.18.12.tgz" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f"
integrity sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A== integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==
"@types/promise-retry@^1.1.3": "@types/promise-retry@^1.1.3":
version "1.1.3" version "1.1.3"