This commit is contained in:
armaniferrante 2021-03-31 15:12:35 -07:00
parent f37119aef5
commit 1c741fb967
No known key found for this signature in database
GPG Key ID: 58BEF301E91F7828
5 changed files with 68 additions and 45 deletions

View File

@ -9,7 +9,6 @@ description = "Solana Sealevel eDSL"
[features] [features]
derive = [] derive = []
fuzzing = ["anchor-fuzzing"]
default = [] default = []
[dependencies] [dependencies]
@ -26,5 +25,5 @@ solana-program = "=1.5.15"
thiserror = "1.0.20" thiserror = "1.0.20"
base64 = "0.13.0" base64 = "0.13.0"
# Fuzzing. [target.'cfg(fuzzing)'.dependencies]
anchor-fuzzing = { path = "./fuzzing", version = "0.3.0", optional = true } anchor-fuzzing = { path = "./fuzzing", version = "0.3.0" }

View File

@ -1,6 +1,6 @@
//! The fuzz module provides utilities to facilitate fuzzing anchor programs. //! Utilities to facilitate fuzzing anchor programs.
#![allow(mutable_transmutes)] #![feature(option_insert)]
use crate::spl_token_program::SplTokenProgram; use crate::spl_token_program::SplTokenProgram;
use crate::system_program::SystemProgram; use crate::system_program::SystemProgram;
@ -16,7 +16,7 @@ use solana_program::pubkey::Pubkey;
use solana_program::rent::Rent; use solana_program::rent::Rent;
use solana_program::sysvar::{self, Sysvar}; use solana_program::sysvar::{self, Sysvar};
use spl_token::state::{Account as TokenAccount, Mint}; use spl_token::state::{Account as TokenAccount, Mint};
use std::cell::RefCell; use std::cell::{RefCell, UnsafeCell};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::mem::size_of; use std::mem::size_of;
@ -28,16 +28,26 @@ lazy_static::lazy_static! {
static ref ENV: Host = Host::new(); static ref ENV: Host = Host::new();
} }
// Global host environment. /// Returns a fresh host environment. Should be called once at the beginning
/// of a fuzzing iteration.
pub fn env_reset() -> &'static Host {
ENV.programs.replace(HashMap::new());
ENV.current_program.replace(None);
ENV.accounts().reset();
&ENV
}
/// Returns the global, single threaded host environment shared across a single
/// fuzzing iteration.
pub fn env() -> &'static Host { pub fn env() -> &'static Host {
&ENV &ENV
} }
// The host execution environment. /// Host execution environment emulating the Solana runtime.
#[derive(Debug)] #[derive(Debug)]
pub struct Host { pub struct Host {
// All registered programs that can be invoked. // All registered programs that can be invoked.
programs: HashMap<Pubkey, Box<dyn Program>>, programs: RefCell<HashMap<Pubkey, Box<dyn Program>>>,
// The currently executing program. // The currently executing program.
current_program: RefCell<Option<Pubkey>>, current_program: RefCell<Option<Pubkey>>,
// Account storage. // Account storage.
@ -46,14 +56,14 @@ pub struct Host {
impl Host { impl Host {
pub fn new() -> Host { pub fn new() -> Host {
let mut env = Host { let h = Host {
programs: HashMap::new(), programs: RefCell::new(HashMap::new()),
current_program: RefCell::new(None), current_program: RefCell::new(None),
accounts: AccountStore::new(), accounts: AccountStore::new(),
}; };
env.register(Box::new(SystemProgram)); h.register(Box::new(SystemProgram));
env.register(Box::new(SplTokenProgram)); h.register(Box::new(SplTokenProgram));
env h
} }
pub fn accounts(&self) -> &AccountStore { pub fn accounts(&self) -> &AccountStore {
@ -62,8 +72,9 @@ impl Host {
// Registers the program on the environment so that it can be invoked via // Registers the program on the environment so that it can be invoked via
// CPI. // CPI.
pub fn register(&mut self, program: Box<dyn Program>) { pub fn register(&self, program: Box<dyn Program>) {
self.programs.insert(program.id(), program); let mut programs = self.programs.borrow_mut();
programs.insert(program.id(), program);
} }
// Performs a cross program invocation. // Performs a cross program invocation.
@ -90,7 +101,8 @@ impl Host {
self.current_program.replace(Some(ix.program_id)); self.current_program.replace(Some(ix.program_id));
// Invoke the current program. // Invoke the current program.
let program = self.programs.get(&ix.program_id).unwrap(); let programs_map = self.programs.borrow();
let program = programs_map.get(&ix.program_id).unwrap();
let account_infos: Vec<AccountInfo> = ix let account_infos: Vec<AccountInfo> = ix
.accounts .accounts
.iter() .iter()
@ -117,35 +129,42 @@ impl Host {
// lazy static without using locks (which is inconvenient and can cause // lazy static without using locks (which is inconvenient and can cause
// deadlock). The Host, as presently constructed, should never be // deadlock). The Host, as presently constructed, should never be
// used across threads. // used across threads.
unsafe impl std::marker::Sync for Host {} unsafe impl<'storage> std::marker::Sync for Host {}
#[derive(Debug)] #[derive(Debug)]
pub struct AccountStore { pub struct AccountStore {
// Storage bytes. bump: UnsafeCell<Bump>,
storage: Bump,
} }
impl AccountStore { impl AccountStore {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
storage: Bump::new(), bump: UnsafeCell::new(Bump::new()),
} }
} }
pub fn reset(&self) {
self.storage_mut().reset();
}
pub fn storage(&self) -> &Bump { pub fn storage(&self) -> &Bump {
&self.storage unsafe { &mut *self.bump.get() }
}
pub fn storage_mut(&self) -> &mut Bump {
unsafe { &mut *self.bump.get() }
} }
pub fn new_sol_account(&self, lamports: u64) -> AccountInfo { pub fn new_sol_account(&self, lamports: u64) -> AccountInfo {
AccountInfo::new( AccountInfo::new(
random_pubkey(&self.storage), random_pubkey(self.storage()),
true, true,
false, false,
self.storage.alloc(lamports), self.storage().alloc(lamports),
&mut [], &mut [],
// Allocate on the bump allocator, so that the owner can be safely // Allocate on the bump allocator, so that the owner can be safely
// mutated by the SystemProgram's `create_account` instruction. // mutated by the SystemProgram's `create_account` instruction.
self.storage.alloc(system_program::ID), self.storage().alloc(system_program::ID),
false, false,
Epoch::default(), Epoch::default(),
) )
@ -153,15 +172,15 @@ impl AccountStore {
pub fn new_token_mint(&self) -> AccountInfo { pub fn new_token_mint(&self) -> AccountInfo {
let rent = Rent::default(); let rent = Rent::default();
let data = self.storage.alloc_slice_fill_copy(Mint::LEN, 0u8); let data = self.storage().alloc_slice_fill_copy(Mint::LEN, 0u8);
let mut mint = Mint::default(); let mut mint = Mint::default();
mint.is_initialized = true; mint.is_initialized = true;
Mint::pack(mint, data).unwrap(); Mint::pack(mint, data).unwrap();
AccountInfo::new( AccountInfo::new(
random_pubkey(&self.storage), random_pubkey(self.storage()),
false, false,
true, true,
self.storage.alloc(rent.minimum_balance(data.len())), self.storage().alloc(rent.minimum_balance(data.len())),
data, data,
&spl_token::ID, &spl_token::ID,
false, false,
@ -169,14 +188,14 @@ impl AccountStore {
) )
} }
pub fn new_token_account<'a, 'b>( pub fn new_token_account(
&self, &self,
mint_pubkey: &'a Pubkey, mint_pubkey: &Pubkey,
owner_pubkey: &'b Pubkey, owner_pubkey: &Pubkey,
balance: u64, balance: u64,
) -> AccountInfo { ) -> AccountInfo {
let rent = Rent::default(); let rent = Rent::default();
let data = self.storage.alloc_slice_fill_copy(TokenAccount::LEN, 0u8); let data = self.storage().alloc_slice_fill_copy(TokenAccount::LEN, 0u8);
let mut account = TokenAccount::default(); let mut account = TokenAccount::default();
account.state = spl_token::state::AccountState::Initialized; account.state = spl_token::state::AccountState::Initialized;
account.mint = *mint_pubkey; account.mint = *mint_pubkey;
@ -184,10 +203,10 @@ impl AccountStore {
account.amount = balance; account.amount = balance;
TokenAccount::pack(account, data).unwrap(); TokenAccount::pack(account, data).unwrap();
AccountInfo::new( AccountInfo::new(
random_pubkey(&self.storage), random_pubkey(self.storage()),
false, false,
true, true,
self.storage.alloc(rent.minimum_balance(data.len())), self.storage().alloc(rent.minimum_balance(data.len())),
data, data,
&spl_token::ID, &spl_token::ID,
false, false,
@ -197,10 +216,10 @@ impl AccountStore {
pub fn new_program(&self) -> AccountInfo { pub fn new_program(&self) -> AccountInfo {
AccountInfo::new( AccountInfo::new(
random_pubkey(&self.storage), random_pubkey(self.storage()),
false, false,
false, false,
self.storage.alloc(0), self.storage().alloc(0),
&mut [], &mut [],
&bpf_loader::ID, &bpf_loader::ID,
true, true,
@ -210,12 +229,12 @@ impl AccountStore {
fn new_rent_sysvar_account(&self) -> AccountInfo { fn new_rent_sysvar_account(&self) -> AccountInfo {
let lamports = 100000; let lamports = 100000;
let data = self.storage.alloc_slice_fill_copy(size_of::<Rent>(), 0u8); let data = self.storage().alloc_slice_fill_copy(size_of::<Rent>(), 0u8);
let mut account_info = AccountInfo::new( let mut account_info = AccountInfo::new(
&sysvar::rent::ID, &sysvar::rent::ID,
false, false,
false, false,
self.storage.alloc(lamports), self.storage().alloc(lamports),
data, data,
&sysvar::ID, &sysvar::ID,
false, false,
@ -227,8 +246,8 @@ impl AccountStore {
} }
} }
fn random_pubkey(bump: &Bump) -> &Pubkey { fn random_pubkey(storage: &Bump) -> &Pubkey {
bump.alloc(Pubkey::new(transmute_to_bytes(&rand::random::<[u64; 4]>()))) storage.alloc(Pubkey::new(transmute_to_bytes(&rand::random::<[u64; 4]>())))
} }
// Program that can be executed in the environment. // Program that can be executed in the environment.

View File

@ -228,12 +228,14 @@ mod tests {
let host = Host::new(); let host = Host::new();
let owner = Pubkey::new_unique(); let owner = Pubkey::new_unique();
let mut from = host.accounts.new_sol_account(1000); let mut from = host.accounts().new_sol_account(1000);
from.is_writable = true; from.is_writable = true;
from.is_signer = true; from.is_signer = true;
let mut created = host.accounts.new_sol_account(0);
let mut created = host.accounts().new_sol_account(0);
created.is_writable = true; created.is_writable = true;
created.is_signer = true; created.is_signer = true;
let ix = { let ix = {
let data = bincode::serialize(&SystemInstruction::CreateAccount { let data = bincode::serialize(&SystemInstruction::CreateAccount {
lamports: 999, lamports: 999,
@ -268,10 +270,11 @@ mod tests {
fn system_program_transfer() { fn system_program_transfer() {
let host = Host::new(); let host = Host::new();
let mut from = host.accounts.new_sol_account(1000); let mut from = host.accounts().new_sol_account(1000);
from.is_writable = true; from.is_writable = true;
from.is_signer = true; from.is_signer = true;
let mut to = host.accounts.new_sol_account(0);
let mut to = host.accounts().new_sol_account(0);
to.is_writable = true; to.is_writable = true;
let ix = { let ix = {

View File

@ -61,6 +61,8 @@ pub use anchor_attribute_interface::interface;
pub use anchor_attribute_program::program; pub use anchor_attribute_program::program;
pub use anchor_attribute_state::state; pub use anchor_attribute_state::state;
pub use anchor_derive_accounts::Accounts; pub use anchor_derive_accounts::Accounts;
#[cfg(fuzzing)]
pub use anchor_fuzzing;
/// Borsh is the default serialization format for instructions and accounts. /// Borsh is the default serialization format for instructions and accounts.
pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
pub use error::Error; pub use error::Error;

View File

@ -57,7 +57,7 @@ pub fn generate(program: Program) -> proc_macro2::TokenStream {
} }
} }
impl anchor_lang::fuzzing::Program for Program { impl anchor_lang::anchor_fuzzing::Program for Program {
fn entry( fn entry(
&self, &self,
program_id: &Pubkey, program_id: &Pubkey,