Update
This commit is contained in:
parent
7abb781e1b
commit
f37119aef5
|
@ -167,6 +167,19 @@ dependencies = [
|
|||
"syn 1.0.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-fuzzing"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
"rand 0.7.3",
|
||||
"safe-transmute",
|
||||
"solana-program",
|
||||
"spl-token 3.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-lang"
|
||||
version = "0.3.0"
|
||||
|
@ -179,16 +192,10 @@ dependencies = [
|
|||
"anchor-attribute-program",
|
||||
"anchor-attribute-state",
|
||||
"anchor-derive-accounts",
|
||||
"anchor-fuzzing",
|
||||
"base64 0.13.0",
|
||||
"bincode",
|
||||
"borsh",
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
"num-derive",
|
||||
"rand 0.7.3",
|
||||
"safe-transmute",
|
||||
"solana-program",
|
||||
"spl-token 3.1.0",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ members = [
|
|||
"lang",
|
||||
"lang/attribute/*",
|
||||
"lang/derive/*",
|
||||
"lang/fuzzing",
|
||||
"lang/syn",
|
||||
"spl",
|
||||
]
|
||||
|
|
|
@ -9,7 +9,7 @@ description = "Solana Sealevel eDSL"
|
|||
|
||||
[features]
|
||||
derive = []
|
||||
fuzzing = ["bincode", "lazy_static", "spl-token", "bumpalo", "rand", "safe-transmute", "num-derive"]
|
||||
fuzzing = ["anchor-fuzzing"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
|
@ -26,11 +26,5 @@ solana-program = "=1.5.15"
|
|||
thiserror = "1.0.20"
|
||||
base64 = "0.13.0"
|
||||
|
||||
# Fuzz deps.
|
||||
bincode = { version = "1.3.1", optional = true }
|
||||
bumpalo = { version = "3.4.0", features = ["collections", "boxed"], optional = true }
|
||||
lazy_static = { version = "1.4.0", optional = true }
|
||||
rand = { version = "0.7.3", optional = true }
|
||||
safe-transmute = { version = "0.11.0", optional = true }
|
||||
spl-token = { version = "3.0.1", features = ["no-entrypoint"], optional = true }
|
||||
num-derive = { version = "0.3.3", optional = true }
|
||||
# Fuzzing.
|
||||
anchor-fuzzing = { path = "./fuzzing", version = "0.3.0", optional = true }
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "anchor-fuzzing"
|
||||
version = "0.3.0"
|
||||
authors = ["armaniferrante <armaniferrante@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.3.1"
|
||||
bumpalo = { version = "3.4.0", features = ["collections", "boxed"] }
|
||||
lazy_static = "1.4.0"
|
||||
rand = "0.7.3"
|
||||
safe-transmute = "0.11.0"
|
||||
spl-token = { version = "3.0.1", features = ["no-entrypoint"] }
|
||||
solana-program = "=1.5.15"
|
|
@ -0,0 +1,242 @@
|
|||
//! The fuzz module provides utilities to facilitate fuzzing anchor programs.
|
||||
|
||||
#![allow(mutable_transmutes)]
|
||||
|
||||
use crate::spl_token_program::SplTokenProgram;
|
||||
use crate::system_program::SystemProgram;
|
||||
use bumpalo::Bump;
|
||||
use safe_transmute::to_bytes::transmute_to_bytes;
|
||||
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::program_pack::Pack;
|
||||
use solana_program::pubkey::Pubkey;
|
||||
use solana_program::rent::Rent;
|
||||
use solana_program::sysvar::{self, Sysvar};
|
||||
use spl_token::state::{Account as TokenAccount, Mint};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::mem::size_of;
|
||||
|
||||
mod spl_token_program;
|
||||
mod system_program;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref ENV: Host = Host::new();
|
||||
}
|
||||
|
||||
// Global host environment.
|
||||
pub fn env() -> &'static Host {
|
||||
&ENV
|
||||
}
|
||||
|
||||
// The host execution environment.
|
||||
#[derive(Debug)]
|
||||
pub struct Host {
|
||||
// All registered programs that can be invoked.
|
||||
programs: HashMap<Pubkey, Box<dyn Program>>,
|
||||
// The currently executing program.
|
||||
current_program: RefCell<Option<Pubkey>>,
|
||||
// Account storage.
|
||||
accounts: AccountStore,
|
||||
}
|
||||
|
||||
impl Host {
|
||||
pub fn new() -> Host {
|
||||
let mut env = Host {
|
||||
programs: HashMap::new(),
|
||||
current_program: RefCell::new(None),
|
||||
accounts: AccountStore::new(),
|
||||
};
|
||||
env.register(Box::new(SystemProgram));
|
||||
env.register(Box::new(SplTokenProgram));
|
||||
env
|
||||
}
|
||||
|
||||
pub fn accounts(&self) -> &AccountStore {
|
||||
&self.accounts
|
||||
}
|
||||
|
||||
// Registers the program on the environment so that it can be invoked via
|
||||
// CPI.
|
||||
pub fn register(&mut self, program: Box<dyn Program>) {
|
||||
self.programs.insert(program.id(), program);
|
||||
}
|
||||
|
||||
// Performs a cross program invocation.
|
||||
pub fn invoke(
|
||||
&self,
|
||||
ix: &Instruction,
|
||||
accounts: &[AccountInfo],
|
||||
seeds: &[&[&[u8]]],
|
||||
) -> ProgramResult {
|
||||
// If seeds were given, then calculate the expected PDA.
|
||||
let pda = {
|
||||
match *self.current_program.borrow() {
|
||||
None => None,
|
||||
Some(current_program) => match seeds.len() > 0 {
|
||||
false => None,
|
||||
true => {
|
||||
Some(Pubkey::create_program_address(seeds[0], ¤t_program).unwrap())
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// Set the current program.
|
||||
self.current_program.replace(Some(ix.program_id));
|
||||
|
||||
// Invoke the current program.
|
||||
let program = self.programs.get(&ix.program_id).unwrap();
|
||||
let account_infos: Vec<AccountInfo> = ix
|
||||
.accounts
|
||||
.iter()
|
||||
.map(|meta| {
|
||||
let mut acc_info = accounts
|
||||
.iter()
|
||||
.find(|info| *info.key == meta.pubkey)
|
||||
.unwrap()
|
||||
.clone();
|
||||
// If a PDA was given, mark it as signer.
|
||||
if let Some(pda) = pda {
|
||||
if acc_info.key == &pda {
|
||||
acc_info.is_signer = true;
|
||||
}
|
||||
}
|
||||
acc_info
|
||||
})
|
||||
.collect();
|
||||
program.entry(&ix.program_id, &account_infos, &ix.data)
|
||||
}
|
||||
}
|
||||
|
||||
// Not acutally Sync. Implemented so that we can use the Host as a
|
||||
// lazy static without using locks (which is inconvenient and can cause
|
||||
// deadlock). The Host, as presently constructed, should never be
|
||||
// used across threads.
|
||||
unsafe impl std::marker::Sync for Host {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AccountStore {
|
||||
// Storage bytes.
|
||||
storage: Bump,
|
||||
}
|
||||
|
||||
impl AccountStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
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),
|
||||
true,
|
||||
false,
|
||||
self.storage.alloc(lamports),
|
||||
&mut [],
|
||||
// Allocate on the bump allocator, so that the owner can be safely
|
||||
// mutated by the SystemProgram's `create_account` instruction.
|
||||
self.storage.alloc(system_program::ID),
|
||||
false,
|
||||
Epoch::default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_token_mint(&self) -> AccountInfo {
|
||||
let rent = Rent::default();
|
||||
let data = self.storage.alloc_slice_fill_copy(Mint::LEN, 0u8);
|
||||
let mut mint = Mint::default();
|
||||
mint.is_initialized = true;
|
||||
Mint::pack(mint, data).unwrap();
|
||||
AccountInfo::new(
|
||||
random_pubkey(&self.storage),
|
||||
false,
|
||||
true,
|
||||
self.storage.alloc(rent.minimum_balance(data.len())),
|
||||
data,
|
||||
&spl_token::ID,
|
||||
false,
|
||||
Epoch::default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_token_account<'a, 'b>(
|
||||
&self,
|
||||
mint_pubkey: &'a Pubkey,
|
||||
owner_pubkey: &'b Pubkey,
|
||||
balance: u64,
|
||||
) -> AccountInfo {
|
||||
let rent = Rent::default();
|
||||
let data = self.storage.alloc_slice_fill_copy(TokenAccount::LEN, 0u8);
|
||||
let mut account = TokenAccount::default();
|
||||
account.state = spl_token::state::AccountState::Initialized;
|
||||
account.mint = *mint_pubkey;
|
||||
account.owner = *owner_pubkey;
|
||||
account.amount = balance;
|
||||
TokenAccount::pack(account, data).unwrap();
|
||||
AccountInfo::new(
|
||||
random_pubkey(&self.storage),
|
||||
false,
|
||||
true,
|
||||
self.storage.alloc(rent.minimum_balance(data.len())),
|
||||
data,
|
||||
&spl_token::ID,
|
||||
false,
|
||||
Epoch::default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_program(&self) -> AccountInfo {
|
||||
AccountInfo::new(
|
||||
random_pubkey(&self.storage),
|
||||
false,
|
||||
false,
|
||||
self.storage.alloc(0),
|
||||
&mut [],
|
||||
&bpf_loader::ID,
|
||||
true,
|
||||
Epoch::default(),
|
||||
)
|
||||
}
|
||||
|
||||
fn new_rent_sysvar_account(&self) -> AccountInfo {
|
||||
let lamports = 100000;
|
||||
let data = self.storage.alloc_slice_fill_copy(size_of::<Rent>(), 0u8);
|
||||
let mut account_info = AccountInfo::new(
|
||||
&sysvar::rent::ID,
|
||||
false,
|
||||
false,
|
||||
self.storage.alloc(lamports),
|
||||
data,
|
||||
&sysvar::ID,
|
||||
false,
|
||||
Epoch::default(),
|
||||
);
|
||||
let rent = Rent::default();
|
||||
rent.to_account_info(&mut account_info).unwrap();
|
||||
account_info
|
||||
}
|
||||
}
|
||||
|
||||
fn random_pubkey(bump: &Bump) -> &Pubkey {
|
||||
bump.alloc(Pubkey::new(transmute_to_bytes(&rand::random::<[u64; 4]>())))
|
||||
}
|
||||
|
||||
// Program that can be executed in the environment.
|
||||
pub trait Program: Send + Sync + Debug {
|
||||
// The program's ID.
|
||||
fn id(&self) -> Pubkey;
|
||||
|
||||
// Entrypoint to start executing the program.
|
||||
fn entry(&self, program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8])
|
||||
-> ProgramResult;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
use crate::Program;
|
||||
use solana_program::account_info::AccountInfo;
|
||||
use solana_program::entrypoint::ProgramResult;
|
||||
use solana_program::pubkey::Pubkey;
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SplTokenProgram;
|
||||
|
||||
impl Program for SplTokenProgram {
|
||||
fn entry(
|
||||
&self,
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
ix_data: &[u8],
|
||||
) -> ProgramResult {
|
||||
spl_token::processor::Processor::process(program_id, accounts, ix_data)
|
||||
}
|
||||
fn id(&self) -> Pubkey {
|
||||
spl_token::ID
|
||||
}
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
use crate::{env, Host, Program};
|
||||
use solana_program::account_info::next_account_info;
|
||||
use solana_program::account_info::AccountInfo;
|
||||
use solana_program::entrypoint::ProgramResult;
|
||||
use solana_program::program_error::ProgramError;
|
||||
use solana_program::pubkey::Pubkey;
|
||||
use solana_program::system_instruction::{SystemError, SystemInstruction};
|
||||
use solana_program::system_program;
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub use system_program::ID;
|
||||
|
||||
// System program emulation to plug into the fuzzing host. Not all instructions
|
||||
// are implemented, but PRs are welcome.
|
||||
#[derive(Debug)]
|
||||
pub struct SystemProgram;
|
||||
|
||||
impl Program for SystemProgram {
|
||||
fn entry(
|
||||
&self,
|
||||
_program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
ix_data: &[u8],
|
||||
) -> ProgramResult {
|
||||
let ix: SystemInstruction =
|
||||
bincode::deserialize(ix_data).map_err(|_| ProgramError::InvalidInstructionData)?;
|
||||
|
||||
match ix {
|
||||
SystemInstruction::CreateAccount {
|
||||
lamports,
|
||||
space,
|
||||
owner,
|
||||
} => self.create_account(accounts, lamports, space, owner),
|
||||
SystemInstruction::Transfer { lamports } => self.transfer(accounts, lamports),
|
||||
SystemInstruction::CreateAccountWithSeed {
|
||||
base,
|
||||
seed,
|
||||
lamports,
|
||||
space,
|
||||
owner,
|
||||
} => self.create_account_with_seed(accounts, base, seed, lamports, space, owner),
|
||||
SystemInstruction::Assign { owner } => self.assign(accounts, owner),
|
||||
SystemInstruction::AdvanceNonceAccount => self.advance_nonce_account(accounts),
|
||||
SystemInstruction::WithdrawNonceAccount(lamports) => {
|
||||
self.withdraw_nonce_account(accounts, lamports)
|
||||
}
|
||||
SystemInstruction::InitializeNonceAccount(entity) => {
|
||||
self.initialize_nonce_account(accounts, entity)
|
||||
}
|
||||
SystemInstruction::AuthorizeNonceAccount(entity) => {
|
||||
self.authorize_nonce_account(accounts, entity)
|
||||
}
|
||||
SystemInstruction::Allocate { space } => self.allocate(accounts, space),
|
||||
SystemInstruction::AllocateWithSeed {
|
||||
base,
|
||||
seed,
|
||||
space,
|
||||
owner,
|
||||
} => self.allocate_with_seed(accounts, base, seed, space, owner),
|
||||
SystemInstruction::AssignWithSeed { base, seed, owner } => {
|
||||
self.assign_with_seed(accounts, base, seed, owner)
|
||||
}
|
||||
SystemInstruction::TransferWithSeed {
|
||||
lamports,
|
||||
from_seed,
|
||||
from_owner,
|
||||
} => self.transfer_with_seed(accounts, lamports, from_seed, from_owner),
|
||||
}
|
||||
}
|
||||
|
||||
fn id(&self) -> Pubkey {
|
||||
system_program::ID
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemProgram {
|
||||
fn create_account<'info>(
|
||||
&self,
|
||||
accounts: &[AccountInfo<'info>],
|
||||
lamports: u64,
|
||||
space: u64,
|
||||
owner: Pubkey,
|
||||
) -> ProgramResult {
|
||||
let acc_infos = &mut accounts.into_iter();
|
||||
|
||||
let from = next_account_info(acc_infos)?;
|
||||
let created = next_account_info(acc_infos)?;
|
||||
|
||||
if !from.is_signer {
|
||||
panic!("From not signer");
|
||||
}
|
||||
if !created.is_signer {
|
||||
panic!("Created not signer");
|
||||
}
|
||||
if !from.is_writable {
|
||||
panic!("From not writable");
|
||||
}
|
||||
if !created.is_writable {
|
||||
panic!("Created not writable");
|
||||
}
|
||||
|
||||
if **created.lamports.borrow() > 0 {
|
||||
panic!("{}", SystemError::AccountAlreadyInUse);
|
||||
}
|
||||
|
||||
// Safe because access to this method is single threaded.
|
||||
created.data.replace(
|
||||
env()
|
||||
.accounts
|
||||
.storage()
|
||||
.alloc_slice_fill_copy(space as usize, 0u8),
|
||||
);
|
||||
|
||||
let owner_og_ptr_const = created.owner as *const Pubkey;
|
||||
let owner_og_ptr_mut = owner_og_ptr_const as *mut Pubkey;
|
||||
|
||||
unsafe {
|
||||
std::ptr::write_unaligned(owner_og_ptr_mut, owner);
|
||||
}
|
||||
|
||||
**from.lamports.borrow_mut() -= lamports;
|
||||
**created.lamports.borrow_mut() += lamports;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transfer(&self, accounts: &[AccountInfo], lamports: u64) -> ProgramResult {
|
||||
let acc_infos = &mut accounts.into_iter();
|
||||
|
||||
let from = next_account_info(acc_infos)?;
|
||||
let to = next_account_info(acc_infos)?;
|
||||
|
||||
if !from.is_signer {
|
||||
panic!("From not signer");
|
||||
}
|
||||
if !from.is_writable {
|
||||
panic!("From not writable");
|
||||
}
|
||||
if !to.is_writable {
|
||||
panic!("To not writable");
|
||||
}
|
||||
|
||||
**from.lamports.borrow_mut() -= lamports;
|
||||
**to.lamports.borrow_mut() += lamports;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_account_with_seed(
|
||||
&self,
|
||||
accounts: &[AccountInfo],
|
||||
base: Pubkey,
|
||||
seed: String,
|
||||
lamports: u64,
|
||||
space: u64,
|
||||
owner: Pubkey,
|
||||
) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn assign(&self, _accounts: &[AccountInfo], owner: Pubkey) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn advance_nonce_account(&self, _accounts: &[AccountInfo]) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn withdraw_nonce_account(&self, _accounts: &[AccountInfo], _lamports: u64) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn initialize_nonce_account(
|
||||
&self,
|
||||
_accounts: &[AccountInfo],
|
||||
_entity: Pubkey,
|
||||
) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn authorize_nonce_account(&self, _accounts: &[AccountInfo], _entity: Pubkey) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn allocate(&self, _accounts: &[AccountInfo], _space: u64) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn allocate_with_seed(
|
||||
&self,
|
||||
_accounts: &[AccountInfo],
|
||||
_base: Pubkey,
|
||||
_seed: String,
|
||||
_space: u64,
|
||||
_owner: Pubkey,
|
||||
) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn assign_with_seed(
|
||||
&self,
|
||||
_accounts: &[AccountInfo],
|
||||
_base: Pubkey,
|
||||
_seed: String,
|
||||
_owner: Pubkey,
|
||||
) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn transfer_with_seed(
|
||||
&self,
|
||||
_accounts: &[AccountInfo],
|
||||
_lamports: u64,
|
||||
_from_seed: String,
|
||||
_from_owner: Pubkey,
|
||||
) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_program::instruction::{AccountMeta, Instruction};
|
||||
|
||||
#[test]
|
||||
fn system_program_creates_account() {
|
||||
let host = Host::new();
|
||||
|
||||
let owner = Pubkey::new_unique();
|
||||
let mut from = host.accounts.new_sol_account(1000);
|
||||
from.is_writable = true;
|
||||
from.is_signer = true;
|
||||
let mut created = host.accounts.new_sol_account(0);
|
||||
created.is_writable = true;
|
||||
created.is_signer = true;
|
||||
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);
|
||||
assert_eq!(*created.owner, Pubkey::new_from_array([0u8; 32]));
|
||||
host.invoke(&ix, accounts, &[]).unwrap();
|
||||
assert_eq!(*created.data.borrow_mut(), &[0u8; 1234]);
|
||||
assert_eq!(**created.lamports.borrow(), 999);
|
||||
assert_eq!(**from.lamports.borrow(), 1);
|
||||
assert_eq!(*created.owner, owner);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn system_program_transfer() {
|
||||
let host = Host::new();
|
||||
|
||||
let mut from = host.accounts.new_sol_account(1000);
|
||||
from.is_writable = true;
|
||||
from.is_signer = true;
|
||||
let mut to = host.accounts.new_sol_account(0);
|
||||
to.is_writable = true;
|
||||
|
||||
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);
|
||||
host.invoke(&ix, accounts, &[]).unwrap();
|
||||
assert_eq!(**from.lamports.borrow(), 1);
|
||||
assert_eq!(**to.lamports.borrow(), 999);
|
||||
}
|
||||
}
|
|
@ -1,519 +0,0 @@
|
|||
//! 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::{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, SystemInstruction};
|
||||
use solana_program::system_program;
|
||||
use solana_program::sysvar::{self, Sysvar};
|
||||
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::rc::Rc;
|
||||
use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard};
|
||||
use thiserror::Error;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref ENV: Arc<Environment> = Arc::new(Environment::new());
|
||||
}
|
||||
|
||||
// Global host environment.
|
||||
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: RefCell<Option<Pubkey>>,
|
||||
// Account storage.
|
||||
accounts: AccountStore,
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
pub fn new() -> Environment {
|
||||
let mut env = Environment {
|
||||
programs: HashMap::new(),
|
||||
current_program: RefCell::new(None),
|
||||
accounts: AccountStore::new(),
|
||||
};
|
||||
env.register(Box::new(SystemProgram));
|
||||
env.register(Box::new(SplToken));
|
||||
env
|
||||
}
|
||||
|
||||
pub fn accounts(&self) -> &AccountStore {
|
||||
&self.accounts
|
||||
}
|
||||
|
||||
// Registers the program on the environment so that it can be invoked via
|
||||
// CPI.
|
||||
pub fn register(&mut self, program: Box<dyn Program>) {
|
||||
self.programs.insert(program.id(), program);
|
||||
}
|
||||
|
||||
// Performs a cross program invocation.
|
||||
pub fn invoke(
|
||||
&self,
|
||||
ix: &Instruction,
|
||||
accounts: &[AccountInfo],
|
||||
seeds: &[&[&[u8]]],
|
||||
) -> ProgramResult {
|
||||
// If seeds were given, then calculate the expected PDA.
|
||||
let pda = {
|
||||
match *self.current_program.borrow() {
|
||||
None => None,
|
||||
Some(current_program) => match seeds.len() > 0 {
|
||||
false => None,
|
||||
true => {
|
||||
Some(Pubkey::create_program_address(seeds[0], ¤t_program).unwrap())
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// Set the current program.
|
||||
self.current_program.replace(Some(ix.program_id));
|
||||
|
||||
// Invoke the current program.
|
||||
let program = self.programs.get(&ix.program_id).unwrap();
|
||||
let account_infos: Vec<AccountInfo> = ix
|
||||
.accounts
|
||||
.iter()
|
||||
.map(|meta| {
|
||||
let mut acc_info = accounts
|
||||
.iter()
|
||||
.find(|info| *info.key == meta.pubkey)
|
||||
.unwrap()
|
||||
.clone();
|
||||
// If a PDA was given, market it as signer.
|
||||
if let Some(pda) = pda {
|
||||
if acc_info.key == &pda {
|
||||
acc_info.is_signer = true;
|
||||
}
|
||||
}
|
||||
acc_info
|
||||
})
|
||||
.collect();
|
||||
program.entry(&ix.program_id, &account_infos, &ix.data)
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
|
||||
impl AccountStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
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),
|
||||
true,
|
||||
false,
|
||||
self.storage.alloc(lamports),
|
||||
&mut [],
|
||||
&system_program::ID,
|
||||
false,
|
||||
Epoch::default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_token_mint(&self) -> AccountInfo {
|
||||
let rent = Rent::default();
|
||||
let data = self.storage.alloc_slice_fill_copy(Mint::LEN, 0u8);
|
||||
let mut mint = Mint::default();
|
||||
mint.is_initialized = true;
|
||||
Mint::pack(mint, data).unwrap();
|
||||
AccountInfo::new(
|
||||
random_pubkey(&self.storage),
|
||||
false,
|
||||
true,
|
||||
self.storage.alloc(rent.minimum_balance(data.len())),
|
||||
data,
|
||||
&spl_token::ID,
|
||||
false,
|
||||
Epoch::default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_token_account<'a, 'b>(
|
||||
&self,
|
||||
mint_pubkey: &'a Pubkey,
|
||||
owner_pubkey: &'b Pubkey,
|
||||
balance: u64,
|
||||
) -> AccountInfo {
|
||||
let rent = Rent::default();
|
||||
let data = self.storage.alloc_slice_fill_copy(TokenAccount::LEN, 0u8);
|
||||
let mut account = TokenAccount::default();
|
||||
account.state = spl_token::state::AccountState::Initialized;
|
||||
account.mint = *mint_pubkey;
|
||||
account.owner = *owner_pubkey;
|
||||
account.amount = balance;
|
||||
TokenAccount::pack(account, data).unwrap();
|
||||
AccountInfo::new(
|
||||
random_pubkey(&self.storage),
|
||||
false,
|
||||
true,
|
||||
self.storage.alloc(rent.minimum_balance(data.len())),
|
||||
data,
|
||||
&spl_token::ID,
|
||||
false,
|
||||
Epoch::default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_program(&self) -> AccountInfo {
|
||||
AccountInfo::new(
|
||||
random_pubkey(&self.storage),
|
||||
false,
|
||||
false,
|
||||
self.storage.alloc(0),
|
||||
&mut [],
|
||||
&bpf_loader::ID,
|
||||
true,
|
||||
Epoch::default(),
|
||||
)
|
||||
}
|
||||
|
||||
fn new_rent_sysvar_account(&self) -> AccountInfo {
|
||||
let lamports = 100000;
|
||||
let data = self.storage.alloc_slice_fill_copy(size_of::<Rent>(), 0u8);
|
||||
let mut account_info = AccountInfo::new(
|
||||
&sysvar::rent::ID,
|
||||
false,
|
||||
false,
|
||||
self.storage.alloc(lamports),
|
||||
data,
|
||||
&sysvar::ID,
|
||||
false,
|
||||
Epoch::default(),
|
||||
);
|
||||
let rent = Rent::default();
|
||||
rent.to_account_info(&mut account_info).unwrap();
|
||||
account_info
|
||||
}
|
||||
}
|
||||
|
||||
fn random_pubkey(bump: &Bump) -> &Pubkey {
|
||||
bump.alloc(Pubkey::new(transmute_to_bytes(&rand::random::<[u64; 4]>())))
|
||||
}
|
||||
|
||||
// Program that can be executed in the environment.
|
||||
pub trait Program: Send + Sync + Debug {
|
||||
// The program's ID.
|
||||
fn id(&self) -> Pubkey;
|
||||
|
||||
// Entrypoint to start executing the program.
|
||||
fn entry(&self, program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8])
|
||||
-> ProgramResult;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SplToken;
|
||||
|
||||
impl Program for SplToken {
|
||||
fn entry(
|
||||
&self,
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
ix_data: &[u8],
|
||||
) -> ProgramResult {
|
||||
spl_token::processor::Processor::process(program_id, accounts, ix_data)
|
||||
}
|
||||
fn id(&self) -> Pubkey {
|
||||
spl_token::ID
|
||||
}
|
||||
}
|
||||
|
||||
// Bare minimum implementation of the system program. Not all instructions are
|
||||
// implemented. PRs are welcome.
|
||||
#[derive(Debug)]
|
||||
struct SystemProgram;
|
||||
|
||||
impl SystemProgram {
|
||||
fn create_account<'info>(
|
||||
&self,
|
||||
accounts: &[AccountInfo<'info>],
|
||||
lamports: u64,
|
||||
space: u64,
|
||||
owner: Pubkey,
|
||||
) -> ProgramResult {
|
||||
let acc_infos = &mut accounts.into_iter();
|
||||
|
||||
let from = next_account_info(acc_infos)?;
|
||||
let mut created = next_account_info(acc_infos)?;
|
||||
|
||||
if **created.lamports.borrow() > 0 {
|
||||
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;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transfer(&self, accounts: &[AccountInfo], lamports: u64) -> ProgramResult {
|
||||
let acc_infos = &mut accounts.into_iter();
|
||||
|
||||
let from = next_account_info(acc_infos)?;
|
||||
let to = next_account_info(acc_infos)?;
|
||||
|
||||
**from.lamports.borrow_mut() -= lamports;
|
||||
**to.lamports.borrow_mut() += lamports;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_account_with_seed(
|
||||
&self,
|
||||
accounts: &[AccountInfo],
|
||||
base: Pubkey,
|
||||
seed: String,
|
||||
lamports: u64,
|
||||
space: u64,
|
||||
owner: Pubkey,
|
||||
) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn assign(&self, _accounts: &[AccountInfo], owner: Pubkey) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn advance_nonce_account(&self, _accounts: &[AccountInfo]) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn withdraw_nonce_account(&self, _accounts: &[AccountInfo], _lamports: u64) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn initialize_nonce_account(
|
||||
&self,
|
||||
_accounts: &[AccountInfo],
|
||||
_entity: Pubkey,
|
||||
) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn authorize_nonce_account(&self, _accounts: &[AccountInfo], _entity: Pubkey) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn allocate(&self, _accounts: &[AccountInfo], _space: u64) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn allocate_with_seed(
|
||||
&self,
|
||||
_accounts: &[AccountInfo],
|
||||
_base: Pubkey,
|
||||
_seed: String,
|
||||
_space: u64,
|
||||
_owner: Pubkey,
|
||||
) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn assign_with_seed(
|
||||
&self,
|
||||
_accounts: &[AccountInfo],
|
||||
_base: Pubkey,
|
||||
_seed: String,
|
||||
_owner: Pubkey,
|
||||
) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn transfer_with_seed(
|
||||
&self,
|
||||
_accounts: &[AccountInfo],
|
||||
_lamports: u64,
|
||||
_from_seed: String,
|
||||
_from_owner: Pubkey,
|
||||
) -> ProgramResult {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Program for SystemProgram {
|
||||
fn entry(
|
||||
&self,
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
ix_data: &[u8],
|
||||
) -> ProgramResult {
|
||||
let ix: SystemInstruction =
|
||||
bincode::deserialize(ix_data).map_err(|_| ProgramError::InvalidInstructionData)?;
|
||||
|
||||
match ix {
|
||||
SystemInstruction::CreateAccount {
|
||||
lamports,
|
||||
space,
|
||||
owner,
|
||||
} => self.create_account(accounts, lamports, space, owner),
|
||||
SystemInstruction::Transfer { lamports } => self.transfer(accounts, lamports),
|
||||
SystemInstruction::CreateAccountWithSeed {
|
||||
base,
|
||||
seed,
|
||||
lamports,
|
||||
space,
|
||||
owner,
|
||||
} => self.create_account_with_seed(accounts, base, seed, lamports, space, owner),
|
||||
SystemInstruction::Assign { owner } => self.assign(accounts, owner),
|
||||
SystemInstruction::AdvanceNonceAccount => self.advance_nonce_account(accounts),
|
||||
SystemInstruction::WithdrawNonceAccount(lamports) => {
|
||||
self.withdraw_nonce_account(accounts, lamports)
|
||||
}
|
||||
SystemInstruction::InitializeNonceAccount(entity) => {
|
||||
self.initialize_nonce_account(accounts, entity)
|
||||
}
|
||||
SystemInstruction::AuthorizeNonceAccount(entity) => {
|
||||
self.authorize_nonce_account(accounts, entity)
|
||||
}
|
||||
SystemInstruction::Allocate { space } => self.allocate(accounts, space),
|
||||
SystemInstruction::AllocateWithSeed {
|
||||
base,
|
||||
seed,
|
||||
space,
|
||||
owner,
|
||||
} => self.allocate_with_seed(accounts, base, seed, space, owner),
|
||||
SystemInstruction::AssignWithSeed { base, seed, owner } => {
|
||||
self.assign_with_seed(accounts, base, seed, owner)
|
||||
}
|
||||
SystemInstruction::TransferWithSeed {
|
||||
lamports,
|
||||
from_seed,
|
||||
from_owner,
|
||||
} => self.transfer_with_seed(accounts, lamports, from_seed, from_owner),
|
||||
}
|
||||
}
|
||||
fn id(&self) -> Pubkey {
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -21,8 +21,6 @@
|
|||
//!
|
||||
//! Presented here are the Rust primitives for building on Solana.
|
||||
|
||||
#![cfg_attr(any(fuzzing, feature = "fuzzing"), allow(mutable_transmutes))]
|
||||
|
||||
extern crate self as anchor_lang;
|
||||
|
||||
use solana_program::account_info::AccountInfo;
|
||||
|
@ -38,8 +36,6 @@ mod context;
|
|||
mod cpi_account;
|
||||
mod ctor;
|
||||
mod error;
|
||||
#[cfg(any(fuzzing, feature = "fuzzing"))]
|
||||
pub mod fuzzing;
|
||||
pub mod idl;
|
||||
mod program_account;
|
||||
mod state;
|
||||
|
@ -221,7 +217,7 @@ pub mod cpi {
|
|||
accounts: &[AccountInfo<'info>],
|
||||
seeds: &[&[&[u8]]],
|
||||
) -> ProgramResult {
|
||||
fuzzing::env().invoke(ix, accounts, seeds)
|
||||
anchor_fuzzing::env().invoke(ix, accounts, seeds)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue