Yet another no-std no-dependencies library for writing solana programs.
A simple jiminy program that CPIs the system program to transfer 1 SOL from the first input account to the second input account:
use jiminy_cpi::{program_error::BuiltInProgramError, Cpi};
use jiminy_entrypoint::program_error::ProgramError;
use jiminy_system_prog_interface::{TransferIxAccs, TransferIxData};
// Determines the maximum number of accounts that can be deserialized and
// saved to [`Accounts`]. Any proceeding accounts are discarded.
const MAX_ACCS: usize = 3;
// Determines the maximum number of accounts that can be used in a CPI,
// excluding the program being invoked.
const MAX_CPI_ACCS: usize = 2;
const ONE_SOL_IN_LAMPORTS: u64 = 1_000_000_000;
type Accounts<'account> = jiminy_entrypoint::account::Accounts<'account, MAX_ACCS>;
jiminy_entrypoint::entrypoint!(process_ix, MAX_ACCS);
fn process_ix(
accounts: &mut Accounts,
_data: &[u8],
_prog_id: &[u8; 32],
) -> Result<(), ProgramError> {
let (sys_prog, transfer_accs) = match accounts.as_slice().split_last_chunk() {
Some((&[sys_prog], ta)) => (sys_prog, TransferIxAccs(*ta)),
_ => {
return Err(ProgramError::from_builtin(
BuiltInProgramError::NotEnoughAccountKeys,
))
}
};
let sys_prog_key = *accounts.get(sys_prog).key();
Cpi::<MAX_CPI_ACCS>::new().invoke_fwd(
accounts,
&sys_prog_key,
TransferIxData::new(trf_amt).as_buf(),
transfer_accs.0,
)?;
Ok(())
}
Instead of using RefCell
s (maybe wrapped in an Rc
) to implement dynamic borrow checking at runtime
like the other libraries, jiminy solves the issue of aliasing duplicated accounts at compile-time. It does so
by encapsulating all deserialized accounts into a common Accounts
collection that only allows
at any time, either a single Account
to be mutably borrowed or multiple Account
s to be immutably borrowed, just as it is
with any other safe collections data structure (Vec
, HashMap
etc) in Rust. Access is granted via dispensed AccountHandle
s,
which can be held across mutable accesses since they are inert until used.
fn process_ix(
accounts: &mut Accounts,
_data: &[u8],
_prog_id: &[u8; 32],
) -> Result<(), ProgramError> {
let [handle_a, handle_b] = *accounts.as_slice() else {
return Err(ProgramError::from_builtin(
BuiltInProgramError::NotEnoughAccountKeys,
));
};
let a = accounts.get_mut(handle_a);
let b = accounts.get(handle_b);
a.dec_lamports(1); // this fails to compile with "cannot borrow accounts as immutable because it is also borrowed as mutable"
Ok(())
}
The result of this is higher performance and smaller binary sizes because all the previous code
related to handling of RefCell
s is no longer required. Say goodbye to the dreaded AccountBorrowFailed
error.
In jiminy, ProgramError
is simply defined as:
#[repr(transparent)]
pub struct ProgramError(pub NonZeroU64);
This simple change of representation allows for much more optimized bytecode to be generated by the compiler,
as it removes many unnecessary conversion steps between more complex enum variants and the final return value
to put into r0
when the ebpf program exits.
At the same time, convenience functions are still provided for easily creating the built-in errors.
ProgramError::from_builtin(BuiltInProgramError::InvalidArgument)
The CPI syscall expects inputs in a very specific format. Most of the work of each library's invoke_signed()
function is in converting program input data
into the correct format and accumulating them in buffers so that they can be passed to the syscall. jiminy allows these buffers to be reused, minimizing
memory footprint.
// simply Box::new() this if more space is
// required than what is available on the stack.
let mut cpi: Cpi = Cpi::new();
cpi.invoke_fwd(
accounts,
&sys_prog_key,
TransferIxData::new(ONE_SOL_IN_LAMPORTS).as_buf(),
transfer_accs.0,
)?;
// use the same allocation again for a completely different CPI
cpi.invoke_signed(
accounts,
&sys_prog_key,
&AssignIxData::new(prog_id)
assign_accs.into_account_handle_perms(),
&[],
)?;
Current eisodos benchmark results
This section contains dev info for people who wish to work on the library.
$ cargo-build-sbf --version
solana-cargo-build-sbf 2.3.7
platform-tools v1.48
rustc 1.84.1