Skip to content

Latest commit

 

History

History
171 lines (136 loc) · 4.81 KB

duplicate_mutable_accounts.md

File metadata and controls

171 lines (136 loc) · 4.81 KB

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:

  1. 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(())
    }
  1. 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'