|
@@ -1,44 +1,47 @@
|
|
|
//! The fuzz modules provides utilities to facilitate fuzzing anchor programs.
|
|
|
|
|
|
use bumpalo::Bump;
|
|
|
+use num_derive::ToPrimitive;
|
|
|
use safe_transmute::to_bytes::transmute_to_bytes;
|
|
|
use solana_program::account_info::next_account_info;
|
|
|
use solana_program::account_info::AccountInfo;
|
|
|
use solana_program::bpf_loader;
|
|
|
use solana_program::clock::Epoch;
|
|
|
use solana_program::entrypoint::ProgramResult;
|
|
|
-use solana_program::instruction::Instruction;
|
|
|
+use solana_program::instruction::{AccountMeta, Instruction};
|
|
|
use solana_program::program_error::ProgramError;
|
|
|
use solana_program::program_pack::Pack;
|
|
|
use solana_program::pubkey::Pubkey;
|
|
|
use solana_program::rent::Rent;
|
|
|
-use solana_program::system_instruction::SystemError;
|
|
|
-use solana_program::system_instruction::SystemInstruction;
|
|
|
+use solana_program::system_instruction::{SystemError, SystemInstruction};
|
|
|
use solana_program::system_program;
|
|
|
use solana_program::sysvar::{self, Sysvar};
|
|
|
-use spl_token::state::Account as TokenAccount;
|
|
|
-use spl_token::state::Mint;
|
|
|
+use spl_token::state::{Account as TokenAccount, Mint};
|
|
|
+use std::cell::RefCell;
|
|
|
use std::collections::HashMap;
|
|
|
+use std::error::Error;
|
|
|
use std::fmt::Debug;
|
|
|
use std::mem::size_of;
|
|
|
-use std::sync::{Arc, Mutex, MutexGuard};
|
|
|
+use std::rc::Rc;
|
|
|
+use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard};
|
|
|
use thiserror::Error;
|
|
|
|
|
|
lazy_static::lazy_static! {
|
|
|
- static ref ENV: Arc<Mutex<Environment>> = Arc::new(Mutex::new(Environment::new()));
|
|
|
+ static ref ENV: Arc<Environment> = Arc::new(Environment::new());
|
|
|
}
|
|
|
|
|
|
// Global host environment.
|
|
|
-pub fn env() -> MutexGuard<'static, Environment> {
|
|
|
- ENV.lock().unwrap()
|
|
|
+pub fn env<'info>() -> Arc<Environment> {
|
|
|
+ ENV.clone()
|
|
|
}
|
|
|
|
|
|
// The host execution environment.
|
|
|
+#[derive(Debug)]
|
|
|
pub struct Environment {
|
|
|
// All registered programs that can be invoked.
|
|
|
programs: HashMap<Pubkey, Box<dyn Program>>,
|
|
|
// The currently executing program.
|
|
|
- current_program: Option<Pubkey>,
|
|
|
+ current_program: RefCell<Option<Pubkey>>,
|
|
|
// Account storage.
|
|
|
accounts: AccountStore,
|
|
|
}
|
|
@@ -47,7 +50,7 @@ impl Environment {
|
|
|
pub fn new() -> Environment {
|
|
|
let mut env = Environment {
|
|
|
programs: HashMap::new(),
|
|
|
- current_program: None,
|
|
|
+ current_program: RefCell::new(None),
|
|
|
accounts: AccountStore::new(),
|
|
|
};
|
|
|
env.register(Box::new(SystemProgram));
|
|
@@ -55,8 +58,8 @@ impl Environment {
|
|
|
env
|
|
|
}
|
|
|
|
|
|
- pub fn accounts_mut(&mut self) -> &mut AccountStore {
|
|
|
- &mut self.accounts
|
|
|
+ pub fn accounts(&self) -> &AccountStore {
|
|
|
+ &self.accounts
|
|
|
}
|
|
|
|
|
|
// Registers the program on the environment so that it can be invoked via
|
|
@@ -66,15 +69,15 @@ impl Environment {
|
|
|
}
|
|
|
|
|
|
// Performs a cross program invocation.
|
|
|
- pub fn invoke<'info>(
|
|
|
- &mut self,
|
|
|
+ pub fn invoke(
|
|
|
+ &self,
|
|
|
ix: &Instruction,
|
|
|
- accounts: &[AccountInfo<'info>],
|
|
|
+ accounts: &[AccountInfo],
|
|
|
seeds: &[&[&[u8]]],
|
|
|
) -> ProgramResult {
|
|
|
// If seeds were given, then calculate the expected PDA.
|
|
|
let pda = {
|
|
|
- match self.current_program {
|
|
|
+ match *self.current_program.borrow() {
|
|
|
None => None,
|
|
|
Some(current_program) => match seeds.len() > 0 {
|
|
|
false => None,
|
|
@@ -86,7 +89,7 @@ impl Environment {
|
|
|
};
|
|
|
|
|
|
// Set the current program.
|
|
|
- self.current_program = Some(ix.program_id);
|
|
|
+ self.current_program.replace(Some(ix.program_id));
|
|
|
|
|
|
// Invoke the current program.
|
|
|
let program = self.programs.get(&ix.program_id).unwrap();
|
|
@@ -112,6 +115,13 @@ impl Environment {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// Not acutally Sync. Implemented so that we can use the Environment as a
|
|
|
+// lazy static without using locks (which is inconvenient and can cause
|
|
|
+// deadlock). The Environment, as presently constructed, should never be
|
|
|
+// used across threads.
|
|
|
+unsafe impl std::marker::Sync for Environment {}
|
|
|
+
|
|
|
+#[derive(Debug)]
|
|
|
pub struct AccountStore {
|
|
|
// Storage bytes.
|
|
|
storage: Bump,
|
|
@@ -123,6 +133,11 @@ impl AccountStore {
|
|
|
storage: Bump::new(),
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ pub fn storage(&self) -> &Bump {
|
|
|
+ &self.storage
|
|
|
+ }
|
|
|
+
|
|
|
pub fn new_sol_account(&self, lamports: u64) -> AccountInfo {
|
|
|
AccountInfo::new(
|
|
|
random_pubkey(&self.storage),
|
|
@@ -249,9 +264,9 @@ impl Program for SplToken {
|
|
|
struct SystemProgram;
|
|
|
|
|
|
impl SystemProgram {
|
|
|
- fn create_account(
|
|
|
+ fn create_account<'info>(
|
|
|
&self,
|
|
|
- accounts: &[AccountInfo],
|
|
|
+ accounts: &[AccountInfo<'info>],
|
|
|
lamports: u64,
|
|
|
space: u64,
|
|
|
owner: Pubkey,
|
|
@@ -259,14 +274,24 @@ impl SystemProgram {
|
|
|
let acc_infos = &mut accounts.into_iter();
|
|
|
|
|
|
let from = next_account_info(acc_infos)?;
|
|
|
- let created = next_account_info(acc_infos)?;
|
|
|
+ let mut created = next_account_info(acc_infos)?;
|
|
|
|
|
|
if **created.lamports.borrow() > 0 {
|
|
|
- return Err(ProgramError::Custom(
|
|
|
- SystemError::AccountAlreadyInUse.to_u32(),
|
|
|
- ));
|
|
|
+ panic!("{}", SystemError::AccountAlreadyInUse);
|
|
|
}
|
|
|
|
|
|
+ let environment = env();
|
|
|
+ let data = environment
|
|
|
+ .accounts
|
|
|
+ .storage()
|
|
|
+ .alloc_slice_fill_copy(space as usize, 0u8);
|
|
|
+ // Safe because the lifetime is extended to match the other accounts
|
|
|
+ // also allocated in the bump allocator.
|
|
|
+ let data = unsafe { extend_lifetime(data) };
|
|
|
+
|
|
|
+ let created = unsafe { into_mut(created) };
|
|
|
+ created.data.replace(data);
|
|
|
+
|
|
|
**from.lamports.borrow_mut() -= lamports;
|
|
|
**created.lamports.borrow_mut() += lamports;
|
|
|
|
|
@@ -413,3 +438,82 @@ impl Program for SystemProgram {
|
|
|
system_program::ID
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+unsafe fn extend_lifetime<'a, 'info>(data: &'a mut [u8]) -> &'info mut [u8] {
|
|
|
+ std::mem::transmute::<&'a mut [u8], &'info mut [u8]>(data)
|
|
|
+}
|
|
|
+
|
|
|
+unsafe fn into_mut<'a, 'info>(acc: &'a AccountInfo<'info>) -> &'a mut AccountInfo<'info> {
|
|
|
+ std::mem::transmute::<&AccountInfo, &mut AccountInfo>(acc)
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(test)]
|
|
|
+mod tests {
|
|
|
+ use super::*;
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn system_program_creates_account() {
|
|
|
+ let mut environment = Environment::new();
|
|
|
+
|
|
|
+ let owner = Pubkey::new_unique();
|
|
|
+ let from = environment.accounts.new_sol_account(1000);
|
|
|
+ let created = environment.accounts.new_sol_account(0);
|
|
|
+
|
|
|
+ let ix = {
|
|
|
+ let data = bincode::serialize(&SystemInstruction::CreateAccount {
|
|
|
+ lamports: 999,
|
|
|
+ space: 1234,
|
|
|
+ owner,
|
|
|
+ })
|
|
|
+ .unwrap();
|
|
|
+
|
|
|
+ Instruction {
|
|
|
+ program_id: system_program::ID,
|
|
|
+ data,
|
|
|
+ accounts: vec![
|
|
|
+ AccountMeta::new(*from.key, true),
|
|
|
+ AccountMeta::new(*created.key, true),
|
|
|
+ ],
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ let accounts = &[from.clone(), created.clone()];
|
|
|
+
|
|
|
+ assert_eq!(*created.data.borrow_mut(), &[]);
|
|
|
+ assert_eq!(**from.lamports.borrow(), 1000);
|
|
|
+ environment.invoke(&ix, accounts, &[]).unwrap();
|
|
|
+ assert_eq!(*created.data.borrow_mut(), &[0u8; 1234]);
|
|
|
+ assert_eq!(**created.lamports.borrow(), 999);
|
|
|
+ assert_eq!(**from.lamports.borrow(), 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn system_program_transfer() {
|
|
|
+ let mut environment = Environment::new();
|
|
|
+
|
|
|
+ let owner = Pubkey::new_unique();
|
|
|
+ let from = environment.accounts.new_sol_account(1000);
|
|
|
+ let to = environment.accounts.new_sol_account(0);
|
|
|
+
|
|
|
+ let ix = {
|
|
|
+ let data = bincode::serialize(&SystemInstruction::Transfer { lamports: 999 }).unwrap();
|
|
|
+
|
|
|
+ Instruction {
|
|
|
+ program_id: system_program::ID,
|
|
|
+ data,
|
|
|
+ accounts: vec![
|
|
|
+ AccountMeta::new(*from.key, true),
|
|
|
+ AccountMeta::new(*to.key, true),
|
|
|
+ ],
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ let accounts = &[from.clone(), to.clone()];
|
|
|
+
|
|
|
+ assert_eq!(**from.lamports.borrow(), 1000);
|
|
|
+ assert_eq!(**to.lamports.borrow(), 0);
|
|
|
+ environment.invoke(&ix, accounts, &[]).unwrap();
|
|
|
+ assert_eq!(**from.lamports.borrow(), 1);
|
|
|
+ assert_eq!(**to.lamports.borrow(), 999);
|
|
|
+ }
|
|
|
+}
|