In the context of Solana and Anchor, a "duplicate mutable accounts" issue typically arises when you are trying to pass the same mutable account more than once in a single instruction or method call. This issue can occur because Solana's programming model enforces strict constraints on the usage of accounts to ensure safety and prevent unintended side effects.
Here's a code example in Rust using the Anchor framework to illustrate the "duplicate mutable accounts" issue:
use anchor_lang::prelude::*;
#[program]
mod my_program {
use super::*;
pub fn update_account(ctx: Context<UpdateAccount>, amount: u64) -> ProgramResult {
let account1 = &mut ctx.accounts.account1;
account1.data += amount;
let account2 = &mut ctx.accounts.account2;
account2.data += amount;
Ok(())
}
}
#[derive(Accounts)]
pub struct UpdateAccount<'info> {
#[account(mut)]
account1: Box<Account<'info, AccData>>,
#[account(mut)]
account2: Box<Account<'info, AccData>>,
}
#[account]
pub struct AccData {
pub data: u32,
}
In this example, we have a simple Solana program defined using the Anchor framework. The update_account
method takes a mutable reference to the account
and increments its balance by a specified amount
.
In rust solana program you can add following line to mitigate the issue:
if ctx.accounts.account_one.key() == ctx.accounts.account_two.key() {
return Err(ProgramError::InvalidArgument)
}
You can resolve the above duplicate matching data issue by:
- Using rust like code solution in anchor solana
pub fn update_account(ctx: Context<UpdateAccount>, amount: u64) -> ProgramResult {
if ctx.accounts.account1.key() == ctx.accounts.account2.key() {
return Err(ProgramError::InvalidArgument)
}
let account1 = &mut ctx.accounts.account1;
account1.data += amount;
let account2 = &mut ctx.accounts.account2;
account2.data += amount;
Ok(())
}
- Using constraint in account data By adding the check inside the account with constraint it evaluates as true or false.
#[derive(Accounts)]
pub struct UpdateAccount<'info> {
#[account(mut, constraint = account1.key() != account2.key())]
account1: Box<Account<'info, AccData>>,
#[account(mut)]
account2: Box<Account<'info, AccData>>,
}
Let's try to understand by example
use anchor_lang::prelude::*;
#[program]
mod my_program {
use super::*;
pub fn initialize(ctx: Context<InitializeAccounts>) -> Result<()> {
let account1 = &mut ctx.accounts.account1;
account1.number = 0;
let account2 = &mut ctx.accounts.account2;
account2.number = 0;
Ok(())
}
pub fn who_won(ctx: Context<UpdateAccount>, number1: u64, number2: u64) -> Result<()> {
let account1 = &mut ctx.accounts.account1;
account1.data = number1;
let account2 = &mut ctx.accounts.account2;
account2.data = number2;
if account1.data > account2.data {
msg!("user 1 won");
account2.amount += 100;
} else {
msg!("user 2 won");
}
Ok(())
}
}
#[derive(Accounts)]
pub struct UpdateAccount<'info> {
#[account(mut)]
account1: Box<Account<'info, AccData>>,
#[account(mut)]
account2: Box<Account<'info, AccData>>,
}
#[account]
pub struct AccData {
pub data: u64,
pub amount: u64,
}
running the test on this insecure code:
duplicate mutable account
✔ Is Initialize (80ms)
✔ who won insecure function run (45ms)
Now let's add secure winning function
pub fn who_won(ctx: Context<UpdateAccount>, number1: u64, number2: u64) -> Result<()> {
let account1 = &mut ctx.accounts.account1;
account1.data = number1;
let account2 = &mut ctx.accounts.account2;
account2.data = number2;
if account1.data > account2.data {
msg!("user 1 won");
account2.amount += 100;
} else {
msg!("user 2 won");
}
Ok(())
}
#[derive(Accounts)]
pub struct UpdateAccount<'info> {
#[account(mut, constraint = account1.key() != account2.key())]
account1: Box<Account<'info, AccData>>,
#[account(mut)]
account2: Box<Account<'info, AccData>>,
}
Now again running the test on this secure code:
'Program Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS invoke [1]',
'Program log: Instruction: MyProgram',
'Program log: AnchorError caused by account: account1. Error Code: ConstraintRaw. Error Number: 2003. Error Message: A raw constraint was violated.',
'Program Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS consumed 5104 of 200000 compute units',
'Program Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS failed: custom program error: 0x7d3'