From 551192c7f9cde21cff510077ba3f6b25358a6392 Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 10 Oct 2024 12:01:44 +0100 Subject: [PATCH 01/11] Add pinocchio programs --- Cargo.lock | 60 ++++++++++++++++++ Cargo.toml | 4 +- README.md | 2 + cpi/pinocchio/Cargo.toml | 18 ++++++ cpi/pinocchio/src/lib.rs | 49 +++++++++++++++ cpi/pinocchio/tests/functional.rs | 53 ++++++++++++++++ transfer-lamports/pinocchio/Cargo.toml | 18 ++++++ transfer-lamports/pinocchio/src/entrypoint.rs | 15 +++++ transfer-lamports/pinocchio/src/lib.rs | 6 ++ transfer-lamports/pinocchio/src/processor.rs | 27 ++++++++ .../pinocchio/tests/functional.rs | 61 +++++++++++++++++++ 11 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 cpi/pinocchio/Cargo.toml create mode 100644 cpi/pinocchio/src/lib.rs create mode 100644 cpi/pinocchio/tests/functional.rs create mode 100644 transfer-lamports/pinocchio/Cargo.toml create mode 100644 transfer-lamports/pinocchio/src/entrypoint.rs create mode 100644 transfer-lamports/pinocchio/src/lib.rs create mode 100644 transfer-lamports/pinocchio/src/processor.rs create mode 100644 transfer-lamports/pinocchio/tests/functional.rs diff --git a/Cargo.lock b/Cargo.lock index d332008..c471bc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1311,6 +1311,21 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "five8_const" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b4f62f0f8ca357f93ae90c8c2dd1041a1f665fde2f889ea9b1787903829015" +dependencies = [ + "five8_core", +] + +[[package]] +name = "five8_core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a72055cd9cffc40c9f75f1e5810c80559e158796cf2202292ce4745889588" + [[package]] name = "fixedbitset" version = "0.4.2" @@ -2516,6 +2531,51 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pinocchio" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4205e6f0d6f9155ece9e9cff154192a8e36c2c987ce3143f50b5fbea46e7d2fa" + +[[package]] +name = "pinocchio-pubkey" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2826006b44e26d8db9cf1f9ead4e43f01a36fa5f59d032cf9fdb5d118356249" +dependencies = [ + "five8_const", + "pinocchio", +] + +[[package]] +name = "pinocchio-rosetta-cpi" +version = "1.0.0" +dependencies = [ + "pinocchio", + "pinocchio-system", + "solana-program-test", + "solana-sdk", +] + +[[package]] +name = "pinocchio-rosetta-transfer-lamports" +version = "1.0.0" +dependencies = [ + "pinocchio", + "solana-program-test", + "solana-sdk", +] + +[[package]] +name = "pinocchio-system" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "906738307ebf61b4a3751f756ee5635018b20a9151564c265ed84982e63d4323" +dependencies = [ + "pinocchio", + "pinocchio-pubkey", +] + [[package]] name = "pkg-config" version = "0.3.30" diff --git a/Cargo.toml b/Cargo.toml index 17fc4a7..22181dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,10 @@ [workspace] members = [ "cpi", + "cpi/pinocchio", "helloworld", - "transfer-lamports" + "transfer-lamports", + "transfer-lamports/pinocchio" ] resolver = "2" diff --git a/README.md b/README.md index 11dbf2b..04381c7 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,7 @@ lets the VM assume it worked. | Rust | 464 | | Zig | 43 | | C | 103 | +| Rust (pinocchio) | 56 | | Assembly | 22 | This one starts to get interesting since it requires parsing the instruction @@ -182,3 +183,4 @@ the address and `invoke_signed` to CPI to the system program. | Rust | 3662 | | Zig | 2825 | | C | 3122 | +| Rust (pinocchio) | 2947 | diff --git a/cpi/pinocchio/Cargo.toml b/cpi/pinocchio/Cargo.toml new file mode 100644 index 0000000..12ce2bc --- /dev/null +++ b/cpi/pinocchio/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "pinocchio-rosetta-cpi" +version = "1.0.0" +edition = "2021" + +[features] +test-sbf = [] + +[dependencies] +pinocchio = "0.4" +pinocchio-system = "0.1" + +[dev-dependencies] +solana-program-test = "2.0.3" +solana-sdk = "2.0.3" + +[lib] +crate-type = ["cdylib", "lib"] diff --git a/cpi/pinocchio/src/lib.rs b/cpi/pinocchio/src/lib.rs new file mode 100644 index 0000000..1a478dd --- /dev/null +++ b/cpi/pinocchio/src/lib.rs @@ -0,0 +1,49 @@ +//! Rust example demonstrating invoking another program +#![deny(missing_docs)] +#![forbid(unsafe_code)] + +use pinocchio::{ + account_info::AccountInfo, + entrypoint, + entrypoint::ProgramResult, + instruction::{Seed, Signer}, + program_error::ProgramError, + pubkey::{create_program_address, Pubkey}, +}; +use pinocchio_system::instructions::Allocate; + +pinocchio::entrypoint!(process_instruction); + +/// Amount of bytes of account data to allocate +pub const SIZE: usize = 42; + +/// Instruction processor +pub fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + // Account info to allocate abd for the program being invoked + let [allocated_info, _system_program_info] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + let expected_allocated_key = + create_program_address(&[b"You pass butter", &[instruction_data[0]]], program_id)?; + if *allocated_info.key() != expected_allocated_key { + // allocated key does not match the derived address + return Err(ProgramError::InvalidArgument); + } + + // Invoke the system program to allocate account data + Allocate { + account: allocated_info, + space: SIZE as u64, + } + .invoke_signed(&[Signer::from(&[ + Seed::from(b"You pass butter"), + Seed::from(&[instruction_data[0]]), + ])])?; + + Ok(()) +} diff --git a/cpi/pinocchio/tests/functional.rs b/cpi/pinocchio/tests/functional.rs new file mode 100644 index 0000000..97cb519 --- /dev/null +++ b/cpi/pinocchio/tests/functional.rs @@ -0,0 +1,53 @@ +use { + pinocchio_rosetta_cpi::SIZE, + solana_program_test::*, + solana_sdk::{ + account::Account, + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + rent::Rent, + signature::Signer, + system_program, + transaction::Transaction, + }, + std::str::FromStr, +}; + +#[tokio::test] +async fn test_cross_program_invocation() { + let program_id = Pubkey::from_str("invoker111111111111111111111111111111111111").unwrap(); + let (allocated_pubkey, bump_seed) = + Pubkey::find_program_address(&[b"You pass butter"], &program_id); + let mut program_test = ProgramTest::new("pinocchio_rosetta_cpi", program_id, None); + program_test.add_account( + allocated_pubkey, + Account { + lamports: Rent::default().minimum_balance(SIZE), + ..Account::default() + }, + ); + + let (mut banks_client, payer, recent_blockhash) = program_test.start().await; + + let mut transaction = Transaction::new_with_payer( + &[Instruction::new_with_bincode( + program_id, + &[bump_seed], + vec![ + AccountMeta::new(allocated_pubkey, false), + AccountMeta::new_readonly(system_program::id(), false), + ], + )], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); + + // Associated account now exists + let allocated_account = banks_client + .get_account(allocated_pubkey) + .await + .expect("get_account") + .expect("associated_account not none"); + assert_eq!(allocated_account.data.len(), SIZE); +} diff --git a/transfer-lamports/pinocchio/Cargo.toml b/transfer-lamports/pinocchio/Cargo.toml new file mode 100644 index 0000000..098b177 --- /dev/null +++ b/transfer-lamports/pinocchio/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "pinocchio-rosetta-transfer-lamports" +version = "1.0.0" +edition = "2021" + +[features] +no-entrypoint = [] +test-sbf = [] + +[dependencies] +pinocchio = "0.4" + +[dev-dependencies] +solana-program-test = "2.0.3" +solana-sdk = "2.0.3" + +[lib] +crate-type = ["cdylib", "lib"] diff --git a/transfer-lamports/pinocchio/src/entrypoint.rs b/transfer-lamports/pinocchio/src/entrypoint.rs new file mode 100644 index 0000000..6b746ce --- /dev/null +++ b/transfer-lamports/pinocchio/src/entrypoint.rs @@ -0,0 +1,15 @@ +//! Program entrypoint + +#![cfg(not(feature = "no-entrypoint"))] + +use pinocchio::{account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey}; + +pinocchio::entrypoint!(process_instruction); + +fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + crate::processor::process_instruction(program_id, accounts, instruction_data) +} diff --git a/transfer-lamports/pinocchio/src/lib.rs b/transfer-lamports/pinocchio/src/lib.rs new file mode 100644 index 0000000..04c0a8c --- /dev/null +++ b/transfer-lamports/pinocchio/src/lib.rs @@ -0,0 +1,6 @@ +//! A program demonstrating the transfer of lamports +#![deny(missing_docs)] +#![forbid(unsafe_code)] + +mod entrypoint; +pub mod processor; diff --git a/transfer-lamports/pinocchio/src/processor.rs b/transfer-lamports/pinocchio/src/processor.rs new file mode 100644 index 0000000..7bbf656 --- /dev/null +++ b/transfer-lamports/pinocchio/src/processor.rs @@ -0,0 +1,27 @@ +#![allow(clippy::arithmetic_side_effects)] +//! Program instruction processor + +use pinocchio::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, +}; + +/// Instruction processor +pub fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + // As part of the program specification the first account is the source + // account and the second is the destination account + let [source_info, destination_info] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + // Withdraw five lamports from the source + *source_info.try_borrow_mut_lamports()? -= 5; + // Deposit five lamports into the destination + *destination_info.try_borrow_mut_lamports()? += 5; + + Ok(()) +} diff --git a/transfer-lamports/pinocchio/tests/functional.rs b/transfer-lamports/pinocchio/tests/functional.rs new file mode 100644 index 0000000..df6fef5 --- /dev/null +++ b/transfer-lamports/pinocchio/tests/functional.rs @@ -0,0 +1,61 @@ +use { + solana_program_test::*, + solana_sdk::{ + account::Account, + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + signature::Signer, + transaction::Transaction, + }, + std::str::FromStr, +}; + +#[tokio::test] +async fn test_lamport_transfer() { + let program_id = Pubkey::from_str("TransferLamports111111111111111111111111111").unwrap(); + let source_pubkey = Pubkey::new_unique(); + let destination_pubkey = Pubkey::new_unique(); + let mut program_test = + ProgramTest::new("pinocchio_rosetta_transfer_lamports", program_id, None); + let source_lamports = 5; + let destination_lamports = 890_875; + program_test.add_account( + source_pubkey, + Account { + lamports: source_lamports, + data: vec![0], + owner: program_id, // Can only withdraw lamports from accounts owned by the program + ..Account::default() + }, + ); + program_test.add_account( + destination_pubkey, + Account { + lamports: destination_lamports, + ..Account::default() + }, + ); + let (mut banks_client, payer, recent_blockhash) = program_test.start().await; + + let mut transaction = Transaction::new_with_payer( + &[Instruction::new_with_bincode( + program_id, + &(), + vec![ + AccountMeta::new(source_pubkey, false), + AccountMeta::new(destination_pubkey, false), + ], + )], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); + let source = banks_client.get_account(source_pubkey).await.unwrap(); + assert_eq!(source, None); + let destination = banks_client + .get_account(destination_pubkey) + .await + .unwrap() + .unwrap(); + assert_eq!(destination.lamports, destination_lamports + source_lamports); +} From 693f836d0e22d0a4e7baee18cee76d96dca8df70 Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 10 Oct 2024 13:37:46 +0100 Subject: [PATCH 02/11] Bypass borrow check --- README.md | 4 +-- cpi/pinocchio/src/lib.rs | 31 +++++++++++++------- transfer-lamports/pinocchio/src/lib.rs | 1 - transfer-lamports/pinocchio/src/processor.rs | 10 ++++--- 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 04381c7..e24ec62 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ lets the VM assume it worked. | Rust | 464 | | Zig | 43 | | C | 103 | -| Rust (pinocchio) | 56 | +| Rust (pinocchio) | 51 | | Assembly | 22 | This one starts to get interesting since it requires parsing the instruction @@ -183,4 +183,4 @@ the address and `invoke_signed` to CPI to the system program. | Rust | 3662 | | Zig | 2825 | | C | 3122 | -| Rust (pinocchio) | 2947 | +| Rust (pinocchio) | 2900 | diff --git a/cpi/pinocchio/src/lib.rs b/cpi/pinocchio/src/lib.rs index 1a478dd..a686162 100644 --- a/cpi/pinocchio/src/lib.rs +++ b/cpi/pinocchio/src/lib.rs @@ -1,16 +1,15 @@ //! Rust example demonstrating invoking another program #![deny(missing_docs)] -#![forbid(unsafe_code)] use pinocchio::{ account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, - instruction::{Seed, Signer}, + instruction::{Account, AccountMeta, Instruction, Seed, Signer}, + program::invoke_signed_unchecked, program_error::ProgramError, pubkey::{create_program_address, Pubkey}, }; -use pinocchio_system::instructions::Allocate; pinocchio::entrypoint!(process_instruction); @@ -36,14 +35,26 @@ pub fn process_instruction( } // Invoke the system program to allocate account data - Allocate { - account: allocated_info, - space: SIZE as u64, + let mut data = [0; 12]; + data[0] = 8; // ix discriminator + data[4..12].copy_from_slice(&SIZE.to_le_bytes()); + + let instruction = Instruction { + program_id: &pinocchio_system::ID, + accounts: &[AccountMeta::writable_signer(allocated_info.key())], + data: &data, + }; + + unsafe { + invoke_signed_unchecked( + &instruction, + &[Account::from(allocated_info)], + &[Signer::from(&[ + Seed::from(b"You pass butter"), + Seed::from(&[instruction_data[0]]), + ])], + ); } - .invoke_signed(&[Signer::from(&[ - Seed::from(b"You pass butter"), - Seed::from(&[instruction_data[0]]), - ])])?; Ok(()) } diff --git a/transfer-lamports/pinocchio/src/lib.rs b/transfer-lamports/pinocchio/src/lib.rs index 04c0a8c..5c1c835 100644 --- a/transfer-lamports/pinocchio/src/lib.rs +++ b/transfer-lamports/pinocchio/src/lib.rs @@ -1,6 +1,5 @@ //! A program demonstrating the transfer of lamports #![deny(missing_docs)] -#![forbid(unsafe_code)] mod entrypoint; pub mod processor; diff --git a/transfer-lamports/pinocchio/src/processor.rs b/transfer-lamports/pinocchio/src/processor.rs index 7bbf656..eb8b543 100644 --- a/transfer-lamports/pinocchio/src/processor.rs +++ b/transfer-lamports/pinocchio/src/processor.rs @@ -18,10 +18,12 @@ pub fn process_instruction( return Err(ProgramError::NotEnoughAccountKeys); }; - // Withdraw five lamports from the source - *source_info.try_borrow_mut_lamports()? -= 5; - // Deposit five lamports into the destination - *destination_info.try_borrow_mut_lamports()? += 5; + unsafe { + // Withdraw five lamports from the source + *source_info.borrow_mut_lamports_unchecked() -= 5; + // Deposit five lamports into the destination + *destination_info.borrow_mut_lamports_unchecked() += 5; + } Ok(()) } From 4435c76bbe195275c7416c8e5e82c99561245c02 Mon Sep 17 00:00:00 2001 From: febo Date: Mon, 21 Oct 2024 10:09:27 +0100 Subject: [PATCH 03/11] Use lazy entrypoint --- Cargo.lock | 15 +++--- cpi/pinocchio/Cargo.toml | 4 +- cpi/pinocchio/src/lib.rs | 52 +++++++++---------- transfer-lamports/pinocchio/Cargo.toml | 2 +- transfer-lamports/pinocchio/src/entrypoint.rs | 26 +++++++--- transfer-lamports/pinocchio/src/lib.rs | 1 - transfer-lamports/pinocchio/src/processor.rs | 29 ----------- 7 files changed, 53 insertions(+), 76 deletions(-) delete mode 100644 transfer-lamports/pinocchio/src/processor.rs diff --git a/Cargo.lock b/Cargo.lock index c471bc4..5e58212 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2533,15 +2533,13 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pinocchio" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4205e6f0d6f9155ece9e9cff154192a8e36c2c987ce3143f50b5fbea46e7d2fa" +version = "0.5.0" +source = "git+https://github.com/febo/pinocchio.git?branch=create-pda-unchecked#a22cc1861d5666c4aa977dfb25c7b5b33bacc34b" [[package]] name = "pinocchio-pubkey" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2826006b44e26d8db9cf1f9ead4e43f01a36fa5f59d032cf9fdb5d118356249" +version = "0.2.0" +source = "git+https://github.com/febo/pinocchio.git?branch=create-pda-unchecked#a22cc1861d5666c4aa977dfb25c7b5b33bacc34b" dependencies = [ "five8_const", "pinocchio", @@ -2568,9 +2566,8 @@ dependencies = [ [[package]] name = "pinocchio-system" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "906738307ebf61b4a3751f756ee5635018b20a9151564c265ed84982e63d4323" +version = "0.1.3" +source = "git+https://github.com/febo/pinocchio.git?branch=create-pda-unchecked#a22cc1861d5666c4aa977dfb25c7b5b33bacc34b" dependencies = [ "pinocchio", "pinocchio-pubkey", diff --git a/cpi/pinocchio/Cargo.toml b/cpi/pinocchio/Cargo.toml index 12ce2bc..ed7c134 100644 --- a/cpi/pinocchio/Cargo.toml +++ b/cpi/pinocchio/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" test-sbf = [] [dependencies] -pinocchio = "0.4" -pinocchio-system = "0.1" +pinocchio = { version = "0.5", git = "https://github.com/febo/pinocchio.git", branch = "create-pda-unchecked" } +pinocchio-system = { version="0.1", git = "https://github.com/febo/pinocchio.git", branch = "create-pda-unchecked" } [dev-dependencies] solana-program-test = "2.0.3" diff --git a/cpi/pinocchio/src/lib.rs b/cpi/pinocchio/src/lib.rs index a686162..46fdac0 100644 --- a/cpi/pinocchio/src/lib.rs +++ b/cpi/pinocchio/src/lib.rs @@ -2,30 +2,33 @@ #![deny(missing_docs)] use pinocchio::{ - account_info::AccountInfo, - entrypoint, - entrypoint::ProgramResult, - instruction::{Account, AccountMeta, Instruction, Seed, Signer}, + instruction::{Account, AccountMeta, Instruction}, + lazy_entrypoint::InstructionContext, program::invoke_signed_unchecked, program_error::ProgramError, - pubkey::{create_program_address, Pubkey}, + pubkey::create_program_address, + signer, ProgramResult, }; -pinocchio::entrypoint!(process_instruction); +// Since this is a single instruction program, we use the "lazy" variation +// of the entrypoint. +pinocchio::lazy_entrypoint!(process_instruction); /// Amount of bytes of account data to allocate pub const SIZE: usize = 42; -/// Instruction processor -pub fn process_instruction( - program_id: &Pubkey, - accounts: &[AccountInfo], - instruction_data: &[u8], -) -> ProgramResult { - // Account info to allocate abd for the program being invoked - let [allocated_info, _system_program_info] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; +/// Instruction processor. +unsafe fn process_instruction(mut context: InstructionContext) -> ProgramResult { + // Account info to allocate and for the program being invoked. Here we are + // optimizing for CU, so we are not checking that the accounts are present + // ('unchecked' method will panic if the account is duplicated or UB if the + // account is missing). + let allocated_info = context.next_account_unchecked(); + let _system_program_info = context.next_account_unchecked(); + + // Again, we are not checking that all accounts have been consumed (we assume + // that we got only the 2 accounts we expected). + let (instruction_data, program_id) = context.instruction_data_unchecked(); let expected_allocated_key = create_program_address(&[b"You pass butter", &[instruction_data[0]]], program_id)?; @@ -45,16 +48,13 @@ pub fn process_instruction( data: &data, }; - unsafe { - invoke_signed_unchecked( - &instruction, - &[Account::from(allocated_info)], - &[Signer::from(&[ - Seed::from(b"You pass butter"), - Seed::from(&[instruction_data[0]]), - ])], - ); - } + // Invoke the system program with the 'unchcked' function. This is safe since + // we know the accounts are not borrowed elsewhere. + invoke_signed_unchecked( + &instruction, + &[Account::from(&allocated_info)], + &[signer!(b"You pass butter", &[instruction_data[0]])], + ); Ok(()) } diff --git a/transfer-lamports/pinocchio/Cargo.toml b/transfer-lamports/pinocchio/Cargo.toml index 098b177..a8b7898 100644 --- a/transfer-lamports/pinocchio/Cargo.toml +++ b/transfer-lamports/pinocchio/Cargo.toml @@ -8,7 +8,7 @@ no-entrypoint = [] test-sbf = [] [dependencies] -pinocchio = "0.4" +pinocchio = { version = "0.5", git = "https://github.com/febo/pinocchio.git", branch = "create-pda-unchecked" } [dev-dependencies] solana-program-test = "2.0.3" diff --git a/transfer-lamports/pinocchio/src/entrypoint.rs b/transfer-lamports/pinocchio/src/entrypoint.rs index 6b746ce..b4f4cce 100644 --- a/transfer-lamports/pinocchio/src/entrypoint.rs +++ b/transfer-lamports/pinocchio/src/entrypoint.rs @@ -2,14 +2,24 @@ #![cfg(not(feature = "no-entrypoint"))] -use pinocchio::{account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey}; +use pinocchio::{lazy_entrypoint::InstructionContext, ProgramResult}; -pinocchio::entrypoint!(process_instruction); +// Since this is a single instruction program, we use the "lazy" variation +// of the entrypoint. +pinocchio::lazy_entrypoint!(process_instruction); -fn process_instruction( - program_id: &Pubkey, - accounts: &[AccountInfo], - instruction_data: &[u8], -) -> ProgramResult { - crate::processor::process_instruction(program_id, accounts, instruction_data) +#[inline] +unsafe fn process_instruction(mut context: InstructionContext) -> ProgramResult { + // Account infos used in the transfer. Here we are optimizing for CU, so we + // are not checking that the accounts are present ('unchecked' method will panic + // if the account is duplicated or UB if the account is missing). + let source_info = context.next_account_unchecked(); + let destination_info = context.next_account_unchecked(); + + // Transfer five lamports from the source to the destination using 'unchecked' + // borrowing. This is safe since we know the lamports are not borrowed elsewhere. + *source_info.borrow_mut_lamports_unchecked() -= 5; // withdraw five lamports + *destination_info.borrow_mut_lamports_unchecked() += 5; // deposit five lamports + + Ok(()) } diff --git a/transfer-lamports/pinocchio/src/lib.rs b/transfer-lamports/pinocchio/src/lib.rs index 5c1c835..4f5bd25 100644 --- a/transfer-lamports/pinocchio/src/lib.rs +++ b/transfer-lamports/pinocchio/src/lib.rs @@ -2,4 +2,3 @@ #![deny(missing_docs)] mod entrypoint; -pub mod processor; diff --git a/transfer-lamports/pinocchio/src/processor.rs b/transfer-lamports/pinocchio/src/processor.rs deleted file mode 100644 index eb8b543..0000000 --- a/transfer-lamports/pinocchio/src/processor.rs +++ /dev/null @@ -1,29 +0,0 @@ -#![allow(clippy::arithmetic_side_effects)] -//! Program instruction processor - -use pinocchio::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - pubkey::Pubkey, -}; - -/// Instruction processor -pub fn process_instruction( - _program_id: &Pubkey, - accounts: &[AccountInfo], - _instruction_data: &[u8], -) -> ProgramResult { - // As part of the program specification the first account is the source - // account and the second is the destination account - let [source_info, destination_info] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - unsafe { - // Withdraw five lamports from the source - *source_info.borrow_mut_lamports_unchecked() -= 5; - // Deposit five lamports into the destination - *destination_info.borrow_mut_lamports_unchecked() += 5; - } - - Ok(()) -} From 230ff50c8b993f59d13914f6c1bc70e4abc8e826 Mon Sep 17 00:00:00 2001 From: febo Date: Mon, 21 Oct 2024 10:09:44 +0100 Subject: [PATCH 04/11] Update pinocchio results --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e24ec62..10a01ac 100644 --- a/README.md +++ b/README.md @@ -164,8 +164,8 @@ lets the VM assume it worked. | Rust | 464 | | Zig | 43 | | C | 103 | -| Rust (pinocchio) | 51 | | Assembly | 22 | +| Rust (pinocchio) | 20 | This one starts to get interesting since it requires parsing the instruction input. Since the assembly version knows exactly where to find everything, it can @@ -183,4 +183,4 @@ the address and `invoke_signed` to CPI to the system program. | Rust | 3662 | | Zig | 2825 | | C | 3122 | -| Rust (pinocchio) | 2900 | +| Rust (pinocchio) | 2815 | From a3e2b5633c6671286af4dd15b1359f4a704df4db Mon Sep 17 00:00:00 2001 From: febo Date: Sat, 26 Oct 2024 17:07:58 +0100 Subject: [PATCH 05/11] Update crate version --- cpi/pinocchio/Cargo.toml | 4 ++-- cpi/pinocchio/src/lib.rs | 4 ++-- transfer-lamports/pinocchio/Cargo.toml | 2 +- transfer-lamports/pinocchio/src/entrypoint.rs | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cpi/pinocchio/Cargo.toml b/cpi/pinocchio/Cargo.toml index ed7c134..efdcbb2 100644 --- a/cpi/pinocchio/Cargo.toml +++ b/cpi/pinocchio/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" test-sbf = [] [dependencies] -pinocchio = { version = "0.5", git = "https://github.com/febo/pinocchio.git", branch = "create-pda-unchecked" } -pinocchio-system = { version="0.1", git = "https://github.com/febo/pinocchio.git", branch = "create-pda-unchecked" } +pinocchio = "0.6" +pinocchio-system = "0.2" [dev-dependencies] solana-program-test = "2.0.3" diff --git a/cpi/pinocchio/src/lib.rs b/cpi/pinocchio/src/lib.rs index 46fdac0..e35e7b6 100644 --- a/cpi/pinocchio/src/lib.rs +++ b/cpi/pinocchio/src/lib.rs @@ -23,8 +23,8 @@ unsafe fn process_instruction(mut context: InstructionContext) -> ProgramResult // optimizing for CU, so we are not checking that the accounts are present // ('unchecked' method will panic if the account is duplicated or UB if the // account is missing). - let allocated_info = context.next_account_unchecked(); - let _system_program_info = context.next_account_unchecked(); + let allocated_info = context.next_account_unchecked().assume_account(); + let _system_program_info = context.next_account_unchecked().assume_account(); // Again, we are not checking that all accounts have been consumed (we assume // that we got only the 2 accounts we expected). diff --git a/transfer-lamports/pinocchio/Cargo.toml b/transfer-lamports/pinocchio/Cargo.toml index a8b7898..a1a6fac 100644 --- a/transfer-lamports/pinocchio/Cargo.toml +++ b/transfer-lamports/pinocchio/Cargo.toml @@ -8,7 +8,7 @@ no-entrypoint = [] test-sbf = [] [dependencies] -pinocchio = { version = "0.5", git = "https://github.com/febo/pinocchio.git", branch = "create-pda-unchecked" } +pinocchio = "0.6" [dev-dependencies] solana-program-test = "2.0.3" diff --git a/transfer-lamports/pinocchio/src/entrypoint.rs b/transfer-lamports/pinocchio/src/entrypoint.rs index b4f4cce..426a637 100644 --- a/transfer-lamports/pinocchio/src/entrypoint.rs +++ b/transfer-lamports/pinocchio/src/entrypoint.rs @@ -11,10 +11,10 @@ pinocchio::lazy_entrypoint!(process_instruction); #[inline] unsafe fn process_instruction(mut context: InstructionContext) -> ProgramResult { // Account infos used in the transfer. Here we are optimizing for CU, so we - // are not checking that the accounts are present ('unchecked' method will panic - // if the account is duplicated or UB if the account is missing). - let source_info = context.next_account_unchecked(); - let destination_info = context.next_account_unchecked(); + // are not checking that the accounts are present ('unchecked' method has UB + // if the account is missing). + let source_info = context.next_account_unchecked().assume_account(); + let destination_info = context.next_account_unchecked().assume_account(); // Transfer five lamports from the source to the destination using 'unchecked' // borrowing. This is safe since we know the lamports are not borrowed elsewhere. From 24414df1c27d6cbd3dafae70d8a65a541eadeac0 Mon Sep 17 00:00:00 2001 From: febo Date: Sat, 26 Oct 2024 17:08:49 +0100 Subject: [PATCH 06/11] Remove duplicated tests --- cpi/pinocchio/tests/functional.rs | 53 ---------------- .../pinocchio/tests/functional.rs | 61 ------------------- 2 files changed, 114 deletions(-) delete mode 100644 cpi/pinocchio/tests/functional.rs delete mode 100644 transfer-lamports/pinocchio/tests/functional.rs diff --git a/cpi/pinocchio/tests/functional.rs b/cpi/pinocchio/tests/functional.rs deleted file mode 100644 index 97cb519..0000000 --- a/cpi/pinocchio/tests/functional.rs +++ /dev/null @@ -1,53 +0,0 @@ -use { - pinocchio_rosetta_cpi::SIZE, - solana_program_test::*, - solana_sdk::{ - account::Account, - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - rent::Rent, - signature::Signer, - system_program, - transaction::Transaction, - }, - std::str::FromStr, -}; - -#[tokio::test] -async fn test_cross_program_invocation() { - let program_id = Pubkey::from_str("invoker111111111111111111111111111111111111").unwrap(); - let (allocated_pubkey, bump_seed) = - Pubkey::find_program_address(&[b"You pass butter"], &program_id); - let mut program_test = ProgramTest::new("pinocchio_rosetta_cpi", program_id, None); - program_test.add_account( - allocated_pubkey, - Account { - lamports: Rent::default().minimum_balance(SIZE), - ..Account::default() - }, - ); - - let (mut banks_client, payer, recent_blockhash) = program_test.start().await; - - let mut transaction = Transaction::new_with_payer( - &[Instruction::new_with_bincode( - program_id, - &[bump_seed], - vec![ - AccountMeta::new(allocated_pubkey, false), - AccountMeta::new_readonly(system_program::id(), false), - ], - )], - Some(&payer.pubkey()), - ); - transaction.sign(&[&payer], recent_blockhash); - banks_client.process_transaction(transaction).await.unwrap(); - - // Associated account now exists - let allocated_account = banks_client - .get_account(allocated_pubkey) - .await - .expect("get_account") - .expect("associated_account not none"); - assert_eq!(allocated_account.data.len(), SIZE); -} diff --git a/transfer-lamports/pinocchio/tests/functional.rs b/transfer-lamports/pinocchio/tests/functional.rs deleted file mode 100644 index df6fef5..0000000 --- a/transfer-lamports/pinocchio/tests/functional.rs +++ /dev/null @@ -1,61 +0,0 @@ -use { - solana_program_test::*, - solana_sdk::{ - account::Account, - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - signature::Signer, - transaction::Transaction, - }, - std::str::FromStr, -}; - -#[tokio::test] -async fn test_lamport_transfer() { - let program_id = Pubkey::from_str("TransferLamports111111111111111111111111111").unwrap(); - let source_pubkey = Pubkey::new_unique(); - let destination_pubkey = Pubkey::new_unique(); - let mut program_test = - ProgramTest::new("pinocchio_rosetta_transfer_lamports", program_id, None); - let source_lamports = 5; - let destination_lamports = 890_875; - program_test.add_account( - source_pubkey, - Account { - lamports: source_lamports, - data: vec![0], - owner: program_id, // Can only withdraw lamports from accounts owned by the program - ..Account::default() - }, - ); - program_test.add_account( - destination_pubkey, - Account { - lamports: destination_lamports, - ..Account::default() - }, - ); - let (mut banks_client, payer, recent_blockhash) = program_test.start().await; - - let mut transaction = Transaction::new_with_payer( - &[Instruction::new_with_bincode( - program_id, - &(), - vec![ - AccountMeta::new(source_pubkey, false), - AccountMeta::new(destination_pubkey, false), - ], - )], - Some(&payer.pubkey()), - ); - transaction.sign(&[&payer], recent_blockhash); - banks_client.process_transaction(transaction).await.unwrap(); - let source = banks_client.get_account(source_pubkey).await.unwrap(); - assert_eq!(source, None); - let destination = banks_client - .get_account(destination_pubkey) - .await - .unwrap() - .unwrap(); - assert_eq!(destination.lamports, destination_lamports + source_lamports); -} From e544db5859b0e6f9f0514a668221a92ba8fc3197 Mon Sep 17 00:00:00 2001 From: febo Date: Sat, 26 Oct 2024 17:09:02 +0100 Subject: [PATCH 07/11] Add pinocchio test script --- .github/workflows/main.yml | 33 +++++++++++++++++++++++++++ Cargo.lock | 15 +++++++----- cpi/tests/functional.rs | 18 ++++++++++++--- test-pinocchio.sh | 8 +++++++ transfer-lamports/tests/functional.rs | 19 +++++++++++++-- 5 files changed, 82 insertions(+), 11 deletions(-) create mode 100755 test-pinocchio.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9b1d06c..03e937b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -150,3 +150,36 @@ jobs: - name: Build and test program run: ./test-asm.sh ${{ matrix.program }} + + pinocchio-test: + name: Run tests against Pinocchio Rust implementations + strategy: + matrix: + program: [transfer-lamports, cpi] + fail-fast: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + ~/.cache/solana + key: rust-${{ hashFiles('./Cargo.lock') }} + + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.78.0 + + - name: Install Rust build deps + run: ./install-rust-build-deps.sh + + - name: Install Solana + run: | + ./install-solana.sh + echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH + + - name: Build and test program + run: ./test-pinocchio.sh ${{ matrix.program }} diff --git a/Cargo.lock b/Cargo.lock index 5e58212..b289778 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2533,13 +2533,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pinocchio" -version = "0.5.0" -source = "git+https://github.com/febo/pinocchio.git?branch=create-pda-unchecked#a22cc1861d5666c4aa977dfb25c7b5b33bacc34b" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9f716de2190437efa787dd7414f4bcea88d22c2f81bbeecabb7db6d9cc326bd" [[package]] name = "pinocchio-pubkey" -version = "0.2.0" -source = "git+https://github.com/febo/pinocchio.git?branch=create-pda-unchecked#a22cc1861d5666c4aa977dfb25c7b5b33bacc34b" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4800103b9bea24df2e78f161403fc8f8c2286cb5f874b2e5feebe6c5c4fb35a" dependencies = [ "five8_const", "pinocchio", @@ -2566,8 +2568,9 @@ dependencies = [ [[package]] name = "pinocchio-system" -version = "0.1.3" -source = "git+https://github.com/febo/pinocchio.git?branch=create-pda-unchecked#a22cc1861d5666c4aa977dfb25c7b5b33bacc34b" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6a50555b55fde54156049064cc503219068ee53ecd4add7014ec928ee21f67f" dependencies = [ "pinocchio", "pinocchio-pubkey", diff --git a/cpi/tests/functional.rs b/cpi/tests/functional.rs index c8b3413..4304e0e 100644 --- a/cpi/tests/functional.rs +++ b/cpi/tests/functional.rs @@ -5,22 +5,34 @@ use { rent::Rent, system_program, }, - solana_program_rosetta_cpi::{process_instruction, SIZE}, + solana_program_rosetta_cpi::SIZE, solana_program_test::*, solana_sdk::{account::Account, signature::Signer, transaction::Transaction}, std::str::FromStr, }; +/// The name of the program to test when using the `solana_program` library. +const SOLANA_PROGRAM: &str = "solana_program_rosetta_cpi"; + +/// The name of the program to test when using the `pinocchio` library. +const PINOCCHIO_PROGRAM: &str = "pinocchio_rosetta_cpi"; + #[tokio::test] async fn test_cross_program_invocation() { let program_id = Pubkey::from_str("invoker111111111111111111111111111111111111").unwrap(); let (allocated_pubkey, bump_seed) = Pubkey::find_program_address(&[b"You pass butter"], &program_id); + + let library = std::env::var("ROSETTA_LIBRARY").unwrap_or(String::from("solana_program")); let mut program_test = ProgramTest::new( - "solana_program_rosetta_cpi", + match library.as_str() { + "pinocchio" => PINOCCHIO_PROGRAM, + _ => SOLANA_PROGRAM, + }, program_id, - processor!(process_instruction), + None, ); + program_test.add_account( allocated_pubkey, Account { diff --git a/test-pinocchio.sh b/test-pinocchio.sh new file mode 100755 index 0000000..9150c8e --- /dev/null +++ b/test-pinocchio.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +PROGRAM_NAME="$1" +ROOT_DIR="$(cd "$(dirname "$0")"; pwd)" +#set -e +PROGRAM_DIR=$ROOT_DIR/$PROGRAM_NAME +cd $PROGRAM_DIR/pinocchio +cargo build-sbf +ROSETTA_LIBRARY="pinocchio" SBF_OUT_DIR="$ROOT_DIR/target/deploy" cargo test --manifest-path "$PROGRAM_DIR/Cargo.toml" \ No newline at end of file diff --git a/transfer-lamports/tests/functional.rs b/transfer-lamports/tests/functional.rs index ee94c08..9ea9472 100644 --- a/transfer-lamports/tests/functional.rs +++ b/transfer-lamports/tests/functional.rs @@ -8,13 +8,28 @@ use { std::str::FromStr, }; +/// The name of the program to test when using the `solana_program` library. +const SOLANA_PROGRAM: &str = "solana_program_rosetta_transfer_lamports"; + +/// The name of the program to test when using the `pinocchio` library. +const PINOCCHIO_PROGRAM: &str = "pinocchio_rosetta_transfer_lamports"; + #[tokio::test] async fn test_lamport_transfer() { let program_id = Pubkey::from_str("TransferLamports111111111111111111111111111").unwrap(); let source_pubkey = Pubkey::new_unique(); let destination_pubkey = Pubkey::new_unique(); - let mut program_test = - ProgramTest::new("solana_program_rosetta_transfer_lamports", program_id, None); + + let library = std::env::var("ROSETTA_LIBRARY").unwrap_or(String::from("solana_program")); + let mut program_test = ProgramTest::new( + match library.as_str() { + "pinocchio" => PINOCCHIO_PROGRAM, + _ => SOLANA_PROGRAM, + }, + program_id, + None, + ); + let source_lamports = 5; let destination_lamports = 890_875; program_test.add_account( From a89c781a9c2048c609c70af7946e0419687203c7 Mon Sep 17 00:00:00 2001 From: febo Date: Sat, 26 Oct 2024 17:12:14 +0100 Subject: [PATCH 08/11] Update CU values --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 10a01ac..2f1b416 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ lets the VM assume it worked. | Zig | 43 | | C | 103 | | Assembly | 22 | -| Rust (pinocchio) | 20 | +| Rust (pinocchio) | 21 | This one starts to get interesting since it requires parsing the instruction input. Since the assembly version knows exactly where to find everything, it can @@ -183,4 +183,4 @@ the address and `invoke_signed` to CPI to the system program. | Rust | 3662 | | Zig | 2825 | | C | 3122 | -| Rust (pinocchio) | 2815 | +| Rust (pinocchio) | 2812 | From 5c17062e0328ea7d56d0a94ad3564ea81456e67a Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 31 Oct 2024 16:59:20 +0000 Subject: [PATCH 09/11] Clean up --- Cargo.lock | 4 ---- cpi/pinocchio/Cargo.toml | 7 ------- transfer-lamports/pinocchio/Cargo.toml | 5 ----- 3 files changed, 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b289778..4496581 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2553,8 +2553,6 @@ version = "1.0.0" dependencies = [ "pinocchio", "pinocchio-system", - "solana-program-test", - "solana-sdk", ] [[package]] @@ -2562,8 +2560,6 @@ name = "pinocchio-rosetta-transfer-lamports" version = "1.0.0" dependencies = [ "pinocchio", - "solana-program-test", - "solana-sdk", ] [[package]] diff --git a/cpi/pinocchio/Cargo.toml b/cpi/pinocchio/Cargo.toml index efdcbb2..40993a5 100644 --- a/cpi/pinocchio/Cargo.toml +++ b/cpi/pinocchio/Cargo.toml @@ -3,16 +3,9 @@ name = "pinocchio-rosetta-cpi" version = "1.0.0" edition = "2021" -[features] -test-sbf = [] - [dependencies] pinocchio = "0.6" pinocchio-system = "0.2" -[dev-dependencies] -solana-program-test = "2.0.3" -solana-sdk = "2.0.3" - [lib] crate-type = ["cdylib", "lib"] diff --git a/transfer-lamports/pinocchio/Cargo.toml b/transfer-lamports/pinocchio/Cargo.toml index a1a6fac..7803d45 100644 --- a/transfer-lamports/pinocchio/Cargo.toml +++ b/transfer-lamports/pinocchio/Cargo.toml @@ -5,14 +5,9 @@ edition = "2021" [features] no-entrypoint = [] -test-sbf = [] [dependencies] pinocchio = "0.6" -[dev-dependencies] -solana-program-test = "2.0.3" -solana-sdk = "2.0.3" - [lib] crate-type = ["cdylib", "lib"] From 2167c9e7dc8ac8a57ecacbf94929ff8c4443fa2e Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 31 Oct 2024 20:37:35 +0000 Subject: [PATCH 10/11] Add number of accounts assert --- README.md | 4 +- cpi/pinocchio/src/lib.rs | 41 +++++++++-------- transfer-lamports/pinocchio/src/entrypoint.rs | 46 ++++++++++++++----- 3 files changed, 60 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 2f1b416..4097c46 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ lets the VM assume it worked. | Zig | 43 | | C | 103 | | Assembly | 22 | -| Rust (pinocchio) | 21 | +| Rust (pinocchio) | 23 | This one starts to get interesting since it requires parsing the instruction input. Since the assembly version knows exactly where to find everything, it can @@ -183,4 +183,4 @@ the address and `invoke_signed` to CPI to the system program. | Rust | 3662 | | Zig | 2825 | | C | 3122 | -| Rust (pinocchio) | 2812 | +| Rust (pinocchio) | 2816 | diff --git a/cpi/pinocchio/src/lib.rs b/cpi/pinocchio/src/lib.rs index e35e7b6..581c358 100644 --- a/cpi/pinocchio/src/lib.rs +++ b/cpi/pinocchio/src/lib.rs @@ -1,4 +1,4 @@ -//! Rust example demonstrating invoking another program +//! Rust example using pinocchio demonstrating invoking another program #![deny(missing_docs)] use pinocchio::{ @@ -18,17 +18,20 @@ pinocchio::lazy_entrypoint!(process_instruction); pub const SIZE: usize = 42; /// Instruction processor. -unsafe fn process_instruction(mut context: InstructionContext) -> ProgramResult { - // Account info to allocate and for the program being invoked. Here we are - // optimizing for CU, so we are not checking that the accounts are present - // ('unchecked' method will panic if the account is duplicated or UB if the - // account is missing). - let allocated_info = context.next_account_unchecked().assume_account(); - let _system_program_info = context.next_account_unchecked().assume_account(); - - // Again, we are not checking that all accounts have been consumed (we assume - // that we got only the 2 accounts we expected). - let (instruction_data, program_id) = context.instruction_data_unchecked(); +fn process_instruction(mut context: InstructionContext) -> ProgramResult { + if context.remaining() != 2 { + return Err(ProgramError::NotEnoughAccountKeys); + } + + // Account info to allocate and for the program being invoked. We know that + // we got 2 accounts, so it is ok use `next_account_unchecked` twice. + let allocated_info = unsafe { context.next_account_unchecked().assume_account() }; + // just move the offset, we don't need the system program info + let _system_program_info = unsafe { context.next_account_unchecked() }; + + // Again, don't need to check that all accounts have been consumed, we know + // we have exactly 2 accounts. + let (instruction_data, program_id) = unsafe { context.instruction_data_unchecked() }; let expected_allocated_key = create_program_address(&[b"You pass butter", &[instruction_data[0]]], program_id)?; @@ -48,13 +51,15 @@ unsafe fn process_instruction(mut context: InstructionContext) -> ProgramResult data: &data, }; - // Invoke the system program with the 'unchcked' function. This is safe since + // Invoke the system program with the 'unchecked' function - this is ok since // we know the accounts are not borrowed elsewhere. - invoke_signed_unchecked( - &instruction, - &[Account::from(&allocated_info)], - &[signer!(b"You pass butter", &[instruction_data[0]])], - ); + unsafe { + invoke_signed_unchecked( + &instruction, + &[Account::from(&allocated_info)], + &[signer!(b"You pass butter", &[instruction_data[0]])], + ) + }; Ok(()) } diff --git a/transfer-lamports/pinocchio/src/entrypoint.rs b/transfer-lamports/pinocchio/src/entrypoint.rs index 426a637..91b20cf 100644 --- a/transfer-lamports/pinocchio/src/entrypoint.rs +++ b/transfer-lamports/pinocchio/src/entrypoint.rs @@ -2,24 +2,48 @@ #![cfg(not(feature = "no-entrypoint"))] -use pinocchio::{lazy_entrypoint::InstructionContext, ProgramResult}; +use pinocchio::{ + lazy_entrypoint::{InstructionContext, MaybeAccount}, + program_error::ProgramError, + ProgramResult, +}; // Since this is a single instruction program, we use the "lazy" variation // of the entrypoint. pinocchio::lazy_entrypoint!(process_instruction); #[inline] -unsafe fn process_instruction(mut context: InstructionContext) -> ProgramResult { - // Account infos used in the transfer. Here we are optimizing for CU, so we - // are not checking that the accounts are present ('unchecked' method has UB - // if the account is missing). - let source_info = context.next_account_unchecked().assume_account(); - let destination_info = context.next_account_unchecked().assume_account(); +fn process_instruction(mut context: InstructionContext) -> ProgramResult { + if context.remaining() != 2 { + return Err(ProgramError::NotEnoughAccountKeys); + } - // Transfer five lamports from the source to the destination using 'unchecked' - // borrowing. This is safe since we know the lamports are not borrowed elsewhere. - *source_info.borrow_mut_lamports_unchecked() -= 5; // withdraw five lamports - *destination_info.borrow_mut_lamports_unchecked() += 5; // deposit five lamports + // This block is declared unsafe because: + // + // - We are using `next_account_unchecked`, which does not decrease the number of + // remaining accounts in the context. This is ok because we know that we have + // exactly two accounts. + // + // - We are using `assume_account` on the first account, which is ok because we + // know that we have at least one account. + // + // - We are using `borrow_mut_lamports_unchecked`, which is ok because we know + // that the lamports are not borrowed elsewhere and the accounts are different. + unsafe { + let source_info = context.next_account_unchecked().assume_account(); + + // The second account is the destination account – this one could be duplicated. + // + // We only need to transfer lamports from the source to the destination when the + // accounts are different, so we can safely ignore the case when the account is + // duplicated. + if let MaybeAccount::Account(destination_info) = context.next_account_unchecked() { + // withdraw five lamports + *source_info.borrow_mut_lamports_unchecked() -= 5; + // deposit five lamports + *destination_info.borrow_mut_lamports_unchecked() += 5; + } + } Ok(()) } From 54569ecd093668d312db5d5852e6f244f657853a Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 31 Oct 2024 22:07:00 +0000 Subject: [PATCH 11/11] Simplify program name env --- cpi/tests/functional.rs | 12 +----------- test-pinocchio.sh | 2 +- transfer-lamports/tests/functional.rs | 12 +----------- 3 files changed, 3 insertions(+), 23 deletions(-) diff --git a/cpi/tests/functional.rs b/cpi/tests/functional.rs index 4304e0e..72282cf 100644 --- a/cpi/tests/functional.rs +++ b/cpi/tests/functional.rs @@ -11,24 +11,14 @@ use { std::str::FromStr, }; -/// The name of the program to test when using the `solana_program` library. -const SOLANA_PROGRAM: &str = "solana_program_rosetta_cpi"; - -/// The name of the program to test when using the `pinocchio` library. -const PINOCCHIO_PROGRAM: &str = "pinocchio_rosetta_cpi"; - #[tokio::test] async fn test_cross_program_invocation() { let program_id = Pubkey::from_str("invoker111111111111111111111111111111111111").unwrap(); let (allocated_pubkey, bump_seed) = Pubkey::find_program_address(&[b"You pass butter"], &program_id); - let library = std::env::var("ROSETTA_LIBRARY").unwrap_or(String::from("solana_program")); let mut program_test = ProgramTest::new( - match library.as_str() { - "pinocchio" => PINOCCHIO_PROGRAM, - _ => SOLANA_PROGRAM, - }, + option_env!("PROGRAM_NAME").unwrap_or("solana_program_rosetta_cpi"), program_id, None, ); diff --git a/test-pinocchio.sh b/test-pinocchio.sh index 9150c8e..269d374 100755 --- a/test-pinocchio.sh +++ b/test-pinocchio.sh @@ -5,4 +5,4 @@ ROOT_DIR="$(cd "$(dirname "$0")"; pwd)" PROGRAM_DIR=$ROOT_DIR/$PROGRAM_NAME cd $PROGRAM_DIR/pinocchio cargo build-sbf -ROSETTA_LIBRARY="pinocchio" SBF_OUT_DIR="$ROOT_DIR/target/deploy" cargo test --manifest-path "$PROGRAM_DIR/Cargo.toml" \ No newline at end of file +PROGRAM_NAME="pinocchio_rosetta_${PROGRAM_NAME//-/_}" SBF_OUT_DIR="$ROOT_DIR/target/deploy" cargo test --manifest-path "$PROGRAM_DIR/Cargo.toml" \ No newline at end of file diff --git a/transfer-lamports/tests/functional.rs b/transfer-lamports/tests/functional.rs index 9ea9472..a090436 100644 --- a/transfer-lamports/tests/functional.rs +++ b/transfer-lamports/tests/functional.rs @@ -8,24 +8,14 @@ use { std::str::FromStr, }; -/// The name of the program to test when using the `solana_program` library. -const SOLANA_PROGRAM: &str = "solana_program_rosetta_transfer_lamports"; - -/// The name of the program to test when using the `pinocchio` library. -const PINOCCHIO_PROGRAM: &str = "pinocchio_rosetta_transfer_lamports"; - #[tokio::test] async fn test_lamport_transfer() { let program_id = Pubkey::from_str("TransferLamports111111111111111111111111111").unwrap(); let source_pubkey = Pubkey::new_unique(); let destination_pubkey = Pubkey::new_unique(); - let library = std::env::var("ROSETTA_LIBRARY").unwrap_or(String::from("solana_program")); let mut program_test = ProgramTest::new( - match library.as_str() { - "pinocchio" => PINOCCHIO_PROGRAM, - _ => SOLANA_PROGRAM, - }, + option_env!("PROGRAM_NAME").unwrap_or("solana_program_rosetta_transfer_lamports"), program_id, None, );