diff --git a/runtime/src/nonce_utils.rs b/runtime/src/nonce_utils.rs index 792d9eaa70..8f96c3dc5c 100644 --- a/runtime/src/nonce_utils.rs +++ b/runtime/src/nonce_utils.rs @@ -161,7 +161,7 @@ mod tests { assert_eq!(state, NonceState::Uninitialized); let recent_blockhashes = create_test_recent_blockhashes(0); nonce_account - .nonce(&recent_blockhashes, &Rent::default(), &signers) + .initialize(&recent_blockhashes, &Rent::default(), &signers) .unwrap(); assert!(verify_nonce(&nonce_account.account, &recent_blockhashes[0])); }); @@ -184,7 +184,7 @@ mod tests { assert_eq!(state, NonceState::Uninitialized); let recent_blockhashes = create_test_recent_blockhashes(0); nonce_account - .nonce(&recent_blockhashes, &Rent::default(), &signers) + .initialize(&recent_blockhashes, &Rent::default(), &signers) .unwrap(); assert!(!verify_nonce( &nonce_account.account, diff --git a/sdk/src/nonce_instruction.rs b/sdk/src/nonce_instruction.rs index d742f0490b..eadf777a76 100644 --- a/sdk/src/nonce_instruction.rs +++ b/sdk/src/nonce_instruction.rs @@ -56,6 +56,15 @@ pub enum NonceInstruction { /// The `u64` parameter is the lamports to withdraw, which must leave the /// account balance above the rent exempt reserve or at zero. Withdraw(u64), + + /// `Initialize` drives state of Uninitalized NonceAccount to Initialized, + /// setting the nonce value. + /// + /// Expects 3 Accounts: + /// 0 - A NonceAccount in the Uninitialized state + /// 1 - RecentBlockHashes sysvar + /// 2 - Rent sysvar + Initialize, } pub fn create_nonce_account( @@ -71,10 +80,22 @@ pub fn create_nonce_account( NonceState::size() as u64, &id(), ), - nonce(nonce_pubkey), + initialize(nonce_pubkey), ] } +pub fn initialize(nonce_pubkey: &Pubkey) -> Instruction { + Instruction::new( + id(), + &NonceInstruction::Initialize, + vec![ + AccountMeta::new(*nonce_pubkey, true), + AccountMeta::new_readonly(recent_blockhashes::id(), false), + AccountMeta::new_readonly(rent::id(), false), + ], + ) +} + pub fn nonce(nonce_pubkey: &Pubkey) -> Instruction { Instruction::new( id(), @@ -113,7 +134,6 @@ pub fn process_instruction( match limited_deserialize(data)? { NonceInstruction::Nonce => me.nonce( &RecentBlockhashes::from_keyed_account(next_keyed_account(keyed_accounts)?)?, - &Rent::from_keyed_account(next_keyed_account(keyed_accounts)?)?, &signers, ), NonceInstruction::Withdraw(lamports) => { @@ -126,13 +146,22 @@ pub fn process_instruction( &signers, ) } + NonceInstruction::Initialize => me.initialize( + &RecentBlockhashes::from_keyed_account(next_keyed_account(keyed_accounts)?)?, + &Rent::from_keyed_account(next_keyed_account(keyed_accounts)?)?, + &signers, + ), } } #[cfg(test)] mod tests { use super::*; - use crate::{account::Account, hash::Hash, nonce_state, system_program, sysvar}; + use crate::{ + account::Account, + hash::{hash, Hash}, + nonce_state, system_program, sysvar, + }; use bincode::serialize; fn process_instruction(instruction: &Instruction) -> Result<(), InstructionError> { @@ -232,50 +261,43 @@ mod tests { ); } - #[test] - fn test_process_nonce_ix_bad_rent_state_fail() { - assert_eq!( - super::process_instruction( - &Pubkey::default(), - &mut [ - KeyedAccount::new(&Pubkey::default(), true, &mut Account::default(),), - KeyedAccount::new( - &sysvar::recent_blockhashes::id(), - false, - &mut sysvar::recent_blockhashes::create_account(1), - ), - KeyedAccount::new(&sysvar::rent::id(), false, &mut Account::default(),), - ], - &serialize(&NonceInstruction::Nonce).unwrap(), - ), - Err(InstructionError::InvalidArgument), - ); - } - #[test] fn test_process_nonce_ix_ok() { + let mut nonce_acc = nonce_state::create_account(1_000_000); + super::process_instruction( + &Pubkey::default(), + &mut [ + KeyedAccount::new(&Pubkey::default(), true, &mut nonce_acc), + KeyedAccount::new( + &sysvar::recent_blockhashes::id(), + false, + &mut sysvar::recent_blockhashes::create_account_with_data( + 1, + vec![(0u64, &Hash::default()); 32].into_iter(), + ), + ), + KeyedAccount::new( + &sysvar::rent::id(), + false, + &mut sysvar::rent::create_account(1, &Rent::default()), + ), + ], + &serialize(&NonceInstruction::Initialize).unwrap(), + ) + .unwrap(); assert_eq!( super::process_instruction( &Pubkey::default(), &mut [ - KeyedAccount::new( - &Pubkey::default(), - true, - &mut nonce_state::create_account(1_000_000), - ), + KeyedAccount::new(&Pubkey::default(), true, &mut nonce_acc,), KeyedAccount::new( &sysvar::recent_blockhashes::id(), false, &mut sysvar::recent_blockhashes::create_account_with_data( 1, - vec![(0u64, &Hash::default()); 32].into_iter(), + vec![(0u64, &hash(&serialize(&0).unwrap())); 32].into_iter(), ), ), - KeyedAccount::new( - &sysvar::rent::id(), - false, - &mut sysvar::rent::create_account(1, &Rent::default()), - ), ], &serialize(&NonceInstruction::Nonce).unwrap(), ), @@ -339,27 +361,6 @@ mod tests { ); } - #[test] - fn test_process_withdraw_ix_bad_rent_state_fail() { - assert_eq!( - super::process_instruction( - &Pubkey::default(), - &mut [ - KeyedAccount::new(&Pubkey::default(), true, &mut Account::default(),), - KeyedAccount::new(&Pubkey::default(), false, &mut Account::default(),), - KeyedAccount::new( - &sysvar::recent_blockhashes::id(), - false, - &mut Account::default(), - ), - KeyedAccount::new(&sysvar::rent::id(), false, &mut Account::default(),), - ], - &serialize(&NonceInstruction::Withdraw(42)).unwrap(), - ), - Err(InstructionError::InvalidArgument), - ); - } - #[test] fn test_process_withdraw_ix_ok() { assert_eq!( diff --git a/sdk/src/nonce_state.rs b/sdk/src/nonce_state.rs index ccc229827f..2d4180842f 100644 --- a/sdk/src/nonce_state.rs +++ b/sdk/src/nonce_state.rs @@ -44,7 +44,6 @@ pub trait NonceAccount { fn nonce( &mut self, recent_blockhashes: &RecentBlockhashes, - rent: &Rent, signers: &HashSet, ) -> Result<(), InstructionError>; fn withdraw( @@ -55,13 +54,18 @@ pub trait NonceAccount { rent: &Rent, signers: &HashSet, ) -> Result<(), InstructionError>; + fn initialize( + &mut self, + recent_blockhashes: &RecentBlockhashes, + rent: &Rent, + signers: &HashSet, + ) -> Result<(), InstructionError>; } impl<'a> NonceAccount for KeyedAccount<'a> { fn nonce( &mut self, recent_blockhashes: &RecentBlockhashes, - rent: &Rent, signers: &HashSet, ) -> Result<(), InstructionError> { if recent_blockhashes.is_empty() { @@ -79,13 +83,7 @@ impl<'a> NonceAccount for KeyedAccount<'a> { } meta } - NonceState::Uninitialized => { - let min_balance = rent.minimum_balance(self.account.data.len()); - if self.account.lamports < min_balance { - return Err(InstructionError::InsufficientFunds); - } - Meta::new() - } + _ => return Err(NonceError::BadAccountState.into()), }; self.set_state(&NonceState::Initialized(meta, recent_blockhashes[0])) @@ -129,6 +127,34 @@ impl<'a> NonceAccount for KeyedAccount<'a> { Ok(()) } + + fn initialize( + &mut self, + recent_blockhashes: &RecentBlockhashes, + rent: &Rent, + signers: &HashSet, + ) -> Result<(), InstructionError> { + if recent_blockhashes.is_empty() { + return Err(NonceError::NoRecentBlockhashes.into()); + } + + if !signers.contains(self.unsigned_key()) { + return Err(InstructionError::MissingRequiredSignature); + } + + let meta = match self.state()? { + NonceState::Uninitialized => { + let min_balance = rent.minimum_balance(self.account.data.len()); + if self.account.lamports < min_balance { + return Err(InstructionError::InsufficientFunds); + } + Meta::new() + } + _ => return Err(NonceError::BadAccountState.into()), + }; + + self.set_state(&NonceState::Initialized(meta, recent_blockhashes[0])) + } } pub fn create_account(lamports: u64) -> Account { @@ -189,24 +215,20 @@ mod test { assert_eq!(state, NonceState::Uninitialized); let recent_blockhashes = create_test_recent_blockhashes(95); keyed_account - .nonce(&recent_blockhashes, &rent, &signers) + .initialize(&recent_blockhashes, &rent, &signers) .unwrap(); let state: NonceState = keyed_account.state().unwrap(); let stored = recent_blockhashes[0]; // First nonce instruction drives state from Uninitialized to Initialized assert_eq!(state, NonceState::Initialized(meta, stored)); let recent_blockhashes = create_test_recent_blockhashes(63); - keyed_account - .nonce(&recent_blockhashes, &rent, &signers) - .unwrap(); + keyed_account.nonce(&recent_blockhashes, &signers).unwrap(); let state: NonceState = keyed_account.state().unwrap(); let stored = recent_blockhashes[0]; // Second nonce instruction consumes and replaces stored nonce assert_eq!(state, NonceState::Initialized(meta, stored)); let recent_blockhashes = create_test_recent_blockhashes(31); - keyed_account - .nonce(&recent_blockhashes, &rent, &signers) - .unwrap(); + keyed_account.nonce(&recent_blockhashes, &signers).unwrap(); let state: NonceState = keyed_account.state().unwrap(); let stored = recent_blockhashes[0]; // Third nonce instruction for fun and profit @@ -237,21 +259,6 @@ mod test { }) } - #[test] - fn nonce_inx_uninitialized_account_not_signer_fail() { - let rent = Rent { - lamports_per_byte_year: 42, - ..Rent::default() - }; - let min_lamports = rent.minimum_balance(NonceState::size()); - with_test_keyed_account(min_lamports + 42, false, |nonce_account| { - let signers = HashSet::new(); - let recent_blockhashes = create_test_recent_blockhashes(0); - let result = nonce_account.nonce(&recent_blockhashes, &rent, &signers); - assert_eq!(result, Err(InstructionError::MissingRequiredSignature),); - }) - } - #[test] fn nonce_inx_initialized_account_not_signer_fail() { let rent = Rent { @@ -266,7 +273,7 @@ mod test { let recent_blockhashes = create_test_recent_blockhashes(31); let stored = recent_blockhashes[0]; nonce_account - .nonce(&recent_blockhashes, &rent, &signers) + .initialize(&recent_blockhashes, &rent, &signers) .unwrap(); let pubkey = nonce_account.account.owner.clone(); let mut nonce_account = KeyedAccount::new(&pubkey, false, nonce_account.account); @@ -274,7 +281,7 @@ mod test { assert_eq!(state, NonceState::Initialized(meta, stored)); let signers = HashSet::new(); let recent_blockhashes = create_test_recent_blockhashes(0); - let result = nonce_account.nonce(&recent_blockhashes, &rent, &signers); + let result = nonce_account.nonce(&recent_blockhashes, &signers); assert_eq!(result, Err(InstructionError::MissingRequiredSignature),); }) } @@ -289,8 +296,12 @@ mod test { with_test_keyed_account(min_lamports + 42, true, |keyed_account| { let mut signers = HashSet::new(); signers.insert(keyed_account.signer_key().unwrap().clone()); + let recent_blockhashes = create_test_recent_blockhashes(0); + keyed_account + .initialize(&recent_blockhashes, &rent, &signers) + .unwrap(); let recent_blockhashes = RecentBlockhashes::from_iter(vec![].into_iter()); - let result = keyed_account.nonce(&recent_blockhashes, &rent, &signers); + let result = keyed_account.nonce(&recent_blockhashes, &signers); assert_eq!(result, Err(NonceError::NoRecentBlockhashes.into())); }) } @@ -307,26 +318,26 @@ mod test { signers.insert(keyed_account.signer_key().unwrap().clone()); let recent_blockhashes = create_test_recent_blockhashes(63); keyed_account - .nonce(&recent_blockhashes, &rent, &signers) + .initialize(&recent_blockhashes, &rent, &signers) .unwrap(); - let result = keyed_account.nonce(&recent_blockhashes, &rent, &signers); + let result = keyed_account.nonce(&recent_blockhashes, &signers); assert_eq!(result, Err(NonceError::NotExpired.into())); }) } #[test] - fn nonce_inx_uninitialized_acc_insuff_funds_fail() { + fn nonce_inx_uninitialized_account_fail() { let rent = Rent { lamports_per_byte_year: 42, ..Rent::default() }; let min_lamports = rent.minimum_balance(NonceState::size()); - with_test_keyed_account(min_lamports - 42, true, |keyed_account| { + with_test_keyed_account(min_lamports + 42, true, |keyed_account| { let mut signers = HashSet::new(); signers.insert(keyed_account.signer_key().unwrap().clone()); let recent_blockhashes = create_test_recent_blockhashes(63); - let result = keyed_account.nonce(&recent_blockhashes, &rent, &signers); - assert_eq!(result, Err(InstructionError::InsufficientFunds)); + let result = keyed_account.nonce(&recent_blockhashes, &signers); + assert_eq!(result, Err(NonceError::BadAccountState.into())); }) } @@ -480,7 +491,7 @@ mod test { signers.insert(nonce_keyed.signer_key().unwrap().clone()); let recent_blockhashes = create_test_recent_blockhashes(31); nonce_keyed - .nonce(&recent_blockhashes, &rent, &signers) + .initialize(&recent_blockhashes, &rent, &signers) .unwrap(); let state: NonceState = nonce_keyed.state().unwrap(); let stored = recent_blockhashes[0]; @@ -536,7 +547,7 @@ mod test { signers.insert(nonce_keyed.signer_key().unwrap().clone()); let recent_blockhashes = create_test_recent_blockhashes(0); nonce_keyed - .nonce(&recent_blockhashes, &rent, &signers) + .initialize(&recent_blockhashes, &rent, &signers) .unwrap(); with_test_keyed_account(42, false, |mut to_keyed| { let mut signers = HashSet::new(); @@ -566,7 +577,7 @@ mod test { signers.insert(nonce_keyed.signer_key().unwrap().clone()); let recent_blockhashes = create_test_recent_blockhashes(95); nonce_keyed - .nonce(&recent_blockhashes, &rent, &signers) + .initialize(&recent_blockhashes, &rent, &signers) .unwrap(); with_test_keyed_account(42, false, |mut to_keyed| { let recent_blockhashes = create_test_recent_blockhashes(63); @@ -597,7 +608,7 @@ mod test { signers.insert(nonce_keyed.signer_key().unwrap().clone()); let recent_blockhashes = create_test_recent_blockhashes(95); nonce_keyed - .nonce(&recent_blockhashes, &rent, &signers) + .initialize(&recent_blockhashes, &rent, &signers) .unwrap(); with_test_keyed_account(42, false, |mut to_keyed| { let recent_blockhashes = create_test_recent_blockhashes(63); @@ -615,4 +626,92 @@ mod test { }) }) } + + #[test] + fn initialize_inx_ok() { + let rent = Rent { + lamports_per_byte_year: 42, + ..Rent::default() + }; + let min_lamports = rent.minimum_balance(NonceState::size()); + with_test_keyed_account(min_lamports + 42, true, |keyed_account| { + let state: NonceState = keyed_account.state().unwrap(); + assert_eq!(state, NonceState::Uninitialized); + let mut signers = HashSet::new(); + signers.insert(keyed_account.signer_key().unwrap().clone()); + let recent_blockhashes = create_test_recent_blockhashes(0); + let stored = recent_blockhashes[0]; + let result = keyed_account.initialize(&recent_blockhashes, &rent, &signers); + assert_eq!(result, Ok(())); + let state: NonceState = keyed_account.state().unwrap(); + assert_eq!(state, NonceState::Initialized(Meta::new(), stored)); + }) + } + + #[test] + fn initialize_inx_empty_recent_blockhashes_fail() { + let rent = Rent { + lamports_per_byte_year: 42, + ..Rent::default() + }; + let min_lamports = rent.minimum_balance(NonceState::size()); + with_test_keyed_account(min_lamports + 42, true, |keyed_account| { + let mut signers = HashSet::new(); + signers.insert(keyed_account.signer_key().unwrap().clone()); + let recent_blockhashes = RecentBlockhashes::from_iter(vec![].into_iter()); + let result = keyed_account.initialize(&recent_blockhashes, &rent, &signers); + assert_eq!(result, Err(NonceError::NoRecentBlockhashes.into())); + }) + } + + #[test] + fn initialize_inx_not_signer_fail() { + let rent = Rent { + lamports_per_byte_year: 42, + ..Rent::default() + }; + let min_lamports = rent.minimum_balance(NonceState::size()); + with_test_keyed_account(min_lamports + 42, false, |keyed_account| { + let signers = HashSet::new(); + let recent_blockhashes = create_test_recent_blockhashes(0); + let result = keyed_account.initialize(&recent_blockhashes, &rent, &signers); + assert_eq!(result, Err(InstructionError::MissingRequiredSignature)); + }) + } + + #[test] + fn initialize_inx_initialized_account_fail() { + let rent = Rent { + lamports_per_byte_year: 42, + ..Rent::default() + }; + let min_lamports = rent.minimum_balance(NonceState::size()); + with_test_keyed_account(min_lamports + 42, true, |keyed_account| { + let mut signers = HashSet::new(); + signers.insert(keyed_account.signer_key().unwrap().clone()); + let recent_blockhashes = create_test_recent_blockhashes(31); + keyed_account + .initialize(&recent_blockhashes, &rent, &signers) + .unwrap(); + let recent_blockhashes = create_test_recent_blockhashes(0); + let result = keyed_account.initialize(&recent_blockhashes, &rent, &signers); + assert_eq!(result, Err(NonceError::BadAccountState.into())); + }) + } + + #[test] + fn initialize_inx_uninitialized_acc_insuff_funds_fail() { + let rent = Rent { + lamports_per_byte_year: 42, + ..Rent::default() + }; + let min_lamports = rent.minimum_balance(NonceState::size()); + with_test_keyed_account(min_lamports - 42, true, |keyed_account| { + let mut signers = HashSet::new(); + signers.insert(keyed_account.signer_key().unwrap().clone()); + let recent_blockhashes = create_test_recent_blockhashes(63); + let result = keyed_account.initialize(&recent_blockhashes, &rent, &signers); + assert_eq!(result, Err(InstructionError::InsufficientFunds)); + }) + } }