|
3 | 3 | use { |
4 | 4 | crate::discriminator::AccountDiscriminator, |
5 | 5 | pinocchio::{ |
6 | | - account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, |
| 6 | + account_info::AccountInfo, |
| 7 | + program_error::ProgramError, |
| 8 | + pubkey::{find_program_address, Pubkey}, |
| 9 | + ProgramResult, |
7 | 10 | }, |
8 | 11 | }; |
9 | 12 |
|
@@ -70,17 +73,52 @@ pub trait AccountWrite: AccountSerialize { |
70 | 73 | } |
71 | 74 |
|
72 | 75 | /// The AccountRead trait is used to handle deserializing an account from [`AccountInfo`] |
73 | | -pub trait AccountRead: AccountDeserialize + Sized { |
74 | | - const PROGRAM_ID: Pubkey; |
75 | | - /// Reads account data, validating the account discriminator and program owner |
| 76 | +pub trait AccountRead: AccountDeserialize + PdaDeriver + Sized { |
| 77 | + /// Reads account data, validating the following: |
| 78 | + /// * Account discriminator |
| 79 | + /// * Account program owner |
| 80 | + /// * Account address |
| 81 | + /// |
| 82 | + /// Account address validation is used to prevent exploits whereby an attacker may create an account in a different program |
| 83 | + /// and store data inside this account that would pass the discriminator and deserialization checks. Then assign the owner of that account |
| 84 | + /// to your program. |
| 85 | + /// |
| 86 | + /// For example if I create an account that stores a bump seed, and assign a value to that bump seed, this will still pass account discriminator |
| 87 | + /// and deserialization checks. However when the [`PdaDeriver::create_pda`] check is performed, the validation will fail. |
76 | 88 | fn account_read(account_info: &AccountInfo) -> Result<Self, ProgramError> { |
| 89 | + // validate the account owner |
77 | 90 | if !account_info.is_owned_by(&Self::PROGRAM_ID) { |
78 | | - return Err(ProgramError::IllegalOwner); |
| 91 | + return Err(ProgramError::InvalidAccountOwner); |
79 | 92 | } |
80 | | - Self::try_from_bytes(&account_info.try_borrow_data()?) |
| 93 | + |
| 94 | + // deserialize the account and validate the discriminator |
| 95 | + let account = Self::try_from_bytes(&account_info.try_borrow_data()?)?; |
| 96 | + |
| 97 | + // validate the account address |
| 98 | + let expected_pda = account.create_pda(); |
| 99 | + if !account_info.key().eq(&expected_pda) { |
| 100 | + return Err(ProgramError::InvalidSeeds); |
| 101 | + } |
| 102 | + |
| 103 | + Ok(account) |
81 | 104 | } |
82 | 105 | } |
83 | 106 |
|
| 107 | +/// The PdaDeriver trait is used to define how to derive a PDA for a specific account |
| 108 | +pub trait PdaDeriver: ProgramId { |
| 109 | + /// Derives a PDA from the provided seeds |
| 110 | + fn derive_pda(seeds: &[u8]) -> (Pubkey, u8) { |
| 111 | + find_program_address(&[seeds], &Self::PROGRAM_ID) |
| 112 | + } |
| 113 | + /// Creates a PDA from values in the account |
| 114 | + fn create_pda(&self) -> Pubkey; |
| 115 | +} |
| 116 | + |
| 117 | +/// The ProgramId trait is used to specify the expected owner of an account |
| 118 | +pub trait ProgramId { |
| 119 | + const PROGRAM_ID: Pubkey; |
| 120 | +} |
| 121 | + |
84 | 122 | #[cfg(test)] |
85 | 123 | mod test { |
86 | 124 | use { |
|
0 commit comments