From a6628c438a084637d750418de390d3f5ec18b7ec Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 12 Jun 2026 18:40:42 +0000 Subject: [PATCH 1/2] feat(basics): add Pinocchio cross-program-invocation and repository-layout examples Port the two newest Pinocchio basics examples contributed to solana-developers/program-examples (upstream #582 and #584) into the Quicknode example set, which already carries Pinocchio variants for every other basics example. - cross-program-invocation/pinocchio: hand + lever two-program CPI demo - repository-layout/pinocchio: recommended multi-file program layout Both register in the workspace and follow the repo's pnpm build/build-and-test convention. Tests are written in Rust against LiteSVM (no JavaScript tests): repository-layout in program/tests/test.rs, cross-program-invocation in programs/lever/tests/test.rs. Original Pinocchio examples authored by @MarkFeder. Co-authored-by: Marco A. --- Cargo.lock | 37 +++++ Cargo.toml | 3 + .../pinocchio/README.md | 21 +++ .../pinocchio/package.json | 8 ++ .../pinocchio/pnpm-lock.yaml | 9 ++ .../pinocchio/programs/hand/Cargo.toml | 18 +++ .../pinocchio/programs/hand/src/lib.rs | 48 +++++++ .../pinocchio/programs/lever/Cargo.toml | 29 ++++ .../pinocchio/programs/lever/src/lib.rs | 76 ++++++++++ .../pinocchio/programs/lever/tests/test.rs | 136 ++++++++++++++++++ basics/repository-layout/pinocchio/README.md | 26 ++++ .../repository-layout/pinocchio/package.json | 9 ++ .../pinocchio/pnpm-lock.yaml | 9 ++ .../pinocchio/program/Cargo.toml | 26 ++++ .../pinocchio/program/src/error.rs | 1 + .../program/src/instructions/eat_food.rs | 35 +++++ .../program/src/instructions/get_on_ride.rs | 51 +++++++ .../pinocchio/program/src/instructions/mod.rs | 3 + .../program/src/instructions/play_game.rs | 40 ++++++ .../pinocchio/program/src/lib.rs | 13 ++ .../pinocchio/program/src/processor.rs | 76 ++++++++++ .../pinocchio/program/src/state/food.rs | 25 ++++ .../pinocchio/program/src/state/game.rs | 31 ++++ .../pinocchio/program/src/state/mod.rs | 3 + .../pinocchio/program/src/state/ride.rs | 35 +++++ .../pinocchio/program/tests/test.rs | 122 ++++++++++++++++ 26 files changed, 890 insertions(+) create mode 100644 basics/cross-program-invocation/pinocchio/README.md create mode 100644 basics/cross-program-invocation/pinocchio/package.json create mode 100644 basics/cross-program-invocation/pinocchio/pnpm-lock.yaml create mode 100644 basics/cross-program-invocation/pinocchio/programs/hand/Cargo.toml create mode 100644 basics/cross-program-invocation/pinocchio/programs/hand/src/lib.rs create mode 100644 basics/cross-program-invocation/pinocchio/programs/lever/Cargo.toml create mode 100644 basics/cross-program-invocation/pinocchio/programs/lever/src/lib.rs create mode 100644 basics/cross-program-invocation/pinocchio/programs/lever/tests/test.rs create mode 100644 basics/repository-layout/pinocchio/README.md create mode 100644 basics/repository-layout/pinocchio/package.json create mode 100644 basics/repository-layout/pinocchio/pnpm-lock.yaml create mode 100644 basics/repository-layout/pinocchio/program/Cargo.toml create mode 100644 basics/repository-layout/pinocchio/program/src/error.rs create mode 100644 basics/repository-layout/pinocchio/program/src/instructions/eat_food.rs create mode 100644 basics/repository-layout/pinocchio/program/src/instructions/get_on_ride.rs create mode 100644 basics/repository-layout/pinocchio/program/src/instructions/mod.rs create mode 100644 basics/repository-layout/pinocchio/program/src/instructions/play_game.rs create mode 100644 basics/repository-layout/pinocchio/program/src/lib.rs create mode 100644 basics/repository-layout/pinocchio/program/src/processor.rs create mode 100644 basics/repository-layout/pinocchio/program/src/state/food.rs create mode 100644 basics/repository-layout/pinocchio/program/src/state/game.rs create mode 100644 basics/repository-layout/pinocchio/program/src/state/mod.rs create mode 100644 basics/repository-layout/pinocchio/program/src/state/ride.rs create mode 100644 basics/repository-layout/pinocchio/program/tests/test.rs diff --git a/Cargo.lock b/Cargo.lock index 2a68a992..082fad85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1285,6 +1285,29 @@ dependencies = [ "spl-token-interface", ] +[[package]] +name = "cross-program-invocation-pinocchio-hand" +version = "0.1.0" +dependencies = [ + "pinocchio 0.10.2", +] + +[[package]] +name = "cross-program-invocation-pinocchio-lever" +version = "0.1.0" +dependencies = [ + "litesvm", + "pinocchio 0.10.2", + "pinocchio-log", + "pinocchio-system", + "solana-instruction 3.3.0", + "solana-keypair", + "solana-native-token 3.0.0", + "solana-pubkey 3.0.0", + "solana-system-interface 2.0.0", + "solana-transaction 3.1.0", +] + [[package]] name = "crunchy" version = "0.2.4" @@ -2958,6 +2981,20 @@ dependencies = [ "solana-transaction 3.1.0", ] +[[package]] +name = "repository-layout-pinocchio-program" +version = "0.1.0" +dependencies = [ + "litesvm", + "pinocchio 0.10.2", + "pinocchio-log", + "solana-instruction 3.3.0", + "solana-keypair", + "solana-native-token 3.0.0", + "solana-pubkey 3.0.0", + "solana-transaction 3.1.0", +] + [[package]] name = "repository-layout-program" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 81c2df58..fa1830f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,8 @@ members = [ "basics/create-account/anchor/programs/create-system-account", "basics/create-account/asm", "basics/cross-program-invocation/anchor/programs/*", + "basics/cross-program-invocation/pinocchio/programs/hand", + "basics/cross-program-invocation/pinocchio/programs/lever", "basics/hello-solana/native/program", "basics/hello-solana/anchor/programs/*", "basics/hello-solana/pinocchio/program", @@ -41,6 +43,7 @@ members = [ "basics/favorites/native/program", "basics/favorites/pinocchio/program", "basics/repository-layout/native/program", + "basics/repository-layout/pinocchio/program", "basics/repository-layout/anchor/programs/*", "basics/transfer-sol/native/program", "basics/transfer-sol/pinocchio/program", diff --git a/basics/cross-program-invocation/pinocchio/README.md b/basics/cross-program-invocation/pinocchio/README.md new file mode 100644 index 00000000..029912dc --- /dev/null +++ b/basics/cross-program-invocation/pinocchio/README.md @@ -0,0 +1,21 @@ +# Cross Program Invocation: Solana Pinocchio + +A [Cross Program Invocation (CPI)](https://solana.com/docs/core/cpi) example written using the [Pinocchio](https://github.com/anza-xyz/pinocchio) framework with only the Solana toolchain. + +Two programs work together: + +- `lever` - owns a `power` account that stores a single on/off byte. It exposes `initialize` (create the account) and `switch_power` (flip the byte and log who pulled it). +- `hand` - takes a name, then invokes `lever`'s `switch_power` instruction via CPI, forwarding the name. + +Because Pinocchio runs in `no_std` without an allocator, `hand` builds the CPI instruction buffer on the stack and caps the forwarded name length. + +## Setup + +1. Build both [programs](https://solana.com/docs/terminology#program): `pnpm build` +2. Build and test: `pnpm build-and-test` + +The tests live in `programs/lever/tests/test.rs` and run on-chain in [LiteSVM](https://github.com/LiteSVM/litesvm) - there are no JavaScript tests. They exercise the full `initialize -> pull -> pull-again` CPI flow plus an invalid-discriminator rejection case. `pnpm build` compiles both programs to `tests/fixtures` so the LiteSVM test can load them. + +## Credits + +Ported from the [Pinocchio cross-program-invocation example](https://github.com/solana-developers/program-examples/pull/584) contributed by [@MarkFeder](https://github.com/MarkFeder) to solana-developers/program-examples. diff --git a/basics/cross-program-invocation/pinocchio/package.json b/basics/cross-program-invocation/pinocchio/package.json new file mode 100644 index 00000000..077016f2 --- /dev/null +++ b/basics/cross-program-invocation/pinocchio/package.json @@ -0,0 +1,8 @@ +{ + "type": "module", + "scripts": { + "build": "cargo build-sbf --manifest-path=./programs/hand/Cargo.toml --sbf-out-dir=./tests/fixtures && cargo build-sbf --manifest-path=./programs/lever/Cargo.toml --sbf-out-dir=./tests/fixtures", + "build-and-test": "pnpm build && cargo test --manifest-path=./programs/lever/Cargo.toml", + "test": "cargo test --manifest-path=./programs/lever/Cargo.toml" + } +} diff --git a/basics/cross-program-invocation/pinocchio/pnpm-lock.yaml b/basics/cross-program-invocation/pinocchio/pnpm-lock.yaml new file mode 100644 index 00000000..9b60ae17 --- /dev/null +++ b/basics/cross-program-invocation/pinocchio/pnpm-lock.yaml @@ -0,0 +1,9 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} diff --git a/basics/cross-program-invocation/pinocchio/programs/hand/Cargo.toml b/basics/cross-program-invocation/pinocchio/programs/hand/Cargo.toml new file mode 100644 index 00000000..d4f80375 --- /dev/null +++ b/basics/cross-program-invocation/pinocchio/programs/hand/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "cross-program-invocation-pinocchio-hand" +version = "0.1.0" +edition = "2021" + +[dependencies] +pinocchio.workspace = true + +[lib] +name = "cross_program_invocation_pinocchio_hand" +crate-type = ["cdylib", "lib"] + +[features] +custom-heap = [] +custom-panic = [] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/basics/cross-program-invocation/pinocchio/programs/hand/src/lib.rs b/basics/cross-program-invocation/pinocchio/programs/hand/src/lib.rs new file mode 100644 index 00000000..23ece5e1 --- /dev/null +++ b/basics/cross-program-invocation/pinocchio/programs/hand/src/lib.rs @@ -0,0 +1,48 @@ +#![no_std] + +use pinocchio::{ + cpi::invoke, + entrypoint, + error::ProgramError, + instruction::{InstructionAccount, InstructionView}, + nostd_panic_handler, AccountView, Address, ProgramResult, +}; + +entrypoint!(process_instruction); +nostd_panic_handler!(); + +// Matches lever's switch_power discriminator. +const LEVER_IX_SWITCH_POWER: u8 = 1; + +// Cap the forwarded name length so we can build the CPI buffer on the stack +// (pinocchio runs in `no_std` without an allocator by default). +const MAX_NAME_LEN: usize = 128; +const CPI_DATA_BUF: usize = MAX_NAME_LEN + 1; + +fn process_instruction( + _program_id: &Address, + accounts: &[AccountView], + instruction_data: &[u8], +) -> ProgramResult { + let [power, lever_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + let name = instruction_data; + if name.len() > MAX_NAME_LEN { + return Err(ProgramError::InvalidInstructionData); + } + + let mut cpi_data = [0u8; CPI_DATA_BUF]; + cpi_data[0] = LEVER_IX_SWITCH_POWER; + cpi_data[1..1 + name.len()].copy_from_slice(name); + + let metas = [InstructionAccount::writable(power.address())]; + let ix = InstructionView { + program_id: lever_program.address(), + accounts: &metas, + data: &cpi_data[..1 + name.len()], + }; + + invoke::<1>(&ix, &[power]) +} diff --git a/basics/cross-program-invocation/pinocchio/programs/lever/Cargo.toml b/basics/cross-program-invocation/pinocchio/programs/lever/Cargo.toml new file mode 100644 index 00000000..a4e4a50d --- /dev/null +++ b/basics/cross-program-invocation/pinocchio/programs/lever/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "cross-program-invocation-pinocchio-lever" +version = "0.1.0" +edition = "2021" + +[dependencies] +pinocchio.workspace = true +pinocchio-log.workspace = true +pinocchio-system.workspace = true + +[lib] +name = "cross_program_invocation_pinocchio_lever" +crate-type = ["cdylib", "lib"] + +[features] +custom-heap = [] +custom-panic = [] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } + +[dev-dependencies] +litesvm = "0.11.0" +solana-instruction = "3.0.0" +solana-keypair = "3.0.1" +solana-native-token = "3.0.0" +solana-pubkey = "3.0.0" +solana-transaction = "3.0.1" +solana-system-interface.workspace = true diff --git a/basics/cross-program-invocation/pinocchio/programs/lever/src/lib.rs b/basics/cross-program-invocation/pinocchio/programs/lever/src/lib.rs new file mode 100644 index 00000000..08106035 --- /dev/null +++ b/basics/cross-program-invocation/pinocchio/programs/lever/src/lib.rs @@ -0,0 +1,76 @@ +#![no_std] + +use pinocchio::{ + entrypoint, + error::ProgramError, + nostd_panic_handler, + sysvars::{rent::Rent, Sysvar}, + AccountView, Address, ProgramResult, +}; +use pinocchio_log::log; +use pinocchio_system::instructions::CreateAccount; + +entrypoint!(process_instruction); +nostd_panic_handler!(); + +// Single-byte account: stores `is_on` as 0 or 1. +const POWER_ACCOUNT_SPACE: u64 = 1; + +// Instruction discriminators +const IX_INITIALIZE: u8 = 0; +const IX_SWITCH_POWER: u8 = 1; + +fn process_instruction( + program_id: &Address, + accounts: &[AccountView], + instruction_data: &[u8], +) -> ProgramResult { + match instruction_data.split_first() { + Some((&IX_INITIALIZE, _)) => initialize(program_id, accounts), + Some((&IX_SWITCH_POWER, name)) => switch_power(accounts, name), + _ => Err(ProgramError::InvalidInstructionData), + } +} + +fn initialize(program_id: &Address, accounts: &[AccountView]) -> ProgramResult { + let [power, user, _system_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + let lamports = Rent::get()?.try_minimum_balance(POWER_ACCOUNT_SPACE as usize)?; + + CreateAccount { + from: user, + to: power, + lamports, + space: POWER_ACCOUNT_SPACE, + owner: program_id, + } + .invoke()?; + + let mut data = power.try_borrow_mut()?; + data[0] = 0; + Ok(()) +} + +fn switch_power(accounts: &[AccountView], name: &[u8]) -> ProgramResult { + let [power] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + let mut data = power.try_borrow_mut()?; + data[0] = if data[0] == 0 { 1 } else { 0 }; + let is_on = data[0] == 1; + drop(data); + + let name_str = core::str::from_utf8(name).map_err(|_| ProgramError::InvalidInstructionData)?; + log!("{} is pulling the power switch!", name_str); + + if is_on { + log!("The power is now on."); + } else { + log!("The power is now off!"); + } + + Ok(()) +} diff --git a/basics/cross-program-invocation/pinocchio/programs/lever/tests/test.rs b/basics/cross-program-invocation/pinocchio/programs/lever/tests/test.rs new file mode 100644 index 00000000..ade87ac8 --- /dev/null +++ b/basics/cross-program-invocation/pinocchio/programs/lever/tests/test.rs @@ -0,0 +1,136 @@ +use litesvm::LiteSVM; +use solana_instruction::{AccountMeta, Instruction}; +use solana_keypair::{Keypair, Signer}; +use solana_native_token::LAMPORTS_PER_SOL; +use solana_pubkey::Pubkey; +use solana_transaction::Transaction; + +// Both .so files are built into ../../../tests/fixtures (the example root) by +// `pnpm build` / `pnpm build-and-test`, which run `cargo build-sbf` for each +// program with --sbf-out-dir set there. Run that before `cargo test`. +const HAND_SO: &[u8] = + include_bytes!("../../../tests/fixtures/cross_program_invocation_pinocchio_hand.so"); +const LEVER_SO: &[u8] = + include_bytes!("../../../tests/fixtures/cross_program_invocation_pinocchio_lever.so"); + +// Lever instruction discriminators. +const IX_INITIALIZE: u8 = 0; + +fn setup() -> (LiteSVM, Pubkey, Pubkey, Keypair) { + let hand_id = Pubkey::new_unique(); + let lever_id = Pubkey::new_unique(); + + let mut svm = LiteSVM::new(); + svm.add_program(hand_id, HAND_SO).unwrap(); + svm.add_program(lever_id, LEVER_SO).unwrap(); + + let payer = Keypair::new(); + svm.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL * 10).unwrap(); + + (svm, hand_id, lever_id, payer) +} + +// Calls lever's `initialize`, which creates the single-byte power account under +// the lever program via a CPI to the System Program. +fn initialize(svm: &mut LiteSVM, lever_id: Pubkey, power: &Keypair, payer: &Keypair) { + let ix = Instruction { + program_id: lever_id, + accounts: vec![ + AccountMeta::new(power.pubkey(), true), + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + data: vec![IX_INITIALIZE], + }; + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[payer, power], + svm.latest_blockhash(), + ); + svm.send_transaction(tx).unwrap(); +} + +// Calls hand, which forwards `switch_power(name)` to lever over a CPI. +fn pull_lever( + svm: &mut LiteSVM, + hand_id: Pubkey, + lever_id: Pubkey, + power: Pubkey, + payer: &Keypair, + name: &str, +) { + let ix = Instruction { + program_id: hand_id, + accounts: vec![ + AccountMeta::new(power, false), + AccountMeta::new_readonly(lever_id, false), + ], + data: name.as_bytes().to_vec(), + }; + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[payer], + svm.latest_blockhash(), + ); + svm.send_transaction(tx).unwrap(); +} + +fn power_byte(svm: &LiteSVM, power: Pubkey) -> u8 { + svm.get_account(&power).unwrap().data[0] +} + +#[test] +fn test_cpi_toggles_power() { + let (mut svm, hand_id, lever_id, payer) = setup(); + let power = Keypair::new(); + + initialize(&mut svm, lever_id, &power, &payer); + assert_eq!(power_byte(&svm, power.pubkey()), 0, "power starts off"); + + pull_lever(&mut svm, hand_id, lever_id, power.pubkey(), &payer, "Chris"); + assert_eq!( + power_byte(&svm, power.pubkey()), + 1, + "power on after first pull" + ); + + pull_lever( + &mut svm, + hand_id, + lever_id, + power.pubkey(), + &payer, + "Ashley", + ); + assert_eq!( + power_byte(&svm, power.pubkey()), + 0, + "power off after second pull" + ); +} + +#[test] +fn test_lever_rejects_unknown_discriminator() { + let (mut svm, _hand_id, lever_id, payer) = setup(); + let power = Keypair::new(); + + initialize(&mut svm, lever_id, &power, &payer); + + let ix = Instruction { + program_id: lever_id, + accounts: vec![AccountMeta::new(power.pubkey(), false)], + data: vec![42], + }; + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer], + svm.latest_blockhash(), + ); + assert!( + svm.send_transaction(tx).is_err(), + "unknown discriminator must fail" + ); +} diff --git a/basics/repository-layout/pinocchio/README.md b/basics/repository-layout/pinocchio/README.md new file mode 100644 index 00000000..517c7c10 --- /dev/null +++ b/basics/repository-layout/pinocchio/README.md @@ -0,0 +1,26 @@ +# Recommended Program Layout: Solana Pinocchio + +The recommended multi-file layout for a Solana [program](https://solana.com/docs/terminology#program), written using the [Pinocchio](https://github.com/anza-xyz/pinocchio) framework with only the Solana toolchain. + +The `src` folder splits responsibilities the same way the `native` and `anchor` examples do: + +- `lib.rs` - module declarations and the program entrypoint +- `processor.rs` - decodes instruction data and dispatches to the right instruction +- `instructions/` - one file per instruction (`get_on_ride`, `play_game`, `eat_food`) +- `state/` - the program's data objects (`ride`, `game`, `food`) +- `error.rs` - custom errors + +## Setup + +1. Build the [program](https://solana.com/docs/terminology#program): `pnpm build` +2. Build and test: `pnpm build-and-test` then `pnpm test` + +The tests live in `program/tests/test.rs` and run on-chain in [LiteSVM](https://github.com/LiteSVM/litesvm) - there are no JavaScript tests. `pnpm build` / `pnpm build-and-test` compile the program to `tests/fixtures` so the LiteSVM test can load it. + +## Deploy + +`pnpm build` then `pnpm deploy` + +## Credits + +Ported from the [Pinocchio repository-layout example](https://github.com/solana-developers/program-examples/pull/582) contributed by [@MarkFeder](https://github.com/MarkFeder) to solana-developers/program-examples. diff --git a/basics/repository-layout/pinocchio/package.json b/basics/repository-layout/pinocchio/package.json new file mode 100644 index 00000000..e1d6f811 --- /dev/null +++ b/basics/repository-layout/pinocchio/package.json @@ -0,0 +1,9 @@ +{ + "type": "module", + "scripts": { + "build": "cargo build-sbf --manifest-path=./program/Cargo.toml --sbf-out-dir=./tests/fixtures", + "build-and-test": "cargo build-sbf --manifest-path=./program/Cargo.toml --sbf-out-dir=./tests/fixtures", + "test": "cargo test --manifest-path=./program/Cargo.toml", + "deploy": "solana program deploy ./tests/fixtures/repository_layout_pinocchio_program.so" + } +} diff --git a/basics/repository-layout/pinocchio/pnpm-lock.yaml b/basics/repository-layout/pinocchio/pnpm-lock.yaml new file mode 100644 index 00000000..9b60ae17 --- /dev/null +++ b/basics/repository-layout/pinocchio/pnpm-lock.yaml @@ -0,0 +1,9 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} diff --git a/basics/repository-layout/pinocchio/program/Cargo.toml b/basics/repository-layout/pinocchio/program/Cargo.toml new file mode 100644 index 00000000..2f6424aa --- /dev/null +++ b/basics/repository-layout/pinocchio/program/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "repository-layout-pinocchio-program" +version = "0.1.0" +edition = "2021" + +[dependencies] +pinocchio.workspace = true +pinocchio-log.workspace = true + +[lib] +crate-type = ["cdylib", "lib"] + +[features] +custom-heap = [] +custom-panic = [] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } + +[dev-dependencies] +litesvm = "0.11.0" +solana-instruction = "3.0.0" +solana-keypair = "3.0.1" +solana-pubkey = "3.0.0" +solana-transaction = "3.0.1" +solana-native-token = "3.0.0" diff --git a/basics/repository-layout/pinocchio/program/src/error.rs b/basics/repository-layout/pinocchio/program/src/error.rs new file mode 100644 index 00000000..9db2fad4 --- /dev/null +++ b/basics/repository-layout/pinocchio/program/src/error.rs @@ -0,0 +1 @@ +// For any custom errors diff --git a/basics/repository-layout/pinocchio/program/src/instructions/eat_food.rs b/basics/repository-layout/pinocchio/program/src/instructions/eat_food.rs new file mode 100644 index 00000000..f435e828 --- /dev/null +++ b/basics/repository-layout/pinocchio/program/src/instructions/eat_food.rs @@ -0,0 +1,35 @@ +use pinocchio::{error::ProgramError, ProgramResult}; +use pinocchio_log::log; + +use crate::state::food; + +// InstructionData Data + +pub struct EatFoodInstructionData<'a> { + pub eater_name: &'a str, + pub eater_ticket_count: u32, + pub food_stand: &'a str, +} + +pub fn eat_food(ix: EatFoodInstructionData) -> ProgramResult { + for food_stand in food::FOOD_STANDS.iter() { + if ix.food_stand == food_stand.name { + log!("Welcome to {}! What can I get you?", food_stand.name); + + if ix.eater_ticket_count < food_stand.tickets { + log!( + " Sorry {}, our {} is {} tickets!", + ix.eater_name, + food_stand.food_type, + food_stand.tickets + ); + } else { + log!(" Enjoy your {}!", food_stand.food_type); + }; + + return Ok(()); + } + } + + Err(ProgramError::InvalidInstructionData) +} diff --git a/basics/repository-layout/pinocchio/program/src/instructions/get_on_ride.rs b/basics/repository-layout/pinocchio/program/src/instructions/get_on_ride.rs new file mode 100644 index 00000000..a8946eab --- /dev/null +++ b/basics/repository-layout/pinocchio/program/src/instructions/get_on_ride.rs @@ -0,0 +1,51 @@ +use pinocchio::{error::ProgramError, ProgramResult}; +use pinocchio_log::log; + +use crate::state::ride; + +// InstructionData Data + +pub struct GetOnRideInstructionData<'a> { + pub rider_name: &'a str, + pub rider_height: u32, + pub rider_ticket_count: u32, + pub ride: &'a str, +} + +pub fn get_on_ride(ix: GetOnRideInstructionData) -> ProgramResult { + for ride in ride::RIDES.iter() { + if ix.ride == ride.name { + log!("You're about to ride the {}!", ride.name); + + if ix.rider_ticket_count < ride.tickets { + log!( + " Sorry {}, you need {} tickets to ride the {}!", + ix.rider_name, + ride.tickets, + ride.name + ); + return Ok(()); + }; + + if ix.rider_height < ride.min_height { + log!( + " Sorry {}, you need to be {} tall to ride the {}!", + ix.rider_name, + ride.min_height, + ride.name + ); + return Ok(()); + }; + + log!(" Welcome aboard the {}!", ride.name); + + if ride.upside_down { + log!(" Btw, this ride goes upside down. Hold on tight!"); + }; + + return Ok(()); + } + } + + Err(ProgramError::InvalidInstructionData) +} diff --git a/basics/repository-layout/pinocchio/program/src/instructions/mod.rs b/basics/repository-layout/pinocchio/program/src/instructions/mod.rs new file mode 100644 index 00000000..ee6ea6d1 --- /dev/null +++ b/basics/repository-layout/pinocchio/program/src/instructions/mod.rs @@ -0,0 +1,3 @@ +pub mod eat_food; +pub mod get_on_ride; +pub mod play_game; diff --git a/basics/repository-layout/pinocchio/program/src/instructions/play_game.rs b/basics/repository-layout/pinocchio/program/src/instructions/play_game.rs new file mode 100644 index 00000000..132fb09f --- /dev/null +++ b/basics/repository-layout/pinocchio/program/src/instructions/play_game.rs @@ -0,0 +1,40 @@ +use pinocchio::{error::ProgramError, ProgramResult}; +use pinocchio_log::log; + +use crate::state::game; + +// InstructionData Data + +pub struct PlayGameInstructionData<'a> { + pub gamer_name: &'a str, + pub gamer_ticket_count: u32, + pub game: &'a str, +} + +pub fn play_game(ix: PlayGameInstructionData) -> ProgramResult { + for game in game::GAMES.iter() { + if ix.game == game.name { + log!("You're about to play {}!", game.name); + + if ix.gamer_ticket_count < game.tickets { + log!( + " Sorry {}, you need {} tickets to play {}!", + ix.gamer_name, + game.tickets, + game.name + ); + } else { + log!(" Let's see what you got!"); + log!( + " You get {} attempts and the prize is a {}!", + game.tries, + game.prize + ); + }; + + return Ok(()); + } + } + + Err(ProgramError::InvalidInstructionData) +} diff --git a/basics/repository-layout/pinocchio/program/src/lib.rs b/basics/repository-layout/pinocchio/program/src/lib.rs new file mode 100644 index 00000000..adf936c0 --- /dev/null +++ b/basics/repository-layout/pinocchio/program/src/lib.rs @@ -0,0 +1,13 @@ +#![no_std] + +// For setting up modules & configs + +pub mod error; +pub mod instructions; +pub mod processor; +pub mod state; + +use pinocchio::{entrypoint, nostd_panic_handler}; + +entrypoint!(processor::process_instruction); +nostd_panic_handler!(); diff --git a/basics/repository-layout/pinocchio/program/src/processor.rs b/basics/repository-layout/pinocchio/program/src/processor.rs new file mode 100644 index 00000000..2529de20 --- /dev/null +++ b/basics/repository-layout/pinocchio/program/src/processor.rs @@ -0,0 +1,76 @@ +use pinocchio::{error::ProgramError, AccountView, Address, ProgramResult}; +use pinocchio_log::log; + +use crate::instructions::{eat_food, get_on_ride, play_game}; + +// For processing everything at the entrypoint +// +// Instruction data layout (matches the native borsh layout): +// - name: u32 LE length + utf-8 bytes +// - height: u32 LE +// - ticket_count: u32 LE +// - attraction: u32 LE length + utf-8 bytes ("ride" | "game" | "food") +// - attraction_name: u32 LE length + utf-8 bytes + +pub fn process_instruction( + _program_id: &Address, + _accounts: &[AccountView], + instruction_data: &[u8], +) -> ProgramResult { + let mut cursor = 0; + let name = read_str(instruction_data, &mut cursor)?; + let height = read_u32(instruction_data, &mut cursor)?; + let ticket_count = read_u32(instruction_data, &mut cursor)?; + let attraction = read_str(instruction_data, &mut cursor)?; + let attraction_name = read_str(instruction_data, &mut cursor)?; + + log!("Welcome to the carnival, {}!", name); + + match attraction { + "ride" => get_on_ride::get_on_ride(get_on_ride::GetOnRideInstructionData { + rider_name: name, + rider_height: height, + rider_ticket_count: ticket_count, + ride: attraction_name, + }), + "game" => play_game::play_game(play_game::PlayGameInstructionData { + gamer_name: name, + gamer_ticket_count: ticket_count, + game: attraction_name, + }), + "food" => eat_food::eat_food(eat_food::EatFoodInstructionData { + eater_name: name, + eater_ticket_count: ticket_count, + food_stand: attraction_name, + }), + _ => Err(ProgramError::InvalidInstructionData), + } +} + +fn read_u32(data: &[u8], cursor: &mut usize) -> Result { + let end = cursor + .checked_add(4) + .ok_or(ProgramError::InvalidInstructionData)?; + if end > data.len() { + return Err(ProgramError::InvalidInstructionData); + } + let bytes: [u8; 4] = data[*cursor..end] + .try_into() + .map_err(|_| ProgramError::InvalidInstructionData)?; + *cursor = end; + Ok(u32::from_le_bytes(bytes)) +} + +fn read_str<'a>(data: &'a [u8], cursor: &mut usize) -> Result<&'a str, ProgramError> { + let len = read_u32(data, cursor)? as usize; + let end = cursor + .checked_add(len) + .ok_or(ProgramError::InvalidInstructionData)?; + if end > data.len() { + return Err(ProgramError::InvalidInstructionData); + } + let s = core::str::from_utf8(&data[*cursor..end]) + .map_err(|_| ProgramError::InvalidInstructionData)?; + *cursor = end; + Ok(s) +} diff --git a/basics/repository-layout/pinocchio/program/src/state/food.rs b/basics/repository-layout/pinocchio/program/src/state/food.rs new file mode 100644 index 00000000..ecf1313f --- /dev/null +++ b/basics/repository-layout/pinocchio/program/src/state/food.rs @@ -0,0 +1,25 @@ +// Objects + +pub struct FoodStand { + pub name: &'static str, + pub food_type: &'static str, + pub tickets: u32, +} + +pub const FOOD_STANDS: &[FoodStand] = &[ + FoodStand { + name: "Larry's Pizza", + food_type: "pizza", + tickets: 3, + }, + FoodStand { + name: "Taco Shack", + food_type: "taco", + tickets: 2, + }, + FoodStand { + name: "Dough Boy's", + food_type: "fried dough", + tickets: 1, + }, +]; diff --git a/basics/repository-layout/pinocchio/program/src/state/game.rs b/basics/repository-layout/pinocchio/program/src/state/game.rs new file mode 100644 index 00000000..2f1e6423 --- /dev/null +++ b/basics/repository-layout/pinocchio/program/src/state/game.rs @@ -0,0 +1,31 @@ +// Objects + +pub struct Game { + pub name: &'static str, + pub tickets: u32, + pub tries: u32, + pub prize: &'static str, +} + +const DEFAULT_TICKETS_TO_PLAY: u32 = 3; + +pub const GAMES: &[Game] = &[ + Game { + name: "Ring Toss", + tickets: DEFAULT_TICKETS_TO_PLAY, + tries: 5, + prize: "teddy bear", + }, + Game { + name: "I Got It!", + tickets: DEFAULT_TICKETS_TO_PLAY, + tries: 12, + prize: "goldfish", + }, + Game { + name: "Ladder Climb", + tickets: DEFAULT_TICKETS_TO_PLAY, + tries: 1, + prize: "popcorn bucket", + }, +]; diff --git a/basics/repository-layout/pinocchio/program/src/state/mod.rs b/basics/repository-layout/pinocchio/program/src/state/mod.rs new file mode 100644 index 00000000..12a92a2c --- /dev/null +++ b/basics/repository-layout/pinocchio/program/src/state/mod.rs @@ -0,0 +1,3 @@ +pub mod food; +pub mod game; +pub mod ride; diff --git a/basics/repository-layout/pinocchio/program/src/state/ride.rs b/basics/repository-layout/pinocchio/program/src/state/ride.rs new file mode 100644 index 00000000..2746d80e --- /dev/null +++ b/basics/repository-layout/pinocchio/program/src/state/ride.rs @@ -0,0 +1,35 @@ +// Objects + +pub struct Ride { + pub name: &'static str, + pub upside_down: bool, + pub tickets: u32, + pub min_height: u32, +} + +pub const RIDES: &[Ride] = &[ + Ride { + name: "Tilt-a-Whirl", + upside_down: false, + tickets: 3, + min_height: 48, + }, + Ride { + name: "Scrambler", + upside_down: false, + tickets: 3, + min_height: 48, + }, + Ride { + name: "Ferris Wheel", + upside_down: false, + tickets: 5, + min_height: 55, + }, + Ride { + name: "Zero Gravity", + upside_down: true, + tickets: 5, + min_height: 60, + }, +]; diff --git a/basics/repository-layout/pinocchio/program/tests/test.rs b/basics/repository-layout/pinocchio/program/tests/test.rs new file mode 100644 index 00000000..d4a19613 --- /dev/null +++ b/basics/repository-layout/pinocchio/program/tests/test.rs @@ -0,0 +1,122 @@ +use litesvm::LiteSVM; +use solana_instruction::{AccountMeta, Instruction}; +use solana_keypair::{Keypair, Signer}; +use solana_native_token::LAMPORTS_PER_SOL; +use solana_pubkey::Pubkey; +use solana_transaction::Transaction; + +// The .so is built into ../../tests/fixtures by `pnpm build-and-test` (which runs +// `cargo build-sbf --sbf-out-dir=./tests/fixtures` from the package root). Run +// that script (or `cargo build-sbf` with --sbf-out-dir set accordingly) before +// `cargo test`. +const PROGRAM_SO: &[u8] = + include_bytes!("../../tests/fixtures/repository_layout_pinocchio_program.so"); + +// Builds the carnival instruction data in the wire format the program decodes: +// name (str), height (u32), ticket_count (u32), attraction (str), attraction_name (str) +// where each str is a u32 LE length followed by its utf-8 bytes. +fn carnival_ix_data( + name: &str, + height: u32, + ticket_count: u32, + attraction: &str, + attraction_name: &str, +) -> Vec { + fn push_str(buf: &mut Vec, s: &str) { + buf.extend_from_slice(&(s.len() as u32).to_le_bytes()); + buf.extend_from_slice(s.as_bytes()); + } + + let mut data = Vec::new(); + push_str(&mut data, name); + data.extend_from_slice(&height.to_le_bytes()); + data.extend_from_slice(&ticket_count.to_le_bytes()); + push_str(&mut data, attraction); + push_str(&mut data, attraction_name); + data +} + +fn setup() -> (LiteSVM, Pubkey, Keypair) { + let program_id = Pubkey::new_unique(); + + let mut svm = LiteSVM::new(); + svm.add_program(program_id, PROGRAM_SO).unwrap(); + + let payer = Keypair::new(); + svm.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL * 10).unwrap(); + + (svm, program_id, payer) +} + +// The program ignores accounts entirely, so a single signer is all we need. +fn send_carnival(svm: &mut LiteSVM, program_id: Pubkey, payer: &Keypair, data: Vec) -> bool { + let ix = Instruction { + program_id, + accounts: vec![AccountMeta::new(payer.pubkey(), true)], + data, + }; + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[payer], + svm.latest_blockhash(), + ); + svm.send_transaction(tx).is_ok() +} + +#[test] +fn test_go_on_a_ride() { + let (mut svm, program_id, payer) = setup(); + assert!(send_carnival( + &mut svm, + program_id, + &payer, + carnival_ix_data("Alice", 56, 15, "ride", "Scrambler") + )); +} + +#[test] +fn test_play_a_game() { + let (mut svm, program_id, payer) = setup(); + assert!(send_carnival( + &mut svm, + program_id, + &payer, + carnival_ix_data("Bob", 49, 6, "game", "Ring Toss") + )); +} + +#[test] +fn test_eat_some_food() { + let (mut svm, program_id, payer) = setup(); + assert!(send_carnival( + &mut svm, + program_id, + &payer, + carnival_ix_data("Mary", 52, 3, "food", "Taco Shack") + )); +} + +#[test] +fn test_unknown_attraction_name_fails() { + let (mut svm, program_id, payer) = setup(); + // "ride" is a valid attraction type, but there is no ride by this name, so the + // program falls through its lookup table and returns InvalidInstructionData. + assert!(!send_carnival( + &mut svm, + program_id, + &payer, + carnival_ix_data("Jimmy", 40, 5, "ride", "Roller Coaster") + )); +} + +#[test] +fn test_unknown_attraction_type_fails() { + let (mut svm, program_id, payer) = setup(); + assert!(!send_carnival( + &mut svm, + program_id, + &payer, + carnival_ix_data("Jimmy", 40, 5, "spaceship", "Apollo") + )); +} From 079dc320f18cf98e608e88bc3b57f40f566c453d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 12 Jun 2026 19:33:15 +0000 Subject: [PATCH 2/2] test(basics): align Pinocchio examples with target/deploy LiteSVM convention Merge in main's Rust-only Pinocchio test convention (PR #69) and adopt it for the two new examples: - Drop package.json / pnpm-lock.yaml (no JS); examples build with `cargo build-sbf` and test with `cargo test`. - include_bytes! the program .so from the workspace target/deploy instead of tests/fixtures, matching the CI that builds with `cargo build-sbf --manifest-path=` (no --sbf-out-dir). - Update READMEs to the cargo build-sbf / cargo test workflow. --- Cargo.lock | 4 ++-- basics/cross-program-invocation/pinocchio/README.md | 8 +++++--- .../cross-program-invocation/pinocchio/package.json | 8 -------- .../cross-program-invocation/pinocchio/pnpm-lock.yaml | 9 --------- .../pinocchio/programs/lever/tests/test.rs | 11 ++++++----- basics/repository-layout/pinocchio/README.md | 10 +++------- basics/repository-layout/pinocchio/package.json | 9 --------- basics/repository-layout/pinocchio/pnpm-lock.yaml | 9 --------- .../repository-layout/pinocchio/program/tests/test.rs | 10 +++++----- 9 files changed, 21 insertions(+), 57 deletions(-) delete mode 100644 basics/cross-program-invocation/pinocchio/package.json delete mode 100644 basics/cross-program-invocation/pinocchio/pnpm-lock.yaml delete mode 100644 basics/repository-layout/pinocchio/package.json delete mode 100644 basics/repository-layout/pinocchio/pnpm-lock.yaml diff --git a/Cargo.lock b/Cargo.lock index cc22a5c7..11c8f40c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1305,7 +1305,7 @@ dependencies = [ "solana-native-token 3.0.0", "solana-pubkey 3.0.0", "solana-system-interface 2.0.0", - "solana-transaction 3.1.0", + "solana-transaction", ] [[package]] @@ -2986,7 +2986,7 @@ dependencies = [ "solana-keypair", "solana-native-token 3.0.0", "solana-pubkey 3.0.0", - "solana-transaction 3.1.0", + "solana-transaction", ] [[package]] diff --git a/basics/cross-program-invocation/pinocchio/README.md b/basics/cross-program-invocation/pinocchio/README.md index 029912dc..a8b91013 100644 --- a/basics/cross-program-invocation/pinocchio/README.md +++ b/basics/cross-program-invocation/pinocchio/README.md @@ -11,10 +11,12 @@ Because Pinocchio runs in `no_std` without an allocator, `hand` builds the CPI i ## Setup -1. Build both [programs](https://solana.com/docs/terminology#program): `pnpm build` -2. Build and test: `pnpm build-and-test` +1. Build both [programs](https://solana.com/docs/terminology#program): + - `cargo build-sbf --manifest-path=./programs/hand/Cargo.toml` + - `cargo build-sbf --manifest-path=./programs/lever/Cargo.toml` +2. Run the Rust + LiteSVM tests: `cargo test --manifest-path=./programs/lever/Cargo.toml` -The tests live in `programs/lever/tests/test.rs` and run on-chain in [LiteSVM](https://github.com/LiteSVM/litesvm) - there are no JavaScript tests. They exercise the full `initialize -> pull -> pull-again` CPI flow plus an invalid-discriminator rejection case. `pnpm build` compiles both programs to `tests/fixtures` so the LiteSVM test can load them. +The tests exercise the full `initialize -> pull -> pull-again` CPI flow plus an invalid-discriminator rejection case. Rebuild the programs after every change before re-running the tests: the tests embed each `.so` at compile time, so a stale binary silently tests old code. ## Credits diff --git a/basics/cross-program-invocation/pinocchio/package.json b/basics/cross-program-invocation/pinocchio/package.json deleted file mode 100644 index 077016f2..00000000 --- a/basics/cross-program-invocation/pinocchio/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "type": "module", - "scripts": { - "build": "cargo build-sbf --manifest-path=./programs/hand/Cargo.toml --sbf-out-dir=./tests/fixtures && cargo build-sbf --manifest-path=./programs/lever/Cargo.toml --sbf-out-dir=./tests/fixtures", - "build-and-test": "pnpm build && cargo test --manifest-path=./programs/lever/Cargo.toml", - "test": "cargo test --manifest-path=./programs/lever/Cargo.toml" - } -} diff --git a/basics/cross-program-invocation/pinocchio/pnpm-lock.yaml b/basics/cross-program-invocation/pinocchio/pnpm-lock.yaml deleted file mode 100644 index 9b60ae17..00000000 --- a/basics/cross-program-invocation/pinocchio/pnpm-lock.yaml +++ /dev/null @@ -1,9 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: {} diff --git a/basics/cross-program-invocation/pinocchio/programs/lever/tests/test.rs b/basics/cross-program-invocation/pinocchio/programs/lever/tests/test.rs index ade87ac8..7f7a4c34 100644 --- a/basics/cross-program-invocation/pinocchio/programs/lever/tests/test.rs +++ b/basics/cross-program-invocation/pinocchio/programs/lever/tests/test.rs @@ -5,13 +5,14 @@ use solana_native_token::LAMPORTS_PER_SOL; use solana_pubkey::Pubkey; use solana_transaction::Transaction; -// Both .so files are built into ../../../tests/fixtures (the example root) by -// `pnpm build` / `pnpm build-and-test`, which run `cargo build-sbf` for each -// program with --sbf-out-dir set there. Run that before `cargo test`. +// Both .so files are built into the workspace target/deploy by +// `cargo build-sbf` for each program (run from the project root). Rebuild after +// every program change: the binaries are embedded at test-compile time, so a +// stale .so silently tests old code. const HAND_SO: &[u8] = - include_bytes!("../../../tests/fixtures/cross_program_invocation_pinocchio_hand.so"); + include_bytes!("../../../../../../target/deploy/cross_program_invocation_pinocchio_hand.so"); const LEVER_SO: &[u8] = - include_bytes!("../../../tests/fixtures/cross_program_invocation_pinocchio_lever.so"); + include_bytes!("../../../../../../target/deploy/cross_program_invocation_pinocchio_lever.so"); // Lever instruction discriminators. const IX_INITIALIZE: u8 = 0; diff --git a/basics/repository-layout/pinocchio/README.md b/basics/repository-layout/pinocchio/README.md index 517c7c10..df9728ab 100644 --- a/basics/repository-layout/pinocchio/README.md +++ b/basics/repository-layout/pinocchio/README.md @@ -12,14 +12,10 @@ The `src` folder splits responsibilities the same way the `native` and `anchor` ## Setup -1. Build the [program](https://solana.com/docs/terminology#program): `pnpm build` -2. Build and test: `pnpm build-and-test` then `pnpm test` +1. Build the [program](https://solana.com/docs/terminology#program): `cargo build-sbf --manifest-path=./program/Cargo.toml` +2. Run the Rust + LiteSVM tests: `cargo test --manifest-path=./program/Cargo.toml` -The tests live in `program/tests/test.rs` and run on-chain in [LiteSVM](https://github.com/LiteSVM/litesvm) - there are no JavaScript tests. `pnpm build` / `pnpm build-and-test` compile the program to `tests/fixtures` so the LiteSVM test can load it. - -## Deploy - -`pnpm build` then `pnpm deploy` +Rebuild the program after every change before re-running the tests: the tests embed the `.so` at compile time, so a stale binary silently tests old code. ## Credits diff --git a/basics/repository-layout/pinocchio/package.json b/basics/repository-layout/pinocchio/package.json deleted file mode 100644 index e1d6f811..00000000 --- a/basics/repository-layout/pinocchio/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "type": "module", - "scripts": { - "build": "cargo build-sbf --manifest-path=./program/Cargo.toml --sbf-out-dir=./tests/fixtures", - "build-and-test": "cargo build-sbf --manifest-path=./program/Cargo.toml --sbf-out-dir=./tests/fixtures", - "test": "cargo test --manifest-path=./program/Cargo.toml", - "deploy": "solana program deploy ./tests/fixtures/repository_layout_pinocchio_program.so" - } -} diff --git a/basics/repository-layout/pinocchio/pnpm-lock.yaml b/basics/repository-layout/pinocchio/pnpm-lock.yaml deleted file mode 100644 index 9b60ae17..00000000 --- a/basics/repository-layout/pinocchio/pnpm-lock.yaml +++ /dev/null @@ -1,9 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: {} diff --git a/basics/repository-layout/pinocchio/program/tests/test.rs b/basics/repository-layout/pinocchio/program/tests/test.rs index d4a19613..bf10fd80 100644 --- a/basics/repository-layout/pinocchio/program/tests/test.rs +++ b/basics/repository-layout/pinocchio/program/tests/test.rs @@ -5,12 +5,12 @@ use solana_native_token::LAMPORTS_PER_SOL; use solana_pubkey::Pubkey; use solana_transaction::Transaction; -// The .so is built into ../../tests/fixtures by `pnpm build-and-test` (which runs -// `cargo build-sbf --sbf-out-dir=./tests/fixtures` from the package root). Run -// that script (or `cargo build-sbf` with --sbf-out-dir set accordingly) before -// `cargo test`. +// The .so is built into the workspace target/deploy by +// `cargo build-sbf --manifest-path=./program/Cargo.toml` (run from the project +// root). Rebuild after every program change: the binary is embedded at +// test-compile time, so a stale .so silently tests old code. const PROGRAM_SO: &[u8] = - include_bytes!("../../tests/fixtures/repository_layout_pinocchio_program.so"); + include_bytes!("../../../../../target/deploy/repository_layout_pinocchio_program.so"); // Builds the carnival instruction data in the wire format the program decodes: // name (str), height (u32), ticket_count (u32), attraction (str), attraction_name (str)