program: add a sequence check IX (#908)
Add a sequence check IX This new IX `SequenceCheck` can be used to avoid having multiple concurrent TX in flight causing unexpected result (multiple borrow for example)
This commit is contained in:
parent
81f05b32c7
commit
494835631b
|
@ -1760,6 +1760,36 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "sequenceCheck",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"isMut": true,
|
||||
"isSigner": false,
|
||||
"relations": [
|
||||
"group",
|
||||
"owner"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "owner",
|
||||
"isMut": false,
|
||||
"isSigner": true
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "expectedSequenceNumber",
|
||||
"type": "u64"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stubOracleCreate",
|
||||
"accounts": [
|
||||
|
@ -7942,12 +7972,16 @@
|
|||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "sequenceNumber",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
152
|
||||
144
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -9721,12 +9755,16 @@
|
|||
"name": "lastCollateralFeeCharge",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "sequenceNumber",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
152
|
||||
144
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -11008,6 +11046,9 @@
|
|||
},
|
||||
{
|
||||
"name": "TokenForceWithdraw"
|
||||
},
|
||||
{
|
||||
"name": "SequenceCheck"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -14350,6 +14391,16 @@
|
|||
"code": 6069,
|
||||
"name": "TokenAssetLiquidationDisabled",
|
||||
"msg": "the asset does not allow liquidation"
|
||||
},
|
||||
{
|
||||
"code": 6070,
|
||||
"name": "BorrowsRequireHealthAccountBank",
|
||||
"msg": "for borrows the bank must be in the health account list"
|
||||
},
|
||||
{
|
||||
"code": 6071,
|
||||
"name": "InvalidSequenceNumber",
|
||||
"msg": "invalid sequence number"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -45,6 +45,7 @@ pub use perp_place_order::*;
|
|||
pub use perp_settle_fees::*;
|
||||
pub use perp_settle_pnl::*;
|
||||
pub use perp_update_funding::*;
|
||||
pub use sequence_check::*;
|
||||
pub use serum3_cancel_all_orders::*;
|
||||
pub use serum3_cancel_order::*;
|
||||
pub use serum3_close_open_orders::*;
|
||||
|
@ -123,6 +124,7 @@ mod perp_place_order;
|
|||
mod perp_settle_fees;
|
||||
mod perp_settle_pnl;
|
||||
mod perp_update_funding;
|
||||
mod sequence_check;
|
||||
mod serum3_cancel_all_orders;
|
||||
mod serum3_cancel_order;
|
||||
mod serum3_close_open_orders;
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
use crate::error::*;
|
||||
use crate::state::*;
|
||||
use anchor_lang::prelude::*;
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct SequenceCheck<'info> {
|
||||
#[account(
|
||||
constraint = group.load()?.is_ix_enabled(IxGate::SequenceCheck) @ MangoError::IxIsDisabled,
|
||||
)]
|
||||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
has_one = group,
|
||||
has_one = owner,
|
||||
constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen
|
||||
)]
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub owner: Signer<'info>,
|
||||
}
|
|
@ -147,6 +147,8 @@ pub enum MangoError {
|
|||
TokenAssetLiquidationDisabled,
|
||||
#[msg("for borrows the bank must be in the health account list")]
|
||||
BorrowsRequireHealthAccountBank,
|
||||
#[msg("invalid sequence number")]
|
||||
InvalidSequenceNumber,
|
||||
}
|
||||
|
||||
impl MangoError {
|
||||
|
|
|
@ -96,6 +96,7 @@ pub fn ix_gate_set(ctx: Context<IxGateSet>, ix_gate: u128) -> Result<()> {
|
|||
);
|
||||
log_if_changed(&group, ix_gate, IxGate::Serum3PlaceOrderV2);
|
||||
log_if_changed(&group, ix_gate, IxGate::TokenForceWithdraw);
|
||||
log_if_changed(&group, ix_gate, IxGate::SequenceCheck);
|
||||
|
||||
group.ix_gate = ix_gate;
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ pub use perp_place_order::*;
|
|||
pub use perp_settle_fees::*;
|
||||
pub use perp_settle_pnl::*;
|
||||
pub use perp_update_funding::*;
|
||||
pub use sequence_check::*;
|
||||
pub use serum3_cancel_all_orders::*;
|
||||
pub use serum3_cancel_order::*;
|
||||
pub use serum3_cancel_order_by_client_order_id::*;
|
||||
|
@ -104,6 +105,7 @@ mod perp_place_order;
|
|||
mod perp_settle_fees;
|
||||
mod perp_settle_pnl;
|
||||
mod perp_update_funding;
|
||||
mod sequence_check;
|
||||
mod serum3_cancel_all_orders;
|
||||
mod serum3_cancel_order;
|
||||
mod serum3_cancel_order_by_client_order_id;
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
use crate::accounts_ix::*;
|
||||
use crate::error::MangoError;
|
||||
use crate::state::*;
|
||||
|
||||
pub fn sequence_check(ctx: Context<SequenceCheck>, expected_sequence_number: u64) -> Result<()> {
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
|
||||
require_eq!(
|
||||
expected_sequence_number,
|
||||
account.fixed.sequence_number,
|
||||
MangoError::InvalidSequenceNumber
|
||||
);
|
||||
|
||||
account.fixed.sequence_number = account.fixed.sequence_number.wrapping_add(1);
|
||||
Ok(())
|
||||
}
|
|
@ -458,6 +458,15 @@ pub mod mango_v4 {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn sequence_check(
|
||||
ctx: Context<SequenceCheck>,
|
||||
expected_sequence_number: u64,
|
||||
) -> Result<()> {
|
||||
#[cfg(feature = "enable-gpl")]
|
||||
instructions::sequence_check(ctx, expected_sequence_number)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// todo:
|
||||
// ckamm: generally, using an I80F48 arg will make it harder to call
|
||||
// because generic anchor clients won't know how to deal with it
|
||||
|
|
|
@ -246,6 +246,7 @@ pub enum IxGate {
|
|||
TokenConditionalSwapCreateLinearAuction = 70,
|
||||
Serum3PlaceOrderV2 = 71,
|
||||
TokenForceWithdraw = 72,
|
||||
SequenceCheck = 73,
|
||||
// NOTE: Adding new variants requires matching changes in ts and the ix_gate_set instruction.
|
||||
}
|
||||
|
||||
|
|
|
@ -157,8 +157,10 @@ pub struct MangoAccount {
|
|||
/// Time at which the last collateral fee was charged
|
||||
pub last_collateral_fee_charge: u64,
|
||||
|
||||
pub sequence_number: u64,
|
||||
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub reserved: [u8; 152],
|
||||
pub reserved: [u8; 144],
|
||||
|
||||
// dynamic
|
||||
pub header_version: u8,
|
||||
|
@ -212,7 +214,8 @@ impl MangoAccount {
|
|||
temporary_delegate: Pubkey::default(),
|
||||
temporary_delegate_expiry: 0,
|
||||
last_collateral_fee_charge: 0,
|
||||
reserved: [0; 152],
|
||||
sequence_number: 0,
|
||||
reserved: [0; 144],
|
||||
header_version: DEFAULT_MANGO_ACCOUNT_VERSION,
|
||||
padding3: Default::default(),
|
||||
padding4: Default::default(),
|
||||
|
@ -337,11 +340,12 @@ pub struct MangoAccountFixed {
|
|||
pub temporary_delegate: Pubkey,
|
||||
pub temporary_delegate_expiry: u64,
|
||||
pub last_collateral_fee_charge: u64,
|
||||
pub reserved: [u8; 152],
|
||||
pub sequence_number: u64,
|
||||
pub reserved: [u8; 144],
|
||||
}
|
||||
const_assert_eq!(
|
||||
size_of::<MangoAccountFixed>(),
|
||||
32 * 4 + 8 + 8 * 8 + 32 + 8 + 8 + 152
|
||||
32 * 4 + 8 + 8 * 8 + 32 + 8 + 8 + 8 + 144
|
||||
);
|
||||
const_assert_eq!(size_of::<MangoAccountFixed>(), 400);
|
||||
const_assert_eq!(size_of::<MangoAccountFixed>() % 8, 0);
|
||||
|
@ -2909,7 +2913,8 @@ mod tests {
|
|||
temporary_delegate: fixed.temporary_delegate,
|
||||
temporary_delegate_expiry: fixed.temporary_delegate_expiry,
|
||||
last_collateral_fee_charge: fixed.last_collateral_fee_charge,
|
||||
reserved: [0u8; 152],
|
||||
sequence_number: 0,
|
||||
reserved: [0u8; 144],
|
||||
|
||||
header_version: *zerocopy_reader.header_version(),
|
||||
padding3: Default::default(),
|
||||
|
|
|
@ -947,3 +947,105 @@ async fn test_withdraw_skip_bank() -> Result<(), TransportError> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_sequence_check() -> 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..1];
|
||||
|
||||
let mango_setup::GroupWithTokens { group, .. } = mango_setup::GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints: mints.to_vec(),
|
||||
..mango_setup::GroupWithTokensConfig::default()
|
||||
}
|
||||
.create(solana)
|
||||
.await;
|
||||
|
||||
let account = send_tx(
|
||||
solana,
|
||||
AccountCreateInstruction {
|
||||
account_num: 0,
|
||||
token_count: 6,
|
||||
serum3_count: 3,
|
||||
perp_count: 3,
|
||||
perp_oo_count: 3,
|
||||
token_conditional_swap_count: 3,
|
||||
group,
|
||||
owner,
|
||||
payer,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
let mango_account = get_mango_account(solana, account).await;
|
||||
assert_eq!(mango_account.fixed.sequence_number, 0);
|
||||
|
||||
//
|
||||
// TEST: Sequence check with right sequence number
|
||||
//
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
SequenceCheckInstruction {
|
||||
account,
|
||||
owner,
|
||||
expected_sequence_number: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mango_account = get_mango_account(solana, account).await;
|
||||
assert_eq!(mango_account.fixed.sequence_number, 1);
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
SequenceCheckInstruction {
|
||||
account,
|
||||
owner,
|
||||
expected_sequence_number: 1,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mango_account = get_mango_account(solana, account).await;
|
||||
assert_eq!(mango_account.fixed.sequence_number, 2);
|
||||
|
||||
//
|
||||
// TEST: Sequence check with wrong sequence number
|
||||
//
|
||||
|
||||
send_tx_expect_error!(
|
||||
solana,
|
||||
SequenceCheckInstruction {
|
||||
account,
|
||||
owner,
|
||||
expected_sequence_number: 1
|
||||
},
|
||||
MangoError::InvalidSequenceNumber
|
||||
);
|
||||
|
||||
send_tx_expect_error!(
|
||||
solana,
|
||||
SequenceCheckInstruction {
|
||||
account,
|
||||
owner,
|
||||
expected_sequence_number: 4
|
||||
},
|
||||
MangoError::InvalidSequenceNumber
|
||||
);
|
||||
|
||||
let mango_account = get_mango_account(solana, account).await;
|
||||
assert_eq!(mango_account.fixed.sequence_number, 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -5169,3 +5169,42 @@ impl<T: ClientInstruction> ClientInstruction for HealthAccountSkipping<T> {
|
|||
self.inner.signers()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SequenceCheckInstruction {
|
||||
pub account: Pubkey,
|
||||
pub owner: TestKeypair,
|
||||
pub expected_sequence_number: u64,
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ClientInstruction for SequenceCheckInstruction {
|
||||
type Accounts = mango_v4::accounts::SequenceCheck;
|
||||
type Instruction = mango_v4::instruction::SequenceCheck;
|
||||
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 {
|
||||
expected_sequence_number: self.expected_sequence_number,
|
||||
};
|
||||
|
||||
let account = account_loader
|
||||
.load_mango_account(&self.account)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: account.fixed.group,
|
||||
account: self.account,
|
||||
owner: self.owner.pubkey(),
|
||||
};
|
||||
|
||||
let instruction = make_instruction(program_id, &accounts, &instruction);
|
||||
(accounts, instruction)
|
||||
}
|
||||
|
||||
fn signers(&self) -> Vec<TestKeypair> {
|
||||
vec![self.owner]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ describe('Mango Account', () => {
|
|||
new BN(0),
|
||||
new BN(0),
|
||||
new BN(0),
|
||||
new BN(0),
|
||||
0,
|
||||
[],
|
||||
[],
|
||||
|
|
|
@ -44,6 +44,7 @@ export class MangoAccount {
|
|||
buybackFeesAccruedCurrent: BN;
|
||||
buybackFeesAccruedPrevious: BN;
|
||||
buybackFeesExpiryTimestamp: BN;
|
||||
sequenceNumber: BN;
|
||||
headerVersion: number;
|
||||
tokens: unknown;
|
||||
serum3: unknown;
|
||||
|
@ -68,6 +69,7 @@ export class MangoAccount {
|
|||
obj.buybackFeesAccruedCurrent,
|
||||
obj.buybackFeesAccruedPrevious,
|
||||
obj.buybackFeesExpiryTimestamp,
|
||||
obj.sequenceNumber,
|
||||
obj.headerVersion,
|
||||
obj.tokens as TokenPositionDto[],
|
||||
obj.serum3 as Serum3PositionDto[],
|
||||
|
@ -94,6 +96,7 @@ export class MangoAccount {
|
|||
public buybackFeesAccruedCurrent: BN,
|
||||
public buybackFeesAccruedPrevious: BN,
|
||||
public buybackFeesExpiryTimestamp: BN,
|
||||
public sequenceNumber: BN,
|
||||
public headerVersion: number,
|
||||
tokens: TokenPositionDto[],
|
||||
serum3: Serum3PositionDto[],
|
||||
|
|
|
@ -1034,6 +1034,20 @@ export class MangoClient {
|
|||
return await this.sendAndConfirmTransactionForGroup(group, [ix]);
|
||||
}
|
||||
|
||||
public async sequenceCheckIx(
|
||||
group: Group,
|
||||
mangoAccount: MangoAccount,
|
||||
): Promise<TransactionInstruction> {
|
||||
return await this.program.methods
|
||||
.sequenceCheck(mangoAccount.sequenceNumber)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
account: mangoAccount.publicKey,
|
||||
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
|
||||
})
|
||||
.instruction();
|
||||
}
|
||||
|
||||
public async getMangoAccount(
|
||||
mangoAccountPk: PublicKey,
|
||||
loadSerum3Oo = false,
|
||||
|
|
|
@ -310,6 +310,7 @@ export interface IxGateParams {
|
|||
TokenConditionalSwapCreateLinearAuction: boolean;
|
||||
Serum3PlaceOrderV2: boolean;
|
||||
TokenForceWithdraw: boolean;
|
||||
SequenceCheck: boolean;
|
||||
}
|
||||
|
||||
// Default with all ixs enabled, use with buildIxGate
|
||||
|
@ -390,6 +391,7 @@ export const TrueIxGateParams: IxGateParams = {
|
|||
TokenConditionalSwapCreateLinearAuction: true,
|
||||
Serum3PlaceOrderV2: true,
|
||||
TokenForceWithdraw: true,
|
||||
SequenceCheck: true,
|
||||
};
|
||||
|
||||
// build ix gate e.g. buildIxGate(Builder(TrueIxGateParams).TokenDeposit(false).build()).toNumber(),
|
||||
|
@ -480,6 +482,7 @@ export function buildIxGate(p: IxGateParams): BN {
|
|||
toggleIx(ixGate, p, 'TokenConditionalSwapCreateLinearAuction', 70);
|
||||
toggleIx(ixGate, p, 'Serum3PlaceOrderV2', 71);
|
||||
toggleIx(ixGate, p, 'TokenForceWithdraw', 72);
|
||||
toggleIx(ixGate, p, 'SequenceCheck', 73);
|
||||
|
||||
return ixGate;
|
||||
}
|
||||
|
|
|
@ -1760,6 +1760,36 @@ export type MangoV4 = {
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "sequenceCheck",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"isMut": true,
|
||||
"isSigner": false,
|
||||
"relations": [
|
||||
"group",
|
||||
"owner"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "owner",
|
||||
"isMut": false,
|
||||
"isSigner": true
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "expectedSequenceNumber",
|
||||
"type": "u64"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stubOracleCreate",
|
||||
"accounts": [
|
||||
|
@ -7942,12 +7972,16 @@ export type MangoV4 = {
|
|||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "sequenceNumber",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
152
|
||||
144
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -9721,12 +9755,16 @@ export type MangoV4 = {
|
|||
"name": "lastCollateralFeeCharge",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "sequenceNumber",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
152
|
||||
144
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -11008,6 +11046,9 @@ export type MangoV4 = {
|
|||
},
|
||||
{
|
||||
"name": "TokenForceWithdraw"
|
||||
},
|
||||
{
|
||||
"name": "SequenceCheck"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -14350,6 +14391,16 @@ export type MangoV4 = {
|
|||
"code": 6069,
|
||||
"name": "TokenAssetLiquidationDisabled",
|
||||
"msg": "the asset does not allow liquidation"
|
||||
},
|
||||
{
|
||||
"code": 6070,
|
||||
"name": "BorrowsRequireHealthAccountBank",
|
||||
"msg": "for borrows the bank must be in the health account list"
|
||||
},
|
||||
{
|
||||
"code": 6071,
|
||||
"name": "InvalidSequenceNumber",
|
||||
"msg": "invalid sequence number"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -16116,6 +16167,36 @@ export const IDL: MangoV4 = {
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "sequenceCheck",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"isMut": true,
|
||||
"isSigner": false,
|
||||
"relations": [
|
||||
"group",
|
||||
"owner"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "owner",
|
||||
"isMut": false,
|
||||
"isSigner": true
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "expectedSequenceNumber",
|
||||
"type": "u64"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stubOracleCreate",
|
||||
"accounts": [
|
||||
|
@ -22298,12 +22379,16 @@ export const IDL: MangoV4 = {
|
|||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "sequenceNumber",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
152
|
||||
144
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -24077,12 +24162,16 @@ export const IDL: MangoV4 = {
|
|||
"name": "lastCollateralFeeCharge",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "sequenceNumber",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
152
|
||||
144
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -25364,6 +25453,9 @@ export const IDL: MangoV4 = {
|
|||
},
|
||||
{
|
||||
"name": "TokenForceWithdraw"
|
||||
},
|
||||
{
|
||||
"name": "SequenceCheck"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -28706,6 +28798,16 @@ export const IDL: MangoV4 = {
|
|||
"code": 6069,
|
||||
"name": "TokenAssetLiquidationDisabled",
|
||||
"msg": "the asset does not allow liquidation"
|
||||
},
|
||||
{
|
||||
"code": 6070,
|
||||
"name": "BorrowsRequireHealthAccountBank",
|
||||
"msg": "for borrows the bank must be in the health account list"
|
||||
},
|
||||
{
|
||||
"code": 6071,
|
||||
"name": "InvalidSequenceNumber",
|
||||
"msg": "invalid sequence number"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue