1

First of all, I'm new to Solana development (came from an Ethereum background), so sorry if my question is too basic. I'm following the SolDev course, I'm on course state management. I have a confusion about section Lab/5. DerivePDA. It is said:

Let’s independently derive the PDA we expect the user to have passed in

As far as I understood, the user provides the PDA for the account to be deployed at, in accounts input, and we try to recalculate the PDA to verify its correctness. However, I see no validation in the code provided in the course.

pub fn add_movie_review(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    title: String,
    rating: u8,
    description: String
) -> ProgramResult {

    // Get Account iterator
    let account_info_iter = &mut accounts.iter();

    // Get accounts
    let initializer = next_account_info(account_info_iter)?;
    let pda_account = next_account_info(account_info_iter)?;
    let system_program = next_account_info(account_info_iter)?;

    // Derive PDA
    let (pda, bump_seed) = Pubkey::find_program_address(&[initializer.key.as_ref(), title.as_bytes().as_ref(),], program_id);

     // QUESTION: why not check the calculated `pda` against `pda_account`??
    
    // Calculate account size required
    let account_len: usize = 1 + 1 + (4 + title.len()) + (4 + description.len());

    // Calculate rent required
    let rent = Rent::get()?;
    let rent_lamports = rent.minimum_balance(account_len);

    // Create the account
    invoke_signed(
        &system_instruction::create_account(
            initializer.key,
            pda_account.key,
            rent_lamports,
            account_len.try_into().unwrap(),
            program_id,
        ),
        &[initializer.clone(), pda_account.clone(), system_program.clone()],
        &[&[initializer.key.as_ref(), title.as_bytes().as_ref(), &[bump_seed]]],
    )?;

    msg!("PDA created: {}", pda);

    msg!("unpacking state account");
    let mut account_data = try_from_slice_unchecked::<MovieAccountState>(&pda_account.data.borrow()).unwrap();
    msg!("borrowed account data");

    account_data.title = title;
    account_data.rating = rating;
    account_data.description = description;
    account_data.is_initialized = true;

    msg!("serializing account");
    account_data.serialize(&mut &mut pda_account.data.borrow_mut()[..])?;
    msg!("state account serialized");

    Ok(())
}

If no check is required, then why do we try to re-calculate the PDA? I can see that the code uses the provided pda_account directly to create the new account here:

// Create the account
    invoke_signed(
        &system_instruction::create_account(
            initializer.key,
            pda_account.key,
            rent_lamports,
            account_len.try_into().unwrap(),
            program_id,
        ),
        &[initializer.clone(), pda_account.clone(), system_program.clone()],
        &[&[initializer.key.as_ref(), title.as_bytes().as_ref(), &[bump_seed]]],
    )?;

1 Answer 1

0

Since you're passing in the seeds to invoke_signed, the runtime will check to make sure that one of the accounts can be promoted to a signer using the provided seeds.

The runtime will perform create_program_address with those seeds, and then match it against all of the accounts provided to see which can become a signer.

There's actually a way to exploit the program do the wrong thing since it isn't checking the address, but I'll leave that as an exercise to the reader :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.