diff --git a/Cargo.lock b/Cargo.lock index 2aa1c3f..558296a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -843,7 +843,6 @@ version = "0.0.0" dependencies = [ "pinocchio", "pinocchio-pubkey", - "pinocchio-system", ] [[package]] @@ -1819,30 +1818,18 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pinocchio" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "530596fa307103e53257f2cf064815919ee7fbc4c7ab999f6f13cc7067c3aff1" +version = "0.8.3" +source = "git+https://github.com/anza-xyz/pinocchio.git?branch=febo%2Fcpi-tweaks#29da007983dfd3ce46a873f0da7a0cfea2f1fbd8" [[package]] name = "pinocchio-pubkey" version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6b20fcebc172c3cd3f54114b0241b48fa8e30893ced2eb4927aaba5e3a0ba5" +source = "git+https://github.com/anza-xyz/pinocchio.git?branch=febo%2Fcpi-tweaks#29da007983dfd3ce46a873f0da7a0cfea2f1fbd8" dependencies = [ "five8_const", "pinocchio", ] -[[package]] -name = "pinocchio-system" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f75423420ae70aa748cf611cab14cfd00af08d0d2d3d258cb0cf5e2880ec19c" -dependencies = [ - "pinocchio", - "pinocchio-pubkey", -] - [[package]] name = "pkg-config" version = "0.3.32" diff --git a/README.md b/README.md index 21e6359..e95de03 100644 --- a/README.md +++ b/README.md @@ -20,26 +20,26 @@ Entrypoint implementation currently included in the benchmark: - [`solana-nostd-entrypoint`](https://github.com/cavemanloverboy/solana-nostd-entrypoint) - [`solana-program`](https://github.com/anza-xyz/agave/tree/master/sdk/program) -| Benchmark | `pinocchio` | `solana-nostd-entrypoint` | `solana-program` | -| ------------- | ------------ | ------------------------- | ----------------- | +| Benchmark | `pinocchio` | `solana-nostd-entrypoint` | `solana-program` | +| ------------- | -------------- | ------------------------- | ------------------ | | _Entrypoint_ | -| Ping | 🟩 **14** | 🟩 **14** | 🟧 41 (+27) | -| Log | 🟩 **119** | 🟩 **119** | 🟧 146 (+27) | -| Account (1) | 🟩 **38** | 🟩 39 (+1) | 🟥 235 (+196) | -| Account (3) | 🟩 **66** | 🟩 69 (+3) | 🟥 541 (+475) | -| Account (5) | 🟩 **94** | 🟩 99 (+5) | 🟥 847 (+753) | -| Account (10) | 🟩 **164** | 🟩 174 (+10) | 🟥 1,612 (+1,448) | -| Account (20) | 🟩 **304** | 🟨 324 (+20) | 🟥 3,142 (+2,838) | -| Account (32) | 🟩 **472** | 🟨 504 (+32) | 🟥 4,978 (+4,506) | -| Account (64) | 🟩 **920** | 🟨 985 (+65) | 🟥 9,874 (+8,954) | +| Ping | 🟩 **14** | 🟩 15 (+1) | 🟥 117 (+103) | +| Log | 🟩 **119** | 🟩 120 (+1) | 🟥 222 (+103) | +| Account (1) | 🟩 **38** | 🟩 42 (+4) | 🟥 317 (+279) | +| Account (3) | 🟩 **66** | 🟩 72 (+6) | 🟥 641 (+575) | +| Account (5) | 🟩 **94** | 🟩 102 (+8) | 🟥 965 (+871) | +| Account (10) | 🟩 **164** | 🟩 177 (+13) | 🟥 1,775 (+1,611) | +| Account (20) | 🟩 **304** | 🟨 327 (+23) | 🟥 3,395 (+3,091) | +| Account (32) | 🟩 **472** | 🟨 507 (+35) | 🟥 5,339 (+4,867) | +| Account (64) | 🟩 **920** | 🟨 988 (+68) | 🟥 10,523 (+9,603) | | _CPI_ | -| CreateAccount | 🟩 **1,449** | 🟨 1,494 (+45) | 🟥 2,786 (+1,337) | -| Transfer | 🟩 **1,439** | 🟨 1,487 (+48) | 🟥 2,379 (+940) | +| CreateAccount | 🟩 **1,311** | 🟩 1,314 (+3) | 🟥 2,866 (+1,555) | +| Transfer | 🟩 **1,307** | 🟩 1,309 (+2) | 🟥 2,459 (+1,152) | > [!IMPORTANT] > Values correspond to compute units (CUs) consumed by the entrypoint. The delta in relation to the lowest consumption is shown in brackets. > -> Solana CLI `v2.2.6` was used in the bench tests. +> Solana CLI `v2.2.13` was used in the bench tests. ## Benchmark diff --git a/programs/pinocchio/Cargo.toml b/programs/pinocchio/Cargo.toml index 0fd20ab..9c63c54 100644 --- a/programs/pinocchio/Cargo.toml +++ b/programs/pinocchio/Cargo.toml @@ -17,6 +17,5 @@ check-cfg = [ crate-type = ["cdylib", "lib"] [dependencies] -pinocchio = "0.8" -pinocchio-pubkey = "0.2.4" -pinocchio-system = "0.2.3" +pinocchio = { version = "0.8", git = "https://github.com/anza-xyz/pinocchio.git", branch = "febo/cpi-tweaks" } +pinocchio-pubkey = { version = "0.2", git = "https://github.com/anza-xyz/pinocchio.git", branch = "febo/cpi-tweaks" } diff --git a/programs/pinocchio/src/cpi/create_account.rs b/programs/pinocchio/src/cpi/create_account.rs new file mode 100644 index 0000000..eca6291 --- /dev/null +++ b/programs/pinocchio/src/cpi/create_account.rs @@ -0,0 +1,58 @@ +use super::SYSTEM_PROGRAM_ID; +use pinocchio::{ + account_info::AccountInfo, + cpi::invoke_signed_unchecked, + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + ProgramResult, +}; + +/// Create a new account. +/// +/// This function is a wrapper around the system program's `create_account` +/// instruction. +/// +/// # Safety +/// +/// This function assumes that accounts are not mutably borrowed. +pub unsafe fn create_account_unchecked( + from: &AccountInfo, + to: &AccountInfo, + lamports: u64, + space: u64, + owner: &Pubkey, +) -> ProgramResult { + // instruction accounts + let account_metas = [ + AccountMeta::writable_signer(from.key()), + AccountMeta::writable_signer(to.key()), + ]; + + // instruction data + // - [0..4 ]: instruction discriminator + // - [4..12 ]: lamports + // - [12..20]: account space + // - [20..52]: owner pubkey + let mut instruction_data = [0; 52]; + // create account instruction has a '0' discriminator + instruction_data[4..12].copy_from_slice(&lamports.to_le_bytes()); + instruction_data[12..20].copy_from_slice(&space.to_le_bytes()); + instruction_data[20..52].copy_from_slice(owner); + + // SAFETY: Accounts are in the correct order since the helper created + // the instruction accounts array. The caller must guarantee that accounts + // are not mutably borrowed. + unsafe { + invoke_signed_unchecked( + &Instruction { + program_id: &SYSTEM_PROGRAM_ID, + accounts: &account_metas, + data: &instruction_data, + }, + &[from.into(), to.into()], + &[], + ); + } + + Ok(()) +} diff --git a/programs/pinocchio/src/cpi/mod.rs b/programs/pinocchio/src/cpi/mod.rs new file mode 100644 index 0000000..97af9af --- /dev/null +++ b/programs/pinocchio/src/cpi/mod.rs @@ -0,0 +1,9 @@ +use pinocchio::pubkey::Pubkey; + +mod create_account; +mod transfer; + +pub use create_account::create_account_unchecked; +pub use transfer::transfer_unchecked; + +const SYSTEM_PROGRAM_ID: Pubkey = [0; 32]; diff --git a/programs/pinocchio/src/cpi/transfer.rs b/programs/pinocchio/src/cpi/transfer.rs new file mode 100644 index 0000000..25d82c0 --- /dev/null +++ b/programs/pinocchio/src/cpi/transfer.rs @@ -0,0 +1,51 @@ +use super::SYSTEM_PROGRAM_ID; +use pinocchio::{ + account_info::AccountInfo, + cpi::invoke_signed_unchecked, + instruction::{AccountMeta, Instruction}, + ProgramResult, +}; + +/// Transfer lamports between accounts. +/// +/// This function is a wrapper around the system program's `transfer` +/// instruction. +/// +/// # Safety +/// +/// This function assumes that accounts are not mutably borrowed. +pub unsafe fn transfer_unchecked( + from: &AccountInfo, + to: &AccountInfo, + lamports: u64, +) -> ProgramResult { + // instruction accounts + let account_metas = [ + AccountMeta::writable_signer(from.key()), + AccountMeta::writable(to.key()), + ]; + + // instruction data + // - [0..4 ]: instruction discriminator + // - [4..12]: lamports amount + let mut instruction_data = [0; 12]; + instruction_data[0] = 2; + instruction_data[4..12].copy_from_slice(&lamports.to_le_bytes()); + + // SAFETY: Accounts are in the correct order since the helper created + // the instruction accounts array. The caller must guarantee that accounts + // are not mutably borrowed. + unsafe { + invoke_signed_unchecked( + &Instruction { + program_id: &SYSTEM_PROGRAM_ID, + accounts: &account_metas, + data: &instruction_data, + }, + &[from.into(), to.into()], + &[], + ); + } + + Ok(()) +} diff --git a/programs/pinocchio/src/entrypoint.rs b/programs/pinocchio/src/entrypoint.rs index 078fd29..2e7545d 100644 --- a/programs/pinocchio/src/entrypoint.rs +++ b/programs/pinocchio/src/entrypoint.rs @@ -5,10 +5,15 @@ use { process_account, process_create_account, process_log, process_ping, process_transfer, }, }, - pinocchio::{account_info::AccountInfo, entrypoint, pubkey::Pubkey, ProgramResult}, + pinocchio::{ + account_info::AccountInfo, no_allocator, nostd_panic_handler, program_entrypoint, + pubkey::Pubkey, ProgramResult, + }, }; -entrypoint!(process_instruction); +program_entrypoint!(process_instruction); +no_allocator!(); +nostd_panic_handler!(); #[inline(always)] pub fn process_instruction( diff --git a/programs/pinocchio/src/lib.rs b/programs/pinocchio/src/lib.rs index 61f3e92..67e5b4e 100644 --- a/programs/pinocchio/src/lib.rs +++ b/programs/pinocchio/src/lib.rs @@ -1,3 +1,6 @@ +#![no_std] + +pub mod cpi; pub mod entrypoint; pub mod instruction; pub mod processor; diff --git a/programs/pinocchio/src/processor.rs b/programs/pinocchio/src/processor.rs index 10c94ae..b676d63 100644 --- a/programs/pinocchio/src/processor.rs +++ b/programs/pinocchio/src/processor.rs @@ -1,5 +1,5 @@ +use crate::cpi::{create_account_unchecked, transfer_unchecked}; use pinocchio::{account_info::AccountInfo, msg, program_error::ProgramError, ProgramResult}; -use pinocchio_system::instructions::{CreateAccount, Transfer}; #[inline(always)] pub fn process_ping() -> ProgramResult { @@ -23,22 +23,18 @@ pub fn process_account(accounts: &[AccountInfo], expected: u64) -> ProgramResult #[inline(always)] pub fn process_create_account(accounts: &[AccountInfo]) -> ProgramResult { - CreateAccount { - from: &accounts[0], - to: &accounts[1], - lamports: 500_000_000, - space: 10, - owner: &crate::ID, - } - .invoke() + let [from, to, _remaining @ ..] = accounts else { + return Err(ProgramError::InvalidArgument); + }; + + unsafe { create_account_unchecked(from, to, 500_000_000, 10, &crate::ID) } } #[inline(always)] pub fn process_transfer(accounts: &[AccountInfo]) -> ProgramResult { - Transfer { - from: &accounts[0], - to: &accounts[1], - lamports: 1_000_000_000, - } - .invoke() + let [from, to, _remaining @ ..] = accounts else { + return Err(ProgramError::InvalidArgument); + }; + + unsafe { transfer_unchecked(from, to, 1_000_000_000) } } diff --git a/programs/solana-nostd-entrypoint/src/cpi/create_account.rs b/programs/solana-nostd-entrypoint/src/cpi/create_account.rs index a5a9649..583b66a 100644 --- a/programs/solana-nostd-entrypoint/src/cpi/create_account.rs +++ b/programs/solana-nostd-entrypoint/src/cpi/create_account.rs @@ -1,4 +1,4 @@ -use super::invoke; +use super::invoke_unchecked; use solana_nostd_entrypoint::{InstructionC, NoStdAccountInfo}; use solana_program::{entrypoint::ProgramResult, pubkey::Pubkey, system_program}; @@ -6,7 +6,11 @@ use solana_program::{entrypoint::ProgramResult, pubkey::Pubkey, system_program}; /// /// This function is a wrapper around the system program's `create_account` /// instruction. -pub fn create_account( +/// +/// # Safety +/// +/// This function assumes that accounts are not mutably borrowed. +pub unsafe fn create_account_unchecked( from: &NoStdAccountInfo, to: &NoStdAccountInfo, lamports: u64, @@ -26,7 +30,7 @@ pub fn create_account( let instruction_accounts = [from.to_meta_c_signer(), to.to_meta_c_signer()]; - invoke( + invoke_unchecked( &InstructionC { program_id: &system_program::ID, accounts: instruction_accounts.as_ptr(), diff --git a/programs/solana-nostd-entrypoint/src/cpi/mod.rs b/programs/solana-nostd-entrypoint/src/cpi/mod.rs index 4b30e15..b9199cc 100644 --- a/programs/solana-nostd-entrypoint/src/cpi/mod.rs +++ b/programs/solana-nostd-entrypoint/src/cpi/mod.rs @@ -19,7 +19,12 @@ pub use transfer::*; /// /// These checks are similar to the checks performed by the default invoke in /// `solana_program`. -fn invoke( +/// +/// # Safety +/// +/// This function assumes that accounts are not mutably borrowed and passed +/// in the correct order. +unsafe fn invoke_unchecked( instruction: &InstructionC, accounts: &[&NoStdAccountInfo; ACCOUNTS], ) -> ProgramResult { @@ -29,27 +34,12 @@ fn invoke( const UNINIT: MaybeUninit = MaybeUninit::::uninit(); let mut infos = [UNINIT; ACCOUNTS]; - - let metas = unsafe { core::slice::from_raw_parts(instruction.accounts, ACCOUNTS) }; - - for index in 0..ACCOUNTS { - let info = &accounts[index]; - let meta = &metas[index]; - - if *info.key() != unsafe { *meta.pubkey } { - return Err(ProgramError::InvalidArgument); - } - - if meta.is_writable { - let _ = info.try_borrow_mut_data(); - let _ = info.try_borrow_mut_lamports(); - } else { - let _ = info.try_borrow_data(); - let _ = info.try_borrow_lamports(); - } - - infos[index].write(info.to_info_c()); - } + infos + .iter_mut() + .zip(accounts.iter()) + .for_each(|(info, account)| { + info.write(account.to_info_c()); + }); let seeds: &[&[&[u8]]] = &[]; diff --git a/programs/solana-nostd-entrypoint/src/cpi/transfer.rs b/programs/solana-nostd-entrypoint/src/cpi/transfer.rs index 06aa92c..2904cf6 100644 --- a/programs/solana-nostd-entrypoint/src/cpi/transfer.rs +++ b/programs/solana-nostd-entrypoint/src/cpi/transfer.rs @@ -1,4 +1,4 @@ -use super::invoke; +use super::invoke_unchecked; use solana_nostd_entrypoint::{InstructionC, NoStdAccountInfo}; use solana_program::{entrypoint::ProgramResult, system_program}; @@ -6,7 +6,15 @@ use solana_program::{entrypoint::ProgramResult, system_program}; /// /// This function is a wrapper around the system program's `transfer` /// instruction. -pub fn transfer(from: &NoStdAccountInfo, to: &NoStdAccountInfo, lamports: u64) -> ProgramResult { +/// +/// # Safety +/// +/// This function assumes that accounts are not mutably borrowed. +pub unsafe fn transfer_unchecked( + from: &NoStdAccountInfo, + to: &NoStdAccountInfo, + lamports: u64, +) -> ProgramResult { // instruction data // - [0..4 ]: instruction discriminator // - [4..12 ]: lamports @@ -16,7 +24,7 @@ pub fn transfer(from: &NoStdAccountInfo, to: &NoStdAccountInfo, lamports: u64) - let instruction_accounts = [from.to_meta_c_signer(), to.to_meta_c_signer()]; - invoke( + invoke_unchecked( &InstructionC { program_id: &system_program::ID, accounts: instruction_accounts.as_ptr(), diff --git a/programs/solana-nostd-entrypoint/src/processor.rs b/programs/solana-nostd-entrypoint/src/processor.rs index da10b96..3a22e79 100644 --- a/programs/solana-nostd-entrypoint/src/processor.rs +++ b/programs/solana-nostd-entrypoint/src/processor.rs @@ -1,5 +1,5 @@ use { - crate::cpi::{create_account, transfer}, + crate::cpi::{create_account_unchecked, transfer_unchecked}, solana_nostd_entrypoint::NoStdAccountInfo, solana_program::{entrypoint::ProgramResult, msg, program_error::ProgramError}, }; @@ -26,10 +26,18 @@ pub fn process_account(accounts: &[NoStdAccountInfo], expected: u64) -> ProgramR #[inline(always)] pub fn process_create_account(accounts: &[NoStdAccountInfo]) -> ProgramResult { - create_account(&accounts[0], &accounts[1], 500_000_000, 10, &crate::ID) + let [from, to, _remaining @ ..] = accounts else { + return Err(ProgramError::InvalidArgument); + }; + + unsafe { create_account_unchecked(from, to, 500_000_000, 10, &crate::ID) } } #[inline(always)] pub fn process_transfer(accounts: &[NoStdAccountInfo]) -> ProgramResult { - transfer(&accounts[0], &accounts[1], 1_000_000_000) + let [from, to, _remaining @ ..] = accounts else { + return Err(ProgramError::InvalidArgument); + }; + + unsafe { transfer_unchecked(from, to, 1_000_000_000) } }