Merge branch 'dev'
This commit is contained in:
commit
3c2a55bf07
21
README.md
21
README.md
|
@ -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,
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
14
package.json
14
package.json
|
@ -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"
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {}",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
|
@ -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(
|
||||||
|
|
|
@ -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())?;
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(())
|
|
||||||
}
|
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
|
@ -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 = {
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 || '';
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue