Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions lang/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,19 +305,32 @@ pub mod __internal {
} else {
// Dup branch: borrow_state != NOT_BORROWED means the SVM
// deduplicated this account slot.
if flags.is_ref_mut && !flags.allow_dup {
// Mutable dups without #[account(dup)] are rejected.
return Err(ProgramError::AccountBorrowFailed);
}

let idx = (actual_header & 0xFF) as usize;
if crate::utils::hint::unlikely(idx >= offset) {
return Err(ProgramError::InvalidAccountData);
}

// Read the original AccountView first — `raw` in the dup branch only
// points to an 8-byte dup entry, not a full RuntimeAccount.
let orig_view = core::ptr::read(base.add(idx));

// Optional None sentinels (address == program_id) represent absent
// accounts, not real duplicate borrows. Skip borrow tracking for
// them to avoid false positives when multiple Option fields are
// simultaneously None.
if flags.is_optional && crate::keys_eq(orig_view.address(), program_id) {
core::ptr::write(base.add(offset), orig_view);
let input = input.add(core::mem::size_of::<u64>());
return Ok(input);
}

if flags.is_ref_mut && !flags.allow_dup {
// Mutable dups without #[account(dup)] are rejected.
return Err(ProgramError::AccountBorrowFailed);
}

if flags.is_ref_mut {
// Mutable dup: claim exclusive access.
let orig_view = core::ptr::read(base.add(idx));
let bs_ptr = orig_view.account_ptr() as *mut u8;
let bs = *bs_ptr;
if crate::utils::hint::unlikely(bs != NOT_BORROWED) {
Expand All @@ -326,7 +339,6 @@ pub mod __internal {
*bs_ptr = 0;
} else {
// Immutable dup: consume one immutable borrow slot.
let orig_view = core::ptr::read(base.add(idx));
let bs_ptr = orig_view.account_ptr() as *mut u8;
let bs = *bs_ptr;
if crate::utils::hint::unlikely(bs <= 1) {
Expand All @@ -335,7 +347,7 @@ pub mod __internal {
*bs_ptr = bs - 1;
}

core::ptr::write(base.add(offset), core::ptr::read(base.add(idx)));
core::ptr::write(base.add(offset), orig_view);
let input = input.add(core::mem::size_of::<u64>());
Ok(input)
}
Expand Down
3 changes: 3 additions & 0 deletions tests/programs/test-misc/src/instructions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,6 @@ pub use dynamic_view_mut_missing_field::*;

pub mod cpi_mut_readback;
pub use cpi_mut_readback::*;

pub mod optional_mut_accounts;
pub use optional_mut_accounts::*;
26 changes: 26 additions & 0 deletions tests/programs/test-misc/src/instructions/optional_mut_accounts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use {crate::state::SimpleAccount, quasar_derive::Accounts, quasar_lang::prelude::*};

/// Accounts struct with multiple `mut` Option fields that can all be `None`.
/// Used to verify that the sentinel-first dup path skips borrow tracking for
/// program-ID sentinels.
#[derive(Accounts)]
pub struct OptionalMutAccounts {
#[account(mut)]
pub authority: Signer,

#[account(mut)]
pub first: Option<Account<SimpleAccount>>,

#[account(mut)]
pub second: Option<Account<SimpleAccount>>,

#[account(mut)]
pub third: Option<Account<SimpleAccount>>,
}

impl OptionalMutAccounts {
#[inline(always)]
pub fn handler(&self) -> Result<(), ProgramError> {
Ok(())
}
}
5 changes: 5 additions & 0 deletions tests/programs/test-misc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,4 +338,9 @@ mod quasar_test_misc {
let _ = (maybe_name, maybe_addrs);
ctx.accounts.handler()
}

#[instruction(discriminator = 62)]
pub fn optional_mut_accounts(ctx: Ctx<OptionalMutAccounts>) -> Result<(), ProgramError> {
ctx.accounts.handler()
}
}
29 changes: 29 additions & 0 deletions tests/suite/src/optional_accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,32 @@ fn some_wrong_discriminator() {
assert!(result.is_err(), "wrong disc on present optional");
result.assert_error(ProgramError::InvalidAccountData);
}

/// Multiple `#[account(mut)] Option<T>` fields can all be `None` (program-ID
/// sentinel) without triggering the duplicate-account borrow checker.
#[test]
fn multiple_mut_optional_none_sentinels() {
let mut svm = svm_misc();
let authority = Pubkey::new_unique();
let sentinel = quasar_test_misc::ID;

let ix: Instruction = OptionalMutAccountsInstruction {
authority,
first: sentinel,
second: sentinel,
third: sentinel,
}
.into();

let result = svm.process_instruction(
&ix,
&[
signer_account(authority),
],
);
assert!(
result.is_ok(),
"all mut optionals as None sentinel should parse: {:?}",
result.raw_result
);
}
Loading