Add force_withdraw state and instruction (#884)
Co-authored-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
parent
338a9cb7b8
commit
46c6e86206
|
@ -1067,6 +1067,12 @@
|
||||||
"type": {
|
"type": {
|
||||||
"option": "f32"
|
"option": "f32"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "forceWithdrawOpt",
|
||||||
|
"type": {
|
||||||
|
"option": "bool"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -3789,6 +3795,63 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "tokenForceWithdraw",
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "group",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "account",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"group"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bank",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"group",
|
||||||
|
"vault",
|
||||||
|
"oracle"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vault",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "oracle",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ownerAtaTokenAccount",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "alternateOwnerTokenAccount",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"docs": [
|
||||||
|
"Only for the unusual case where the owner_ata account is not owned by account.owner"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tokenProgram",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "perpCreateMarket",
|
"name": "perpCreateMarket",
|
||||||
"docs": [
|
"docs": [
|
||||||
|
@ -7426,12 +7489,16 @@
|
||||||
],
|
],
|
||||||
"type": "u8"
|
"type": "u8"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "forceWithdraw",
|
||||||
|
"type": "u8"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "padding",
|
"name": "padding",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"u8",
|
||||||
5
|
4
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -10938,6 +11005,9 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Serum3PlaceOrderV2"
|
"name": "Serum3PlaceOrderV2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TokenForceWithdraw"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,7 @@ pub use token_deposit::*;
|
||||||
pub use token_deregister::*;
|
pub use token_deregister::*;
|
||||||
pub use token_edit::*;
|
pub use token_edit::*;
|
||||||
pub use token_force_close_borrows_with_token::*;
|
pub use token_force_close_borrows_with_token::*;
|
||||||
|
pub use token_force_withdraw::*;
|
||||||
pub use token_liq_bankruptcy::*;
|
pub use token_liq_bankruptcy::*;
|
||||||
pub use token_liq_with_token::*;
|
pub use token_liq_with_token::*;
|
||||||
pub use token_register::*;
|
pub use token_register::*;
|
||||||
|
@ -145,6 +146,7 @@ mod token_deposit;
|
||||||
mod token_deregister;
|
mod token_deregister;
|
||||||
mod token_edit;
|
mod token_edit;
|
||||||
mod token_force_close_borrows_with_token;
|
mod token_force_close_borrows_with_token;
|
||||||
|
mod token_force_withdraw;
|
||||||
mod token_liq_bankruptcy;
|
mod token_liq_bankruptcy;
|
||||||
mod token_liq_with_token;
|
mod token_liq_with_token;
|
||||||
mod token_register;
|
mod token_register;
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
use anchor_lang::prelude::*;
|
||||||
|
use anchor_spl::associated_token::get_associated_token_address;
|
||||||
|
use anchor_spl::token::Token;
|
||||||
|
use anchor_spl::token::TokenAccount;
|
||||||
|
|
||||||
|
use crate::error::*;
|
||||||
|
use crate::state::*;
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct TokenForceWithdraw<'info> {
|
||||||
|
#[account(
|
||||||
|
constraint = group.load()?.is_ix_enabled(IxGate::TokenForceWithdraw) @ MangoError::IxIsDisabled,
|
||||||
|
)]
|
||||||
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
has_one = group,
|
||||||
|
constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen,
|
||||||
|
)]
|
||||||
|
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||||
|
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
has_one = group,
|
||||||
|
has_one = vault,
|
||||||
|
has_one = oracle,
|
||||||
|
// the mints of bank/vault/token_accounts are implicitly the same because
|
||||||
|
// spl::token::transfer succeeds between token_account and vault
|
||||||
|
)]
|
||||||
|
pub bank: AccountLoader<'info, Bank>,
|
||||||
|
|
||||||
|
#[account(mut)]
|
||||||
|
pub vault: Box<Account<'info, TokenAccount>>,
|
||||||
|
|
||||||
|
/// CHECK: The oracle can be one of several different account types
|
||||||
|
pub oracle: UncheckedAccount<'info>,
|
||||||
|
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
address = get_associated_token_address(&account.load()?.owner, &vault.mint),
|
||||||
|
// NOTE: the owner may have been changed (before immutable owner was a thing)
|
||||||
|
)]
|
||||||
|
pub owner_ata_token_account: Box<Account<'info, TokenAccount>>,
|
||||||
|
|
||||||
|
/// Only for the unusual case where the owner_ata account is not owned by account.owner
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
constraint = alternate_owner_token_account.owner == account.load()?.owner,
|
||||||
|
)]
|
||||||
|
pub alternate_owner_token_account: Box<Account<'info, TokenAccount>>,
|
||||||
|
|
||||||
|
pub token_program: Program<'info, Token>,
|
||||||
|
}
|
|
@ -95,6 +95,7 @@ pub fn ix_gate_set(ctx: Context<IxGateSet>, ix_gate: u128) -> Result<()> {
|
||||||
IxGate::TokenConditionalSwapCreateLinearAuction,
|
IxGate::TokenConditionalSwapCreateLinearAuction,
|
||||||
);
|
);
|
||||||
log_if_changed(&group, ix_gate, IxGate::Serum3PlaceOrderV2);
|
log_if_changed(&group, ix_gate, IxGate::Serum3PlaceOrderV2);
|
||||||
|
log_if_changed(&group, ix_gate, IxGate::TokenForceWithdraw);
|
||||||
|
|
||||||
group.ix_gate = ix_gate;
|
group.ix_gate = ix_gate;
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,7 @@ pub use token_deposit::*;
|
||||||
pub use token_deregister::*;
|
pub use token_deregister::*;
|
||||||
pub use token_edit::*;
|
pub use token_edit::*;
|
||||||
pub use token_force_close_borrows_with_token::*;
|
pub use token_force_close_borrows_with_token::*;
|
||||||
|
pub use token_force_withdraw::*;
|
||||||
pub use token_liq_bankruptcy::*;
|
pub use token_liq_bankruptcy::*;
|
||||||
pub use token_liq_with_token::*;
|
pub use token_liq_with_token::*;
|
||||||
pub use token_register::*;
|
pub use token_register::*;
|
||||||
|
@ -127,6 +128,7 @@ mod token_deposit;
|
||||||
mod token_deregister;
|
mod token_deregister;
|
||||||
mod token_edit;
|
mod token_edit;
|
||||||
mod token_force_close_borrows_with_token;
|
mod token_force_close_borrows_with_token;
|
||||||
|
mod token_force_withdraw;
|
||||||
mod token_liq_bankruptcy;
|
mod token_liq_bankruptcy;
|
||||||
mod token_liq_with_token;
|
mod token_liq_with_token;
|
||||||
mod token_register;
|
mod token_register;
|
||||||
|
|
|
@ -55,6 +55,7 @@ pub fn token_edit(
|
||||||
platform_liquidation_fee: Option<f32>,
|
platform_liquidation_fee: Option<f32>,
|
||||||
disable_asset_liquidation_opt: Option<bool>,
|
disable_asset_liquidation_opt: Option<bool>,
|
||||||
collateral_fee_per_day: Option<f32>,
|
collateral_fee_per_day: Option<f32>,
|
||||||
|
force_withdraw_opt: Option<bool>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let group = ctx.accounts.group.load()?;
|
let group = ctx.accounts.group.load()?;
|
||||||
|
|
||||||
|
@ -510,6 +511,16 @@ pub fn token_edit(
|
||||||
bank.disable_asset_liquidation = u8::from(disable_asset_liquidation);
|
bank.disable_asset_liquidation = u8::from(disable_asset_liquidation);
|
||||||
require_group_admin = true;
|
require_group_admin = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(force_withdraw) = force_withdraw_opt {
|
||||||
|
msg!(
|
||||||
|
"Force withdraw old {:?}, new {:?}",
|
||||||
|
bank.force_withdraw,
|
||||||
|
force_withdraw
|
||||||
|
);
|
||||||
|
bank.force_withdraw = u8::from(force_withdraw);
|
||||||
|
require_group_admin = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// account constraint #1
|
// account constraint #1
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
use crate::accounts_zerocopy::AccountInfoRef;
|
||||||
|
use crate::error::*;
|
||||||
|
use crate::state::*;
|
||||||
|
use anchor_lang::prelude::*;
|
||||||
|
use anchor_spl::token;
|
||||||
|
use fixed::types::I80F48;
|
||||||
|
|
||||||
|
use crate::accounts_ix::*;
|
||||||
|
use crate::logs::{emit_stack, ForceWithdrawLog, TokenBalanceLog};
|
||||||
|
|
||||||
|
pub fn token_force_withdraw(ctx: Context<TokenForceWithdraw>) -> Result<()> {
|
||||||
|
let group = ctx.accounts.group.load()?;
|
||||||
|
let token_index = ctx.accounts.bank.load()?.token_index;
|
||||||
|
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
|
||||||
|
|
||||||
|
let mut bank = ctx.accounts.bank.load_mut()?;
|
||||||
|
require!(bank.is_force_withdraw(), MangoError::SomeError);
|
||||||
|
|
||||||
|
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||||
|
|
||||||
|
let withdraw_target = if ctx.accounts.owner_ata_token_account.owner == account.fixed.owner {
|
||||||
|
ctx.accounts.owner_ata_token_account.to_account_info()
|
||||||
|
} else {
|
||||||
|
ctx.accounts.alternate_owner_token_account.to_account_info()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (position, raw_token_index) = account.token_position_mut(token_index)?;
|
||||||
|
let native_position = position.native(&bank);
|
||||||
|
|
||||||
|
// Check >= to allow calling this on 0 deposits to close the token position
|
||||||
|
require_gte!(native_position, I80F48::ZERO);
|
||||||
|
let amount = native_position.floor().to_num::<u64>();
|
||||||
|
let amount_i80f48 = I80F48::from(amount);
|
||||||
|
|
||||||
|
// Update the bank and position
|
||||||
|
let position_is_active = bank.withdraw_without_fee(position, amount_i80f48, now_ts)?;
|
||||||
|
|
||||||
|
// Provide a readable error message in case the vault doesn't have enough tokens
|
||||||
|
if ctx.accounts.vault.amount < amount {
|
||||||
|
return err!(MangoError::InsufficentBankVaultFunds).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"bank vault does not have enough tokens, need {} but have {}",
|
||||||
|
amount, ctx.accounts.vault.amount
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer the actual tokens
|
||||||
|
let group_seeds = group_seeds!(group);
|
||||||
|
token::transfer(
|
||||||
|
CpiContext::new(
|
||||||
|
ctx.accounts.token_program.to_account_info(),
|
||||||
|
token::Transfer {
|
||||||
|
from: ctx.accounts.vault.to_account_info(),
|
||||||
|
to: withdraw_target.clone(),
|
||||||
|
authority: ctx.accounts.group.to_account_info(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_signer(&[group_seeds]),
|
||||||
|
amount,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
emit_stack(TokenBalanceLog {
|
||||||
|
mango_group: ctx.accounts.group.key(),
|
||||||
|
mango_account: ctx.accounts.account.key(),
|
||||||
|
token_index,
|
||||||
|
indexed_position: position.indexed_position.to_bits(),
|
||||||
|
deposit_index: bank.deposit_index.to_bits(),
|
||||||
|
borrow_index: bank.borrow_index.to_bits(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the oracle price, even if stale or unconfident: We want to allow force withdraws
|
||||||
|
// even if the oracle is bad.
|
||||||
|
let oracle_ref = &AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?;
|
||||||
|
let unsafe_oracle_state = oracle_state_unchecked(
|
||||||
|
&OracleAccountInfos::from_reader(oracle_ref),
|
||||||
|
bank.mint_decimals,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Update the net deposits - adjust by price so different tokens are on the same basis (in USD terms)
|
||||||
|
let amount_usd = (amount_i80f48 * unsafe_oracle_state.price).to_num::<i64>();
|
||||||
|
account.fixed.net_deposits -= amount_usd;
|
||||||
|
|
||||||
|
if !position_is_active {
|
||||||
|
account.deactivate_token_position_and_log(raw_token_index, ctx.accounts.account.key());
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_stack(ForceWithdrawLog {
|
||||||
|
mango_group: ctx.accounts.group.key(),
|
||||||
|
mango_account: ctx.accounts.account.key(),
|
||||||
|
token_index,
|
||||||
|
quantity: amount,
|
||||||
|
price: unsafe_oracle_state.price.to_bits(),
|
||||||
|
to_token_account: withdraw_target.key(),
|
||||||
|
});
|
||||||
|
|
||||||
|
bank.enforce_borrows_lte_deposits()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -112,6 +112,7 @@ pub fn token_register(
|
||||||
reduce_only,
|
reduce_only,
|
||||||
force_close: 0,
|
force_close: 0,
|
||||||
disable_asset_liquidation: u8::from(disable_asset_liquidation),
|
disable_asset_liquidation: u8::from(disable_asset_liquidation),
|
||||||
|
force_withdraw: 0,
|
||||||
padding: Default::default(),
|
padding: Default::default(),
|
||||||
fees_withdrawn: 0,
|
fees_withdrawn: 0,
|
||||||
token_conditional_swap_taker_fee_rate,
|
token_conditional_swap_taker_fee_rate,
|
||||||
|
|
|
@ -91,6 +91,7 @@ pub fn token_register_trustless(
|
||||||
reduce_only: 2, // deposit-only
|
reduce_only: 2, // deposit-only
|
||||||
force_close: 0,
|
force_close: 0,
|
||||||
disable_asset_liquidation: 1,
|
disable_asset_liquidation: 1,
|
||||||
|
force_withdraw: 0,
|
||||||
padding: Default::default(),
|
padding: Default::default(),
|
||||||
fees_withdrawn: 0,
|
fees_withdrawn: 0,
|
||||||
token_conditional_swap_taker_fee_rate: 0.0,
|
token_conditional_swap_taker_fee_rate: 0.0,
|
||||||
|
|
|
@ -253,6 +253,7 @@ pub mod mango_v4 {
|
||||||
platform_liquidation_fee_opt: Option<f32>,
|
platform_liquidation_fee_opt: Option<f32>,
|
||||||
disable_asset_liquidation_opt: Option<bool>,
|
disable_asset_liquidation_opt: Option<bool>,
|
||||||
collateral_fee_per_day_opt: Option<f32>,
|
collateral_fee_per_day_opt: Option<f32>,
|
||||||
|
force_withdraw_opt: Option<bool>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
#[cfg(feature = "enable-gpl")]
|
#[cfg(feature = "enable-gpl")]
|
||||||
instructions::token_edit(
|
instructions::token_edit(
|
||||||
|
@ -297,6 +298,7 @@ pub mod mango_v4 {
|
||||||
platform_liquidation_fee_opt,
|
platform_liquidation_fee_opt,
|
||||||
disable_asset_liquidation_opt,
|
disable_asset_liquidation_opt,
|
||||||
collateral_fee_per_day_opt,
|
collateral_fee_per_day_opt,
|
||||||
|
force_withdraw_opt,
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -817,6 +819,12 @@ pub mod mango_v4 {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn token_force_withdraw(ctx: Context<TokenForceWithdraw>) -> Result<()> {
|
||||||
|
#[cfg(feature = "enable-gpl")]
|
||||||
|
instructions::token_force_withdraw(ctx)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Perps
|
/// Perps
|
||||||
///
|
///
|
||||||
|
|
|
@ -788,3 +788,13 @@ pub struct TokenCollateralFeeLog {
|
||||||
pub asset_usage_fraction: i128,
|
pub asset_usage_fraction: i128,
|
||||||
pub fee: i128,
|
pub fee: i128,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[event]
|
||||||
|
pub struct ForceWithdrawLog {
|
||||||
|
pub mango_group: Pubkey,
|
||||||
|
pub mango_account: Pubkey,
|
||||||
|
pub token_index: u16,
|
||||||
|
pub quantity: u64,
|
||||||
|
pub price: i128, // I80F48
|
||||||
|
pub to_token_account: Pubkey,
|
||||||
|
}
|
||||||
|
|
|
@ -162,8 +162,10 @@ pub struct Bank {
|
||||||
/// That means bankrupt accounts may still have assets of this type deposited.
|
/// That means bankrupt accounts may still have assets of this type deposited.
|
||||||
pub disable_asset_liquidation: u8,
|
pub disable_asset_liquidation: u8,
|
||||||
|
|
||||||
|
pub force_withdraw: u8,
|
||||||
|
|
||||||
#[derivative(Debug = "ignore")]
|
#[derivative(Debug = "ignore")]
|
||||||
pub padding: [u8; 5],
|
pub padding: [u8; 4],
|
||||||
|
|
||||||
// Do separate bookkeping for how many tokens were withdrawn
|
// Do separate bookkeping for how many tokens were withdrawn
|
||||||
// This ensures that collected_fees_native is strictly increasing for stats gathering purposes
|
// This ensures that collected_fees_native is strictly increasing for stats gathering purposes
|
||||||
|
@ -361,7 +363,8 @@ impl Bank {
|
||||||
reduce_only: existing_bank.reduce_only,
|
reduce_only: existing_bank.reduce_only,
|
||||||
force_close: existing_bank.force_close,
|
force_close: existing_bank.force_close,
|
||||||
disable_asset_liquidation: existing_bank.disable_asset_liquidation,
|
disable_asset_liquidation: existing_bank.disable_asset_liquidation,
|
||||||
padding: [0; 5],
|
force_withdraw: existing_bank.force_withdraw,
|
||||||
|
padding: [0; 4],
|
||||||
token_conditional_swap_taker_fee_rate: existing_bank
|
token_conditional_swap_taker_fee_rate: existing_bank
|
||||||
.token_conditional_swap_taker_fee_rate,
|
.token_conditional_swap_taker_fee_rate,
|
||||||
token_conditional_swap_maker_fee_rate: existing_bank
|
token_conditional_swap_maker_fee_rate: existing_bank
|
||||||
|
@ -417,6 +420,11 @@ impl Bank {
|
||||||
require_eq!(self.maint_asset_weight, I80F48::ZERO);
|
require_eq!(self.maint_asset_weight, I80F48::ZERO);
|
||||||
}
|
}
|
||||||
require_gte!(self.collateral_fee_per_day, 0.0);
|
require_gte!(self.collateral_fee_per_day, 0.0);
|
||||||
|
if self.is_force_withdraw() {
|
||||||
|
require!(self.are_deposits_reduce_only(), MangoError::SomeError);
|
||||||
|
require!(!self.allows_asset_liquidation(), MangoError::SomeError);
|
||||||
|
require_eq!(self.maint_asset_weight, I80F48::ZERO);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,6 +446,10 @@ impl Bank {
|
||||||
self.force_close == 1
|
self.force_close == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_force_withdraw(&self) -> bool {
|
||||||
|
self.force_withdraw == 1
|
||||||
|
}
|
||||||
|
|
||||||
pub fn allows_asset_liquidation(&self) -> bool {
|
pub fn allows_asset_liquidation(&self) -> bool {
|
||||||
self.disable_asset_liquidation == 0
|
self.disable_asset_liquidation == 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -245,6 +245,7 @@ pub enum IxGate {
|
||||||
TokenConditionalSwapCreatePremiumAuction = 69,
|
TokenConditionalSwapCreatePremiumAuction = 69,
|
||||||
TokenConditionalSwapCreateLinearAuction = 70,
|
TokenConditionalSwapCreateLinearAuction = 70,
|
||||||
Serum3PlaceOrderV2 = 71,
|
Serum3PlaceOrderV2 = 71,
|
||||||
|
TokenForceWithdraw = 72,
|
||||||
// NOTE: Adding new variants requires matching changes in ts and the ix_gate_set instruction.
|
// NOTE: Adding new variants requires matching changes in ts and the ix_gate_set instruction.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -438,3 +438,113 @@ async fn test_force_close_perp() -> Result<(), TransportError> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_force_withdraw_token() -> Result<(), TransportError> {
|
||||||
|
let test_builder = TestContextBuilder::new();
|
||||||
|
let context = test_builder.start_default().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..1];
|
||||||
|
|
||||||
|
//
|
||||||
|
// SETUP: Create a group and an account to fill the vaults
|
||||||
|
//
|
||||||
|
|
||||||
|
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
|
||||||
|
admin,
|
||||||
|
payer,
|
||||||
|
mints: mints.to_vec(),
|
||||||
|
..GroupWithTokensConfig::default()
|
||||||
|
}
|
||||||
|
.create(solana)
|
||||||
|
.await;
|
||||||
|
let token = &tokens[0];
|
||||||
|
|
||||||
|
let deposit_amount = 100;
|
||||||
|
|
||||||
|
let account = create_funded_account(
|
||||||
|
&solana,
|
||||||
|
group,
|
||||||
|
owner,
|
||||||
|
0,
|
||||||
|
&context.users[0],
|
||||||
|
mints,
|
||||||
|
deposit_amount,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
//
|
||||||
|
// TEST: fails when force withdraw isn't enabled
|
||||||
|
//
|
||||||
|
assert!(send_tx(
|
||||||
|
solana,
|
||||||
|
TokenForceWithdrawInstruction {
|
||||||
|
account,
|
||||||
|
bank: token.bank,
|
||||||
|
target: context.users[0].token_accounts[0],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.is_err());
|
||||||
|
|
||||||
|
// set force withdraw to enabled
|
||||||
|
send_tx(
|
||||||
|
solana,
|
||||||
|
TokenEdit {
|
||||||
|
admin,
|
||||||
|
group,
|
||||||
|
mint: token.mint.pubkey,
|
||||||
|
fallback_oracle: Pubkey::default(),
|
||||||
|
options: mango_v4::instruction::TokenEdit {
|
||||||
|
maint_asset_weight_opt: Some(0.0),
|
||||||
|
reduce_only_opt: Some(1),
|
||||||
|
disable_asset_liquidation_opt: Some(true),
|
||||||
|
force_withdraw_opt: Some(true),
|
||||||
|
..token_edit_instruction_default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
//
|
||||||
|
// TEST: can't withdraw to foreign address
|
||||||
|
//
|
||||||
|
assert!(send_tx(
|
||||||
|
solana,
|
||||||
|
TokenForceWithdrawInstruction {
|
||||||
|
account,
|
||||||
|
bank: token.bank,
|
||||||
|
target: context.users[1].token_accounts[0], // bad address/owner
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.is_err());
|
||||||
|
|
||||||
|
//
|
||||||
|
// TEST: passes and withdraws tokens
|
||||||
|
//
|
||||||
|
let token_account = context.users[0].token_accounts[0];
|
||||||
|
let before_balance = solana.token_account_balance(token_account).await;
|
||||||
|
send_tx(
|
||||||
|
solana,
|
||||||
|
TokenForceWithdrawInstruction {
|
||||||
|
account,
|
||||||
|
bank: token.bank,
|
||||||
|
target: token_account,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let after_balance = solana.token_account_balance(token_account).await;
|
||||||
|
assert_eq!(after_balance, before_balance + deposit_amount);
|
||||||
|
assert!(account_position_closed(solana, account, token.bank).await);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -1328,6 +1328,7 @@ pub fn token_edit_instruction_default() -> mango_v4::instruction::TokenEdit {
|
||||||
platform_liquidation_fee_opt: None,
|
platform_liquidation_fee_opt: None,
|
||||||
disable_asset_liquidation_opt: None,
|
disable_asset_liquidation_opt: None,
|
||||||
collateral_fee_per_day_opt: None,
|
collateral_fee_per_day_opt: None,
|
||||||
|
force_withdraw_opt: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3112,6 +3113,58 @@ impl ClientInstruction for TokenForceCloseBorrowsWithTokenInstruction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct TokenForceWithdrawInstruction {
|
||||||
|
pub account: Pubkey,
|
||||||
|
pub bank: Pubkey,
|
||||||
|
pub target: Pubkey,
|
||||||
|
}
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl ClientInstruction for TokenForceWithdrawInstruction {
|
||||||
|
type Accounts = mango_v4::accounts::TokenForceWithdraw;
|
||||||
|
type Instruction = mango_v4::instruction::TokenForceWithdraw;
|
||||||
|
async fn to_instruction(
|
||||||
|
&self,
|
||||||
|
account_loader: impl ClientAccountLoader + 'async_trait,
|
||||||
|
) -> (Self::Accounts, instruction::Instruction) {
|
||||||
|
let program_id = mango_v4::id();
|
||||||
|
let instruction = Self::Instruction {};
|
||||||
|
|
||||||
|
let account = account_loader
|
||||||
|
.load_mango_account(&self.account)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let bank = account_loader.load::<Bank>(&self.bank).await.unwrap();
|
||||||
|
let health_check_metas = derive_health_check_remaining_account_metas(
|
||||||
|
&account_loader,
|
||||||
|
&account,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let accounts = Self::Accounts {
|
||||||
|
group: account.fixed.group,
|
||||||
|
account: self.account,
|
||||||
|
bank: self.bank,
|
||||||
|
vault: bank.vault,
|
||||||
|
oracle: bank.oracle,
|
||||||
|
owner_ata_token_account: self.target,
|
||||||
|
alternate_owner_token_account: self.target,
|
||||||
|
token_program: Token::id(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut instruction = make_instruction(program_id, &accounts, &instruction);
|
||||||
|
instruction.accounts.extend(health_check_metas.into_iter());
|
||||||
|
|
||||||
|
(accounts, instruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signers(&self) -> Vec<TestKeypair> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TokenLiqWithTokenInstruction {
|
pub struct TokenLiqWithTokenInstruction {
|
||||||
pub liqee: Pubkey,
|
pub liqee: Pubkey,
|
||||||
pub liqor: Pubkey,
|
pub liqor: Pubkey,
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { AnchorProvider, Wallet } from '@coral-xyz/anchor';
|
||||||
|
import { Cluster, Connection, Keypair, PublicKey } from '@solana/web3.js';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { TokenIndex } from '../src/accounts/bank';
|
||||||
|
import { MangoClient } from '../src/client';
|
||||||
|
import { MANGO_V4_ID } from '../src/constants';
|
||||||
|
|
||||||
|
const CLUSTER: Cluster =
|
||||||
|
(process.env.CLUSTER_OVERRIDE as Cluster) || 'mainnet-beta';
|
||||||
|
const CLUSTER_URL =
|
||||||
|
process.env.CLUSTER_URL_OVERRIDE || process.env.MB_CLUSTER_URL;
|
||||||
|
const USER_KEYPAIR =
|
||||||
|
process.env.USER_KEYPAIR_OVERRIDE || process.env.MB_PAYER_KEYPAIR;
|
||||||
|
const GROUP_PK =
|
||||||
|
process.env.GROUP_PK || '78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX';
|
||||||
|
const TOKEN_INDEX = Number(process.env.TOKEN_INDEX) as TokenIndex;
|
||||||
|
|
||||||
|
async function forceWithdrawTokens(): Promise<void> {
|
||||||
|
const options = AnchorProvider.defaultOptions();
|
||||||
|
const connection = new Connection(CLUSTER_URL!, options);
|
||||||
|
const user = Keypair.fromSecretKey(
|
||||||
|
Buffer.from(
|
||||||
|
JSON.parse(
|
||||||
|
process.env.KEYPAIR || fs.readFileSync(USER_KEYPAIR!, 'utf-8'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const userWallet = new Wallet(user);
|
||||||
|
const userProvider = new AnchorProvider(connection, userWallet, options);
|
||||||
|
const client = await MangoClient.connect(
|
||||||
|
userProvider,
|
||||||
|
CLUSTER,
|
||||||
|
MANGO_V4_ID[CLUSTER],
|
||||||
|
{
|
||||||
|
idsSource: 'get-program-accounts',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const group = await client.getGroup(new PublicKey(GROUP_PK));
|
||||||
|
const forceWithdrawBank = group.getFirstBankByTokenIndex(TOKEN_INDEX);
|
||||||
|
if (forceWithdrawBank.reduceOnly != 2) {
|
||||||
|
throw new Error(
|
||||||
|
`Unexpected reduce only state ${forceWithdrawBank.reduceOnly}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!forceWithdrawBank.forceWithdraw) {
|
||||||
|
throw new Error(
|
||||||
|
`Unexpected force withdraw state ${forceWithdrawBank.forceWithdraw}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all mango accounts with deposits for given token
|
||||||
|
const mangoAccountsWithDeposits = (
|
||||||
|
await client.getAllMangoAccounts(group)
|
||||||
|
).filter((a) => a.getTokenBalanceUi(forceWithdrawBank) > 0);
|
||||||
|
|
||||||
|
for (const mangoAccount of mangoAccountsWithDeposits) {
|
||||||
|
const sig = await client.tokenForceWithdraw(
|
||||||
|
group,
|
||||||
|
mangoAccount,
|
||||||
|
TOKEN_INDEX,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
` tokenForceWithdraw for ${mangoAccount.publicKey}, owner ${
|
||||||
|
mangoAccount.owner
|
||||||
|
}, sig https://explorer.solana.com/tx/${sig}?cluster=${
|
||||||
|
CLUSTER == 'devnet' ? 'devnet' : ''
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
forceWithdrawTokens();
|
|
@ -132,6 +132,7 @@ export class Bank implements BankForHealth {
|
||||||
reduceOnly: number;
|
reduceOnly: number;
|
||||||
forceClose: number;
|
forceClose: number;
|
||||||
disableAssetLiquidation: number;
|
disableAssetLiquidation: number;
|
||||||
|
forceWithdraw: number;
|
||||||
feesWithdrawn: BN;
|
feesWithdrawn: BN;
|
||||||
tokenConditionalSwapTakerFeeRate: number;
|
tokenConditionalSwapTakerFeeRate: number;
|
||||||
tokenConditionalSwapMakerFeeRate: number;
|
tokenConditionalSwapMakerFeeRate: number;
|
||||||
|
@ -218,6 +219,7 @@ export class Bank implements BankForHealth {
|
||||||
obj.disableAssetLiquidation == 0,
|
obj.disableAssetLiquidation == 0,
|
||||||
obj.collectedCollateralFees,
|
obj.collectedCollateralFees,
|
||||||
obj.collateralFeePerDay,
|
obj.collateralFeePerDay,
|
||||||
|
obj.forceWithdraw == 1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,6 +288,7 @@ export class Bank implements BankForHealth {
|
||||||
public allowAssetLiquidation: boolean,
|
public allowAssetLiquidation: boolean,
|
||||||
collectedCollateralFees: I80F48Dto,
|
collectedCollateralFees: I80F48Dto,
|
||||||
public collateralFeePerDay: number,
|
public collateralFeePerDay: number,
|
||||||
|
public forceWithdraw: 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 = {
|
||||||
|
|
|
@ -1,16 +1,10 @@
|
||||||
import {
|
import { AnchorProvider, BN, Program, Wallet } from '@coral-xyz/anchor';
|
||||||
AnchorProvider,
|
|
||||||
BN,
|
|
||||||
Instruction,
|
|
||||||
Program,
|
|
||||||
Provider,
|
|
||||||
Wallet,
|
|
||||||
} from '@coral-xyz/anchor';
|
|
||||||
import * as borsh from '@coral-xyz/borsh';
|
|
||||||
import { OpenOrders, decodeEventQueue } from '@project-serum/serum';
|
import { OpenOrders, decodeEventQueue } from '@project-serum/serum';
|
||||||
import {
|
import {
|
||||||
|
createAccount,
|
||||||
createCloseAccountInstruction,
|
createCloseAccountInstruction,
|
||||||
createInitializeAccount3Instruction,
|
createInitializeAccount3Instruction,
|
||||||
|
unpackAccount,
|
||||||
} from '@solana/spl-token';
|
} from '@solana/spl-token';
|
||||||
import {
|
import {
|
||||||
AccountInfo,
|
AccountInfo,
|
||||||
|
@ -26,13 +20,13 @@ import {
|
||||||
RecentPrioritizationFees,
|
RecentPrioritizationFees,
|
||||||
SYSVAR_INSTRUCTIONS_PUBKEY,
|
SYSVAR_INSTRUCTIONS_PUBKEY,
|
||||||
SYSVAR_RENT_PUBKEY,
|
SYSVAR_RENT_PUBKEY,
|
||||||
|
Signer,
|
||||||
SystemProgram,
|
SystemProgram,
|
||||||
TransactionInstruction,
|
TransactionInstruction,
|
||||||
TransactionSignature,
|
|
||||||
} from '@solana/web3.js';
|
} from '@solana/web3.js';
|
||||||
import bs58 from 'bs58';
|
import bs58 from 'bs58';
|
||||||
import chunk from 'lodash/chunk';
|
|
||||||
import copy from 'fast-copy';
|
import copy from 'fast-copy';
|
||||||
|
import chunk from 'lodash/chunk';
|
||||||
import groupBy from 'lodash/groupBy';
|
import groupBy from 'lodash/groupBy';
|
||||||
import mapValues from 'lodash/mapValues';
|
import mapValues from 'lodash/mapValues';
|
||||||
import maxBy from 'lodash/maxBy';
|
import maxBy from 'lodash/maxBy';
|
||||||
|
@ -45,7 +39,6 @@ import {
|
||||||
Serum3Orders,
|
Serum3Orders,
|
||||||
TokenConditionalSwap,
|
TokenConditionalSwap,
|
||||||
TokenConditionalSwapDisplayPriceStyle,
|
TokenConditionalSwapDisplayPriceStyle,
|
||||||
TokenConditionalSwapDto,
|
|
||||||
TokenConditionalSwapIntention,
|
TokenConditionalSwapIntention,
|
||||||
TokenPosition,
|
TokenPosition,
|
||||||
} from './accounts/mangoAccount';
|
} from './accounts/mangoAccount';
|
||||||
|
@ -70,7 +63,6 @@ import {
|
||||||
} from './accounts/serum3';
|
} from './accounts/serum3';
|
||||||
import {
|
import {
|
||||||
IxGateParams,
|
IxGateParams,
|
||||||
PerpEditParams,
|
|
||||||
TokenEditParams,
|
TokenEditParams,
|
||||||
TokenRegisterParams,
|
TokenRegisterParams,
|
||||||
buildIxGate,
|
buildIxGate,
|
||||||
|
@ -559,6 +551,7 @@ export class MangoClient {
|
||||||
params.platformLiquidationFee,
|
params.platformLiquidationFee,
|
||||||
params.disableAssetLiquidation,
|
params.disableAssetLiquidation,
|
||||||
params.collateralFeePerDay,
|
params.collateralFeePerDay,
|
||||||
|
params.forceWithdraw,
|
||||||
)
|
)
|
||||||
.accounts({
|
.accounts({
|
||||||
group: group.publicKey,
|
group: group.publicKey,
|
||||||
|
@ -625,6 +618,94 @@ export class MangoClient {
|
||||||
return await this.sendAndConfirmTransactionForGroup(group, [ix]);
|
return await this.sendAndConfirmTransactionForGroup(group, [ix]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async tokenForceWithdraw(
|
||||||
|
group: Group,
|
||||||
|
mangoAccount: MangoAccount,
|
||||||
|
tokenIndex: TokenIndex,
|
||||||
|
): Promise<MangoSignatureStatus> {
|
||||||
|
const bank = group.getFirstBankByTokenIndex(tokenIndex);
|
||||||
|
if (!bank.forceWithdraw) {
|
||||||
|
throw new Error('Bank is not in force-withdraw mode');
|
||||||
|
}
|
||||||
|
|
||||||
|
const ownerAtaTokenAccount = await getAssociatedTokenAddress(
|
||||||
|
bank.mint,
|
||||||
|
mangoAccount.owner,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
let alternateOwnerTokenAccount = PublicKey.default;
|
||||||
|
const preInstructions: TransactionInstruction[] = [];
|
||||||
|
const postInstructions: TransactionInstruction[] = [];
|
||||||
|
|
||||||
|
const ai = await this.connection.getAccountInfo(ownerAtaTokenAccount);
|
||||||
|
|
||||||
|
// ensure withdraws don't fail with missing ATAs
|
||||||
|
if (ai == null) {
|
||||||
|
preInstructions.push(
|
||||||
|
await createAssociatedTokenAccountIdempotentInstruction(
|
||||||
|
(this.program.provider as AnchorProvider).wallet.publicKey,
|
||||||
|
mangoAccount.owner,
|
||||||
|
bank.mint,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// wsol case
|
||||||
|
if (bank.mint.equals(NATIVE_MINT)) {
|
||||||
|
postInstructions.push(
|
||||||
|
createCloseAccountInstruction(
|
||||||
|
ownerAtaTokenAccount,
|
||||||
|
mangoAccount.owner,
|
||||||
|
mangoAccount.owner,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const account = await unpackAccount(ownerAtaTokenAccount, ai);
|
||||||
|
// if owner is not same as mango account's owner on the ATA (for whatever reason)
|
||||||
|
// then create another token account
|
||||||
|
if (!account.owner.equals(mangoAccount.owner)) {
|
||||||
|
const kp = Keypair.generate();
|
||||||
|
alternateOwnerTokenAccount = kp.publicKey;
|
||||||
|
await createAccount(
|
||||||
|
this.connection,
|
||||||
|
(this.program.provider as AnchorProvider).wallet as any as Signer,
|
||||||
|
bank.mint,
|
||||||
|
mangoAccount.owner,
|
||||||
|
kp,
|
||||||
|
);
|
||||||
|
|
||||||
|
// wsol case
|
||||||
|
if (bank.mint.equals(NATIVE_MINT)) {
|
||||||
|
postInstructions.push(
|
||||||
|
createCloseAccountInstruction(
|
||||||
|
alternateOwnerTokenAccount,
|
||||||
|
mangoAccount.owner,
|
||||||
|
mangoAccount.owner,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ix = await this.program.methods
|
||||||
|
.tokenForceWithdraw()
|
||||||
|
.accounts({
|
||||||
|
group: group.publicKey,
|
||||||
|
account: mangoAccount.publicKey,
|
||||||
|
bank: bank.publicKey,
|
||||||
|
vault: bank.vault,
|
||||||
|
oracle: bank.oracle,
|
||||||
|
ownerAtaTokenAccount,
|
||||||
|
alternateOwnerTokenAccount,
|
||||||
|
})
|
||||||
|
.instruction();
|
||||||
|
return await this.sendAndConfirmTransactionForGroup(group, [
|
||||||
|
...preInstructions,
|
||||||
|
ix,
|
||||||
|
...postInstructions,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public async tokenDeregister(
|
public async tokenDeregister(
|
||||||
group: Group,
|
group: Group,
|
||||||
mintPk: PublicKey,
|
mintPk: PublicKey,
|
||||||
|
|
|
@ -117,6 +117,7 @@ export interface TokenEditParams {
|
||||||
platformLiquidationFee: number | null;
|
platformLiquidationFee: number | null;
|
||||||
disableAssetLiquidation: boolean | null;
|
disableAssetLiquidation: boolean | null;
|
||||||
collateralFeePerDay: number | null;
|
collateralFeePerDay: number | null;
|
||||||
|
forceWithdraw: boolean | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NullTokenEditParams: TokenEditParams = {
|
export const NullTokenEditParams: TokenEditParams = {
|
||||||
|
@ -160,6 +161,7 @@ export const NullTokenEditParams: TokenEditParams = {
|
||||||
platformLiquidationFee: null,
|
platformLiquidationFee: null,
|
||||||
disableAssetLiquidation: null,
|
disableAssetLiquidation: null,
|
||||||
collateralFeePerDay: null,
|
collateralFeePerDay: null,
|
||||||
|
forceWithdraw: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface PerpEditParams {
|
export interface PerpEditParams {
|
||||||
|
@ -307,6 +309,7 @@ export interface IxGateParams {
|
||||||
TokenConditionalSwapCreatePremiumAuction: boolean;
|
TokenConditionalSwapCreatePremiumAuction: boolean;
|
||||||
TokenConditionalSwapCreateLinearAuction: boolean;
|
TokenConditionalSwapCreateLinearAuction: boolean;
|
||||||
Serum3PlaceOrderV2: boolean;
|
Serum3PlaceOrderV2: boolean;
|
||||||
|
TokenForceWithdraw: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default with all ixs enabled, use with buildIxGate
|
// Default with all ixs enabled, use with buildIxGate
|
||||||
|
@ -386,6 +389,7 @@ export const TrueIxGateParams: IxGateParams = {
|
||||||
TokenConditionalSwapCreatePremiumAuction: true,
|
TokenConditionalSwapCreatePremiumAuction: true,
|
||||||
TokenConditionalSwapCreateLinearAuction: true,
|
TokenConditionalSwapCreateLinearAuction: true,
|
||||||
Serum3PlaceOrderV2: true,
|
Serum3PlaceOrderV2: true,
|
||||||
|
TokenForceWithdraw: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// build ix gate e.g. buildIxGate(Builder(TrueIxGateParams).TokenDeposit(false).build()).toNumber(),
|
// build ix gate e.g. buildIxGate(Builder(TrueIxGateParams).TokenDeposit(false).build()).toNumber(),
|
||||||
|
@ -475,6 +479,7 @@ export function buildIxGate(p: IxGateParams): BN {
|
||||||
toggleIx(ixGate, p, 'TokenConditionalSwapCreatePremiumAuction', 69);
|
toggleIx(ixGate, p, 'TokenConditionalSwapCreatePremiumAuction', 69);
|
||||||
toggleIx(ixGate, p, 'TokenConditionalSwapCreateLinearAuction', 70);
|
toggleIx(ixGate, p, 'TokenConditionalSwapCreateLinearAuction', 70);
|
||||||
toggleIx(ixGate, p, 'Serum3PlaceOrderV2', 71);
|
toggleIx(ixGate, p, 'Serum3PlaceOrderV2', 71);
|
||||||
|
toggleIx(ixGate, p, 'TokenForceWithdraw', 72);
|
||||||
|
|
||||||
return ixGate;
|
return ixGate;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1067,6 +1067,12 @@ export type MangoV4 = {
|
||||||
"type": {
|
"type": {
|
||||||
"option": "f32"
|
"option": "f32"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "forceWithdrawOpt",
|
||||||
|
"type": {
|
||||||
|
"option": "bool"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -3789,6 +3795,63 @@ export type MangoV4 = {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "tokenForceWithdraw",
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "group",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "account",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"group"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bank",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"group",
|
||||||
|
"vault",
|
||||||
|
"oracle"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vault",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "oracle",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ownerAtaTokenAccount",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "alternateOwnerTokenAccount",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"docs": [
|
||||||
|
"Only for the unusual case where the owner_ata account is not owned by account.owner"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tokenProgram",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "perpCreateMarket",
|
"name": "perpCreateMarket",
|
||||||
"docs": [
|
"docs": [
|
||||||
|
@ -7426,12 +7489,16 @@ export type MangoV4 = {
|
||||||
],
|
],
|
||||||
"type": "u8"
|
"type": "u8"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "forceWithdraw",
|
||||||
|
"type": "u8"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "padding",
|
"name": "padding",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"u8",
|
||||||
5
|
4
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -10938,6 +11005,9 @@ export type MangoV4 = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Serum3PlaceOrderV2"
|
"name": "Serum3PlaceOrderV2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TokenForceWithdraw"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -15245,6 +15315,12 @@ export const IDL: MangoV4 = {
|
||||||
"type": {
|
"type": {
|
||||||
"option": "f32"
|
"option": "f32"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "forceWithdrawOpt",
|
||||||
|
"type": {
|
||||||
|
"option": "bool"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -17967,6 +18043,63 @@ export const IDL: MangoV4 = {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "tokenForceWithdraw",
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "group",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "account",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"group"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bank",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"group",
|
||||||
|
"vault",
|
||||||
|
"oracle"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vault",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "oracle",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ownerAtaTokenAccount",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "alternateOwnerTokenAccount",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"docs": [
|
||||||
|
"Only for the unusual case where the owner_ata account is not owned by account.owner"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tokenProgram",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "perpCreateMarket",
|
"name": "perpCreateMarket",
|
||||||
"docs": [
|
"docs": [
|
||||||
|
@ -21604,12 +21737,16 @@ export const IDL: MangoV4 = {
|
||||||
],
|
],
|
||||||
"type": "u8"
|
"type": "u8"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "forceWithdraw",
|
||||||
|
"type": "u8"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "padding",
|
"name": "padding",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"u8",
|
||||||
5
|
4
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -25116,6 +25253,9 @@ export const IDL: MangoV4 = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Serum3PlaceOrderV2"
|
"name": "Serum3PlaceOrderV2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TokenForceWithdraw"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue