Crates.io | poc-framework-osec |
lib.rs | poc-framework-osec |
version | 0.1.1 |
source | src |
created_at | 2022-04-10 03:20:12.418036 |
updated_at | 2022-04-10 03:33:32.164581 |
description | A framework for creating PoC's for Solana Smart Contracts in a painless and intuitive way: A fork of https://github.com/neodyme-labs/solana-poc-framework |
homepage | |
repository | https://github.com/otter-sec/solana-poc-framework |
max_upload_size | |
id | 564956 |
size | 502,006 |
This project is a fork of poc-framework.
DISCLAIMER: any illegal usage of this framework is heavily discouraged. Most projects on Solana offer a more than generous bug bounty. Also you don't want your kneecaps broken.
To get started, just add the following line to the dependencies
section in your Cargo.toml
:
[dependencies]
poc-framework-osec = "0.1.1"
This crate already re-exports every Solana dependency you should need.
This framework was made for security researchers, to facilitate a fast and convenient development of Proof-of-Concepts for bugs in Solana smart contracts or even Solana core. The generic Environment
interface allows for exploits to be developed locally, and then tested on Testnet or Devnet.
This framework offers many utility functions that proved very useful time and time again for the PoC's we developed for the smart contrats we audited at Neodyme.
The first thing you want to do in any PoC is setup logging. This is especially useful if you use a local environment, as it is the only way to figure out why a transaction could not be executed (if for example signers are missing):
setup_logging(LogLevel::DEBUG);
Afterwards you want to define what keys you will use. Keys should easily be identifiable when printing a transaction. This purpoise gets fulfilled by the keypair(n: u8)
function. The framework contains 256 pre-ground keys that start with Kxxx
, where xxx
is the 3-digit representation of the argument n
. Note that the base58 charset does not contain 0
, which is why we used o
instead:
let authority = keypair(0); // KoooVyhdpoRPA6gpn7xr3cmjqAvtpHcjcBX6JBKu1nf
let target = keypair(1); // Koo1BQTQYawwKVBg71J2sru7W51EJgfbyyHsTFCssRW
let mint = keypair(2); // Koo2SZ393psmp7ags3hMz59ciV3XWLj1GkPousNgTH1
let victim = keypair(137); // K137jwH7CncXBTadHbLDsHNWUhuLDN4ddegJL2hmn6u
There is also a random_keypair
function if you don't care about recognising a keypair.
Also very valuable for debugging purpoises is the ability to print the result of a transaction in a neat way. For this the framework provides the trait PrintableTransaction
, which it implements both for ConfirmedTransaction
as well as EncodedConfirmedTransaction
. This trait provides the function print
, which can conviniently be chained to the end of any env.execute_transaction
call:
env.execute_as_transaction(&[...], &[...]).print();
At the core if this framework is the Environment
trait. This encapsulates the ability to execute transactions on some chain state, as well as the utility of having a payer
that pays for all fee and rent expenditures.
There are currently two different implementations: the RemoteEnvironment
which executes all transactions on a cluster, and the LocalEnvironment
, which executes all transactions locally on an arbitrary chain state.
The Environment
trait also provides many useful shortcuts for sending transactions, like interacting with spl-token
accounts or even creating accounts with arbitrary content (but obviously with a fixed owner).
To construct a remote environment, you require an RpcClient
. These can be conveniently construced using devnet_client()
/testnet_client()
/localhost_client()
. We do not condone using this framework on mainnet. Airdrops are also implemented, with the new_with_airdrop
and airdrop
functions.
let payer = read_keypair_file("big-fat-wallet.json").unwrap();
let client = devnet_client();
let mut env = RemoteEnvironment::new(client, payer);
Constructing a local environment usually takes some effort, as one has to first clone the relevant chain state. The framework offers many different ways of doing this. From deploying a contract from a file to inserting an arbitrary account up to cloning accounts and even whole upgradable programs from a cluster:
let mut env = LocalEnvironment::builder()
.add_account_with_lamports(authority, system_program::ID, sol_to_lamports(10.0))
.add_token_mint(mint, Some(authority), 0, 1, None)
.add_associated_token_account(authority, mint, 1337)
.clone_upgradable_program_from_cluster(client, my_program::ID)
.build();
Note however that it is possible to craft state that is not legal on the chain using this builder (for example accounts that belong to a program that contain state that the program itself would never write to it), leading to exploits that are only reproducible locally. Try to use transactions on the environment for as many things as possible to prevent these pitfalls.