From dc017cbfdf539cbfcd81244ee28f336a62a2feac Mon Sep 17 00:00:00 2001 From: SashaMIT Date: Wed, 5 Aug 2026 18:49:57 +0700 Subject: [PATCH 1/2] fix(native): bind native market writes to PerpMarket discriminator Native [0xFF;4] handlers write fixed offsets into accounts[0] without checking it is a PerpMarket. Owner/length alone is not enough. Reuse the same discriminator check as PerpMarketMap before mutably borrowing the market in mm-oracle and amm-spread native paths. Co-authored-by: Cursor --- programs/drift/src/instructions/admin.rs | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/programs/drift/src/instructions/admin.rs b/programs/drift/src/instructions/admin.rs index 2d6d7c4b17..36a3fa88d0 100644 --- a/programs/drift/src/instructions/admin.rs +++ b/programs/drift/src/instructions/admin.rs @@ -10,6 +10,7 @@ use std::mem::size_of; use crate::controller; use crate::controller::token::{close_vault, initialize_immutable_owner, initialize_token_account}; use crate::error::ErrorCode; +use anchor_lang::Discriminator; use crate::get_then_update_id; use crate::ids::{admin_hot_wallet, amm_spread_adjust_wallet, mm_oracle_crank_wallet}; use crate::instructions::constraints::*; @@ -4963,7 +4964,31 @@ pub fn handle_zero_mm_oracle_fields(ctx: Context) -> R Ok(()) } + +/// Native paths bypass Anchor account constraints — bind market writes to a real PerpMarket. +fn require_account_is_perp_market(account: &AccountInfo) { + assert!( + account.owner == &crate::ID, + "perp market must be owned by this program" + ); + assert!( + account.data_len() >= PerpMarket::SIZE, + "perp market account too short" + ); + let data = account.data.borrow(); + assert!( + data[..8] == PerpMarket::discriminator(), + "account must have PerpMarket discriminator" + ); +} + pub fn handle_update_mm_oracle_native(accounts: &[AccountInfo], data: &[u8]) -> Result<()> { + assert!( + accounts.len() >= 4, + "mm oracle native path requires market, signer, clock, state" + ); + require_account_is_perp_market(&accounts[0]); + // Verify this ix is allowed let state = &accounts[3].data.borrow(); assert!(state[982] & 1 > 0, "ix disabled by admin state"); @@ -5002,6 +5027,12 @@ pub fn handle_update_amm_spread_adjustment_native( accounts: &[AccountInfo], data: &[u8], ) -> Result<()> { + assert!( + !accounts.is_empty(), + "amm spread native path requires market account" + ); + require_account_is_perp_market(&accounts[0]); + let signer_account = &accounts[1]; #[cfg(not(feature = "anchor-test"))] assert!( From c3120e8d98e85d7e3bcee8623ba03162f5a612f9 Mon Sep 17 00:00:00 2001 From: SashaMIT Date: Wed, 5 Aug 2026 19:47:46 +0700 Subject: [PATCH 2/2] fix(native): require market+signer before indexing accounts[1] Avoid OOB panic on one-account amm-spread native calls; return assert with clear message instead. Co-authored-by: Cursor --- programs/drift/src/instructions/admin.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programs/drift/src/instructions/admin.rs b/programs/drift/src/instructions/admin.rs index 36a3fa88d0..bb203852fc 100644 --- a/programs/drift/src/instructions/admin.rs +++ b/programs/drift/src/instructions/admin.rs @@ -5028,8 +5028,8 @@ pub fn handle_update_amm_spread_adjustment_native( data: &[u8], ) -> Result<()> { assert!( - !accounts.is_empty(), - "amm spread native path requires market account" + accounts.len() >= 2, + "amm spread native path requires market and signer accounts" ); require_account_is_perp_market(&accounts[0]);