Skip to content

Commit

Permalink
✨ Allow direct accounts manipulation
Browse files Browse the repository at this point in the history
  • Loading branch information
Ikrk committed Mar 12, 2024
1 parent 36ed252 commit 34e7765
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ incremented upon a breaking change and the patch version will be incremented for

## [Unreleased]
### Added
- feat/allow direct accounts manipulation and storage ([#142](https://github.com/Ackee-Blockchain/trdelnik/pull/142))
- feat/support of non-corresponding instruction and context names ([#130](https://github.com/Ackee-Blockchain/trdelnik/pull/130))
- feat/refactored and improved program flow during init and build, added activity indicator ([#129](https://github.com/Ackee-Blockchain/trdelnik/pull/129))
- feat/allow solana versions up to v1.17.* and pin Rust 1.77 nightly compiler ([#128](https://github.com/Ackee-Blockchain/trdelnik/pull/128))
Expand Down
12 changes: 9 additions & 3 deletions crates/client/src/fuzzer/accounts_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use crate::{data_builder::FuzzClient, AccountId};

pub struct PdaStore {
pubkey: Pubkey,
pub pubkey: Pubkey,
pub seeds: Vec<Vec<u8>>,
}
impl PdaStore {
Expand All @@ -28,21 +28,27 @@ pub struct ProgramStore {

pub struct AccountsStorage<T> {
accounts: HashMap<AccountId, T>,
pub max_accounts: u8,
_max_accounts: u8,
}

impl<T> AccountsStorage<T> {
pub fn new(max_accounts: u8) -> Self {
let accounts: HashMap<AccountId, T> = HashMap::new();
Self {
accounts,
max_accounts,
_max_accounts: max_accounts,
}
}

/// Gets a reference to the account with the given account ID
pub fn get(&self, account_id: AccountId) -> Option<&T> {
self.accounts.get(&account_id)
}

/// Returns a mutable reference to the underlying HashMap that stores accounts with IDs as keys
pub fn storage(&mut self) -> &mut HashMap<AccountId, T> {
&mut self.accounts
}
}

impl<T> Default for AccountsStorage<T> {
Expand Down
28 changes: 22 additions & 6 deletions crates/client/src/fuzzer/data_builder.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use anchor_client::anchor_lang::solana_program::account_info::{Account as Acc, AccountInfo};
use anchor_client::anchor_lang::solana_program::hash::Hash;
use anchor_lang::prelude::Rent;
use arbitrary::Arbitrary;
use arbitrary::Unstructured;
use solana_sdk::account::Account;
use solana_sdk::account::{Account, AccountSharedData};
use solana_sdk::instruction::AccountMeta;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Keypair;
Expand Down Expand Up @@ -156,11 +157,15 @@ pub trait FuzzDeserialize<'info> {
) -> Result<Self::Ix, FuzzingError>;
}

/// A trait providing methods to read and write (manipulate) accounts
pub trait FuzzClient {
// TODO add method to add another program
// TODO add methods to modify current accounts
// TODO check if self must be mutable
/// Create an empty account and add lamports to it
fn set_account(&mut self, lamports: u64) -> Keypair;

/// Create or overwrite a custom account, subverting normal runtime checks.
fn set_account_custom(&mut self, address: &Pubkey, account: &AccountSharedData);

/// Create an SPL token account
#[allow(clippy::too_many_arguments)]
fn set_token_account(
&mut self,
Expand All @@ -172,23 +177,34 @@ pub trait FuzzClient {
delegated_amount: u64,
close_authority: Option<Pubkey>,
) -> Pubkey;

/// Create an SPL mint account
fn set_mint_account(
&mut self,
decimals: u8,
owner: &Pubkey,
freeze_authority: Option<Pubkey>,
) -> Pubkey;

/// Get the Keypair of the client's payer account
fn payer(&self) -> Keypair;

fn get_account(&mut self, key: &Pubkey) -> Result<Option<Account>, FuzzClientError>; // TODO support dynamic errors
// TODO add interface to modify existing accounts
/// Get the account at the given address
fn get_account(&mut self, key: &Pubkey) -> Result<Option<Account>, FuzzClientError>;

/// Get accounts based on the supplied meta information
fn get_accounts(
&mut self,
metas: &[AccountMeta],
) -> Result<Vec<Option<Account>>, FuzzClientErrorWithOrigin>;

/// Get last blockhash
fn get_last_blockhash(&self) -> Hash;

/// Get the cluster rent
fn get_rent(&mut self) -> Result<Rent, FuzzClientError>;

/// Send a transaction and return until the transaction has been finalized or rejected.
fn process_transaction(
&mut self,
transaction: impl Into<VersionedTransaction>,
Expand Down
8 changes: 8 additions & 0 deletions crates/client/src/fuzzer/program_test_client_blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,12 @@ impl FuzzClient for ProgramTestClientBlocking {
.rt
.block_on(self.ctx.banks_client.process_transaction(transaction))?)
}

fn set_account_custom(&mut self, address: &Pubkey, account: &AccountSharedData) {
self.ctx.set_account(address, account);
}

fn get_rent(&mut self) -> Result<Rent, FuzzClientError> {
Ok(self.rt.block_on(self.ctx.banks_client.get_rent())?)
}
}
67 changes: 67 additions & 0 deletions documentation/docs/fuzzing/howto/fuzzing-howto-p3.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,70 @@ fn get_accounts(
}
```
Notice especially the helper method `fuzz_accounts.<account_name>.get_or_create_account` that is used to create a Keypair or retrieve the Public key of the already existing account.

## Create an arbitrary account
The `AccountsStorage<T>` type provides an implementation of the `get_or_create_account` method that helps you create new or read already existing accounts. There are different implementations for different types of storage (`Keypair`, `TokenStore`, `MintStore`, `PdaStore`) to simplify the creation of new accounts. There are cases when the provided implementation is not sufficient and it is necessary to create an account manually. These cases can be (but are not limited to) for example:

- you need to create a new account with a predefined address
- you need to create a new account that is not owned by the system program
- you need to create and initialize a new PDA account
- your program expects an account to be initialized in a previous instruction

In that case, you can use the `storage` method of the `AccountsStorage<T>` struct that exposes the underlying `HashMap<AccountId, T>` and you can add new accounts directly to it.

It is possible to create and store any kind of account. For example:

- to add an account that uses the #[account(zero)] anchor constraint (must be rent exempt, owned by your program, with empty data):

```rust
let state = fuzz_accounts
.state
.storage() // gets the storage of all `state` account variants
.entry(self.accounts.state) // returns the Keypair of the `state` account with the given `AccountId` if it has been added previously
.or_insert_with(|| {
let space = State::SIZE;
let rent_exempt_lamports = client.get_rent().unwrap().minimum_balance(space);
let keypair = Keypair::new();
let account = AccountSharedData::new_data_with_space::<[u8; 0]>(
rent_exempt_lamports,
&[],
space,
&my_program::id(),
)
.unwrap();
client.set_account_custom(&keypair.pubkey(), &account);
keypair
});
```

- to add a new system-owned account with a specific PDA (address):

```rust
let rent_exempt_for_token_acc = client
.get_rent()
.unwrap()
.minimum_balance(anchor_spl::token::spl_token::state::Account::LEN);

let my_pda = fuzz_accounts
.my_pda
.storage() // gets the storage of all `my_pda` account variants
.entry(self.accounts.my_pda) // returns the PdaStore struct of the `my_pda` account with the given `AccountId` if it has been added previously
.or_insert_with(|| {
let seeds = &[b"some-seeds"];
let pda = Pubkey::find_program_address(seeds, &my_program::id()).0;
let account = AccountSharedData::new_data_with_space::<[u8; 0]>(
rent_exempt_for_token_acc,
&[],
0,
&SYSTEM_PROGRAM_ID,
)
.unwrap();
client.set_account_custom(&pda, &account);
let vec_of_seeds: Vec<Vec<u8>> = seeds.iter().map(|&seed| seed.to_vec()).collect();
PdaStore {
pubkey: pda,
seeds: vec_of_seeds,
}
})
.pubkey();
```

0 comments on commit 34e7765

Please sign in to comment.