Skip to content

Commit

Permalink
program: add a sequence check IX (#908)
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
farnyser authored Mar 7, 2024
1 parent 81f05b3 commit 4948356
Show file tree
Hide file tree
Showing 17 changed files with 386 additions and 11 deletions.
55 changes: 53 additions & 2 deletions mango_v4.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down Expand Up @@ -7942,12 +7972,16 @@
],
"type": "u64"
},
{
"name": "sequenceNumber",
"type": "u64"
},
{
"name": "reserved",
"type": {
"array": [
"u8",
152
144
]
}
},
Expand Down Expand Up @@ -9721,12 +9755,16 @@
"name": "lastCollateralFeeCharge",
"type": "u64"
},
{
"name": "sequenceNumber",
"type": "u64"
},
{
"name": "reserved",
"type": {
"array": [
"u8",
152
144
]
}
}
Expand Down Expand Up @@ -11008,6 +11046,9 @@
},
{
"name": "TokenForceWithdraw"
},
{
"name": "SequenceCheck"
}
]
}
Expand Down Expand Up @@ -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"
}
]
}
2 changes: 2 additions & 0 deletions programs/mango-v4/src/accounts_ix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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;
Expand Down
20 changes: 20 additions & 0 deletions programs/mango-v4/src/accounts_ix/sequence_check.rs
Original file line number Diff line number Diff line change
@@ -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>,
}
2 changes: 2 additions & 0 deletions programs/mango-v4/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions programs/mango-v4/src/instructions/ix_gate_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 2 additions & 0 deletions programs/mango-v4/src/instructions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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;
Expand Down
18 changes: 18 additions & 0 deletions programs/mango-v4/src/instructions/sequence_check.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
9 changes: 9 additions & 0 deletions programs/mango-v4/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions programs/mango-v4/src/state/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}

Expand Down
15 changes: 10 additions & 5 deletions programs/mango-v4/src/state/mango_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(),
Expand Down
102 changes: 102 additions & 0 deletions programs/mango-v4/tests/cases/test_basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
Loading

0 comments on commit 4948356

Please sign in to comment.