diff --git a/Cargo.lock b/Cargo.lock index 2aa1c3f..43722f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -618,6 +618,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "const-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c06f1eb05f06cf2e380fdded278fbf056a38974299d77960555a311dcf91a52" +dependencies = [ + "keccak-const", + "sha2-const-stable", +] + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -825,6 +835,7 @@ dependencies = [ name = "eisodos" version = "0.0.0" dependencies = [ + "eisodos-jiminy", "eisodos-pinocchio", "eisodos-solana-nostd-entrypoint", "eisodos-solana-program", @@ -837,6 +848,18 @@ dependencies = [ "solana-system-interface", ] +[[package]] +name = "eisodos-jiminy" +version = "0.0.0" +dependencies = [ + "const-crypto", + "jiminy-cpi", + "jiminy-entrypoint", + "jiminy-log", + "jiminy-syscall", + "jiminy-system-prog-interface", +] + [[package]] name = "eisodos-pinocchio" version = "0.0.0" @@ -1047,6 +1070,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "generic-array-struct" +version = "0.3.0-bc" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3313f0db62e8711fcace7bf202025b00efe5ab9c8d35209241b79c877f72ba6f" +dependencies = [ + "heck", + "quote", + "syn 2.0.87", +] + [[package]] name = "gethostname" version = "0.2.3" @@ -1141,6 +1175,12 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -1344,6 +1384,69 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jiminy-account" +version = "0.1.0" +source = "git+https://github.com/igneous-labs/jiminy?branch=master#a428ab29c91871f74f8411cf87a780a6426ed068" +dependencies = [ + "jiminy-program-error", +] + +[[package]] +name = "jiminy-cpi" +version = "0.1.0" +source = "git+https://github.com/igneous-labs/jiminy?branch=master#a428ab29c91871f74f8411cf87a780a6426ed068" +dependencies = [ + "jiminy-account", + "jiminy-pda", + "jiminy-program-error", + "jiminy-syscall", +] + +[[package]] +name = "jiminy-entrypoint" +version = "0.1.0" +source = "git+https://github.com/igneous-labs/jiminy?branch=master#a428ab29c91871f74f8411cf87a780a6426ed068" +dependencies = [ + "jiminy-account", + "jiminy-syscall", +] + +[[package]] +name = "jiminy-log" +version = "0.1.0" +source = "git+https://github.com/igneous-labs/jiminy?branch=master#a428ab29c91871f74f8411cf87a780a6426ed068" +dependencies = [ + "jiminy-syscall", +] + +[[package]] +name = "jiminy-pda" +version = "0.1.0" +source = "git+https://github.com/igneous-labs/jiminy?branch=master#a428ab29c91871f74f8411cf87a780a6426ed068" +dependencies = [ + "jiminy-syscall", +] + +[[package]] +name = "jiminy-program-error" +version = "0.1.0" +source = "git+https://github.com/igneous-labs/jiminy?branch=master#a428ab29c91871f74f8411cf87a780a6426ed068" + +[[package]] +name = "jiminy-syscall" +version = "0.1.0" +source = "git+https://github.com/igneous-labs/jiminy?branch=master#a428ab29c91871f74f8411cf87a780a6426ed068" + +[[package]] +name = "jiminy-system-prog-interface" +version = "0.1.0" +source = "git+https://github.com/igneous-labs/jiminy?branch=master#a428ab29c91871f74f8411cf87a780a6426ed068" +dependencies = [ + "generic-array-struct", + "jiminy-cpi", +] + [[package]] name = "jobserver" version = "0.1.32" @@ -1372,6 +1475,12 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + [[package]] name = "lazy_static" version = "1.5.0" @@ -2287,6 +2396,12 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + [[package]] name = "sha3" version = "0.10.8" diff --git a/Cargo.toml b/Cargo.toml index 4876486..4150e86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "2" members = [ "benchmark", + "programs/jiminy", "programs/pinocchio", "programs/solana-nostd-entrypoint", "programs/solana-program" diff --git a/README.md b/README.md index 21e6359..24e463f 100644 --- a/README.md +++ b/README.md @@ -19,28 +19,37 @@ Entrypoint implementation currently included in the benchmark: - [`pinocchio`](https://github.com/anza-xyz/pinocchio) - [`solana-nostd-entrypoint`](https://github.com/cavemanloverboy/solana-nostd-entrypoint) - [`solana-program`](https://github.com/anza-xyz/agave/tree/master/sdk/program) +- [`jiminy`](https://github.com/igneous-labs/jiminy) -| Benchmark | `pinocchio` | `solana-nostd-entrypoint` | `solana-program` | -| ------------- | ------------ | ------------------------- | ----------------- | +| Benchmark | `pinocchio` | `solana-nostd-entrypoint` | `solana-program` | `jiminy` | +| ------------- | --------------- | ------------------------- | ----------------- | ------------ | | _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** | 🟩 **14** | 🟧 41 (+27) | 🟩 **14** | +| Log | 🟩 **119** | 🟩 **119** | 🟧 146 (+27) | 🟩 **119** | +| Account (1) | 🟩 38 (+2) | 🟩 39 (+3) | 🟥 235 (+199) | 🟩 **36** | +| Account (3) | 🟩 **66** | 🟩 69 (+3) | 🟥 541 (+475) | 🟩 **66** | +| Account (5) | 🟩 **94** | 🟩 99 (+5) | 🟥 847 (+751) | 🟩 96 (+2) | +| Account (10) | 🟩 **164** | 🟩 174 (+10) | 🟥 1,612 (+1,441) | 🟩 171 (+7) | +| Account (20) | 🟩 **304** | 🟨 324 (+20) | 🟥 3,142 (+2,821) | 🟨 321 (+17) | +| Account (32) | 🟩 **472** | 🟨 504 (+32) | 🟥 4,978 (+4,477) | 🟨 501 (+29) | +| Account (64) | 🟩 **920** | 🟨 985 (+65) | 🟥 9,874 (+8,893) | 🟨 981 (+61) | | _CPI_ | -| CreateAccount | 🟩 **1,449** | 🟨 1,494 (+45) | 🟥 2,786 (+1,337) | -| Transfer | 🟩 **1,439** | 🟨 1,487 (+48) | 🟥 2,379 (+940) | +| CreateAccount | 🟨 1,449 (+142) | 🟨 1,494 (+187) | 🟥 2,786 (+1,479) | 🟩 **1,307** | +| Transfer | 🟨 1,439 (+140) | 🟨 1,487 (+180) | 🟥 2,379 (+1,080) | 🟩 **1,299** | > [!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. +## Binary Sizes + +The size of the compiled benchmark program for each entrypoint is shown below. The delta in relation to the smallest binary size is shown in brackets. + +| Binary size (bytes) | `pinocchio` | `solana-nostd-entrypoint` | `solana-program` | `jiminy` | +| ------------------- | ------------------ | ------------------------- | ------------------- | -------- | +| | 🟥 10,736 (+7,240) | 🟥 17,720 (+14,224) | 🟥 64,688 (+61,192) | 🟩 3,496 | + ## Benchmark The benchmark uses a simple program with multiple instructions to measure the compute units (CUs) consumed by the entrypoint. Note that the intention is not to write the most efficient program, instead to reflect an "average" program implementation. The aim is to use the exactly same program implementation, replacing the entrypoint to determine the impact on the CUs consumed. diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index a47623d..5966426 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -7,6 +7,7 @@ name = "coyote" path = "src/main.rs" [dev-dependencies] +eisodos-jiminy = { path="../programs/jiminy" } eisodos-pinocchio = { path="../programs/pinocchio" } eisodos-solana-nostd-entrypoint = { path="../programs/solana-nostd-entrypoint" } eisodos-solana-program = { path="../programs/solana-program" } diff --git a/benchmark/benches/jiminy.rs b/benchmark/benches/jiminy.rs new file mode 100644 index 0000000..b93eb0e --- /dev/null +++ b/benchmark/benches/jiminy.rs @@ -0,0 +1,23 @@ +#![feature(test)] + +extern crate mollusk_svm; +extern crate mollusk_svm_bencher; +extern crate solana_account; +extern crate solana_instruction; +extern crate solana_pubkey; +extern crate test; + +mod setup; +use setup::*; + +#[cfg(test)] +mod jiminy { + + use super::*; + use test::Bencher; + + #[bench] + fn run(_bencher: &mut Bencher) { + runner::run(&eisodos_jiminy::ID.into(), "eisodos_jiminy"); + } +} diff --git a/programs/jiminy/Cargo.toml b/programs/jiminy/Cargo.toml new file mode 100644 index 0000000..6dff70c --- /dev/null +++ b/programs/jiminy/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "eisodos-jiminy" +version = "0.0.0" +edition = "2021" +publish = false + +[package.metadata.solana] +program-id = "Jim1ny1111111111111111111111111111111111111" + +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = [ + 'cfg(target_os, values("solana"))', +] + +[lib] +crate-type = ["cdylib", "lib"] + +[dependencies] +const-crypto = { version = "^0.3", default-features = false } +jiminy-cpi = { git = "https://github.com/igneous-labs/jiminy", branch = "master" } +jiminy-entrypoint = { git = "https://github.com/igneous-labs/jiminy", branch = "master" } +jiminy-log = { git = "https://github.com/igneous-labs/jiminy", branch = "master" } +jiminy-syscall = { git = "https://github.com/igneous-labs/jiminy", branch = "master" } +jiminy-system-prog-interface = { git = "https://github.com/igneous-labs/jiminy", branch = "master" } diff --git a/programs/jiminy/src/entrypoint.rs b/programs/jiminy/src/entrypoint.rs new file mode 100644 index 0000000..5b0a94f --- /dev/null +++ b/programs/jiminy/src/entrypoint.rs @@ -0,0 +1,27 @@ +use crate::{ + instruction::Instruction, + processor::{ + process_account, process_create_account, process_log, process_ping, process_transfer, + }, + Accounts, ProgramResult, MAX_ACCOUNTS, +}; +use jiminy_entrypoint::entrypoint; + +entrypoint!(process_instruction, MAX_ACCOUNTS); + +#[inline(always)] +pub fn process_instruction( + accounts: &mut Accounts, + instruction_data: &[u8], + _program_id: &[u8; 32], +) -> ProgramResult { + let instruction = Instruction::unpack(instruction_data)?; + + match instruction { + Instruction::Ping => process_ping(), + Instruction::Log => process_log(), + Instruction::Account { expected } => process_account(accounts, expected), + Instruction::CreateAccount => process_create_account(accounts), + Instruction::Transfer => process_transfer(accounts), + } +} diff --git a/programs/jiminy/src/instruction.rs b/programs/jiminy/src/instruction.rs new file mode 100644 index 0000000..0d816d6 --- /dev/null +++ b/programs/jiminy/src/instruction.rs @@ -0,0 +1,37 @@ +use jiminy_entrypoint::program_error::{BuiltInProgramError, ProgramError}; + +#[derive(Clone, Debug)] +#[rustfmt::skip] +pub enum Instruction { + Ping, + Log, + Account { + expected: u64, + }, + CreateAccount, + Transfer, +} + +impl Instruction { + /// Unpacks a byte buffer into a [Instruction](enum.Instruction.html). + #[inline(always)] + pub fn unpack(input: &[u8]) -> Result { + match input.split_first() { + // 0 - Ping + Some((&0, [])) => Ok(Instruction::Ping), + // 1 - Log + Some((&1, [])) => Ok(Instruction::Log), + // 2 - Account + Some((&2, remaining)) if remaining.len() == 8 => Ok(Instruction::Account { + expected: u64::from_le_bytes(remaining[0..8].try_into().unwrap()), + }), + // 3 - CreateAccount + Some((&3, [])) => Ok(Instruction::CreateAccount), + // 4 - Transfer + Some((&4, [])) => Ok(Instruction::Transfer), + _ => Err(ProgramError::from_builtin( + BuiltInProgramError::InvalidInstructionData, + )), + } + } +} diff --git a/programs/jiminy/src/lib.rs b/programs/jiminy/src/lib.rs new file mode 100644 index 0000000..81d4acf --- /dev/null +++ b/programs/jiminy/src/lib.rs @@ -0,0 +1,19 @@ +use jiminy_entrypoint::program_error::ProgramError; + +pub mod entrypoint; +pub mod instruction; +pub mod processor; + +type ProgramResult = Result<(), ProgramError>; + +const MAX_ACCOUNTS: usize = 128; +// TODO: CPI takes up way too much stack space +const MAX_CPI_ACCOUNTS: usize = 36; + +type Cpi = jiminy_cpi::Cpi; + +type Accounts<'a> = jiminy_entrypoint::account::Accounts<'a, MAX_ACCOUNTS>; + +const PROG_ID_STR: &str = "Jim1ny1111111111111111111111111111111111111"; + +pub const ID: [u8; 32] = const_crypto::bs58::decode_pubkey(PROG_ID_STR); diff --git a/programs/jiminy/src/processor.rs b/programs/jiminy/src/processor.rs new file mode 100644 index 0000000..bbba034 --- /dev/null +++ b/programs/jiminy/src/processor.rs @@ -0,0 +1,72 @@ +use crate::{Accounts, Cpi, ProgramResult}; +use jiminy_entrypoint::program_error::{BuiltInProgramError, ProgramError}; +use jiminy_log::sol_log; +use jiminy_system_prog_interface::{ + create_account_ix, transfer_ix, CreateAccountIxAccounts, CreateAccountIxData, + TransferIxAccounts, TransferIxData, +}; + +#[inline(always)] +pub fn process_ping() -> ProgramResult { + Ok(()) +} + +#[inline(always)] +pub fn process_log() -> ProgramResult { + const MSG: &str = "Instruction: Log"; + sol_log(MSG); + Ok(()) +} + +#[inline(always)] +pub fn process_account(accounts: &Accounts, expected: u64) -> ProgramResult { + if accounts.len() == expected as usize { + Ok(()) + } else { + Err(ProgramError::from_builtin( + BuiltInProgramError::InvalidArgument, + )) + } +} + +#[inline(always)] +pub fn process_create_account(accounts: &mut Accounts) -> ProgramResult { + let [funding, new, sys_prog] = accounts.as_slice() else { + return Err(ProgramError::from_builtin( + BuiltInProgramError::NotEnoughAccountKeys, + )); + }; + let [funding, new, sys_prog] = [funding, new, sys_prog].map(|h| *h); + Cpi::new().invoke_signed( + accounts, + create_account_ix( + sys_prog, + CreateAccountIxAccounts::memset(sys_prog) + .with_funding(funding) + .with_new(new), + &CreateAccountIxData::new(500_000_000, 10, &crate::ID), + ), + &[], + ) +} + +#[inline(always)] +pub fn process_transfer(accounts: &mut Accounts) -> ProgramResult { + let [from, to, sys_prog] = accounts.as_slice() else { + return Err(ProgramError::from_builtin( + BuiltInProgramError::NotEnoughAccountKeys, + )); + }; + let [from, to, sys_prog] = [from, to, sys_prog].map(|h| *h); + Cpi::new().invoke_signed( + accounts, + transfer_ix( + sys_prog, + TransferIxAccounts::memset(sys_prog) + .with_from(from) + .with_to(to), + &TransferIxData::new(1_000_000_000), + ), + &[], + ) +}