Readonly Account Mutation Remediation
How to fix readonly account mutation logic errors.
Readonly Account Mutation Remediation
Overview
Detector Reference: Readonly Account Mutation
This guide explains how to fix logic errors where programs verify an account is readonly then attempt to modify it.
Recommended Fix
Determine the correct intent and align the check with the operation:
// If the account SHOULD be modified:
if !account.is_writable {
return Err(ProgramError::InvalidAccountData);
}
let mut data = account.data.borrow_mut();
data[8..16].copy_from_slice(&new_value.to_le_bytes());
// If the account should be READONLY:
// Simply remove the modification code
let data = account.data.borrow();
let value = u64::from_le_bytes(data[8..16].try_into()?);
Alternative Mitigations
- Separate instruction handlers: create distinct instruction handlers for read-only and read-write operations so the mutability constraint is structurally enforced.
- Anchor typed constraints: use
#[account(mut)]for writable accounts and undecoratedAccount<'info, T>for readonly, making the intent explicit at the type level.
Common Mistakes
- Inverted condition: writing
if account.is_writable { return Err(...) }when you meantif !account.is_writable { return Err(...) }. Double-check the boolean logic. - Checking the wrong account: verifying account A is readonly but modifying account B, which shares a similar variable name.
- Dead code: the readonly check may be leftover from a refactor. Remove unused checks to avoid confusion.