From dc02cb1d930466bd3986a9ea46e47905f41adaa3 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Tue, 16 Jun 2026 18:11:05 +0900 Subject: [PATCH 1/2] use ABIv2 --- Cargo.lock | 97 +++++++++++++++++++++------- Cargo.toml | 6 ++ program/Cargo.toml | 1 + program/src/instruction_data.rs | 83 ++++++++++++++++++++---- program/src/lib.rs | 81 ++++++++--------------- program/src/processor.rs | 55 ++-------------- program/tests/process_instruction.rs | 53 ++++----------- 7 files changed, 193 insertions(+), 183 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 925cd07..0a28d81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,7 +25,7 @@ dependencies = [ "bincode", "libsecp256k1", "num-traits", - "solana-account", + "solana-account 3.4.0", "solana-account-info", "solana-big-mod-exp", "solana-blake3-hasher", @@ -41,7 +41,7 @@ dependencies = [ "solana-program-entrypoint", "solana-program-runtime", "solana-pubkey 3.0.0", - "solana-sbpf", + "solana-sbpf 0.13.1", "solana-sdk-ids", "solana-secp256k1-recover", "solana-sha256-hasher", @@ -55,7 +55,7 @@ dependencies = [ "solana-svm-type-overrides", "solana-sysvar", "solana-sysvar-id", - "solana-transaction-context", + "solana-transaction-context 3.1.14", "thiserror 2.0.18", ] @@ -1254,7 +1254,7 @@ dependencies = [ "bincode", "mollusk-svm-error", "mollusk-svm-result", - "solana-account", + "solana-account 3.4.0", "solana-bpf-loader-program", "solana-clock", "solana-compute-budget", @@ -1272,7 +1272,7 @@ dependencies = [ "solana-program-error", "solana-program-runtime", "solana-pubkey 4.2.0", - "solana-rent", + "solana-rent 3.1.0", "solana-sdk-ids", "solana-slot-hashes", "solana-stake-interface", @@ -1283,7 +1283,7 @@ dependencies = [ "solana-system-program", "solana-sysvar", "solana-sysvar-id", - "solana-transaction-context", + "solana-transaction-context 3.1.14", "solana-transaction-error", ] @@ -1303,11 +1303,11 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92376062d0cad8a3b28f86e57b58c8949302aa6eaed518f584fdfc2f75d8face" dependencies = [ - "solana-account", + "solana-account 3.4.0", "solana-instruction", "solana-program-error", "solana-pubkey 4.2.0", - "solana-rent", + "solana-rent 3.1.0", "solana-transaction-error", ] @@ -1838,6 +1838,19 @@ dependencies = [ "solana-sysvar", ] +[[package]] +name = "solana-account" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c1b95bd432efb92837bc38001365e5acb5c0175b199bc447b08a8c320ecf6c" +dependencies = [ + "solana-account-info", + "solana-clock", + "solana-instruction-error", + "solana-pubkey 4.2.0", + "solana-sdk-ids", +] + [[package]] name = "solana-account-info" version = "3.1.1" @@ -1945,7 +1958,7 @@ dependencies = [ "agave-syscalls", "bincode", "qualifier_attr", - "solana-account", + "solana-account 3.4.0", "solana-bincode", "solana-clock", "solana-instruction", @@ -1955,14 +1968,14 @@ dependencies = [ "solana-program-entrypoint", "solana-program-runtime", "solana-pubkey 3.0.0", - "solana-sbpf", + "solana-sbpf 0.13.1", "solana-sdk-ids", "solana-svm-feature-set", "solana-svm-log-collector", "solana-svm-measure", "solana-svm-type-overrides", "solana-system-interface", - "solana-transaction-context", + "solana-transaction-context 3.1.14", ] [[package]] @@ -2255,7 +2268,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "805fd25b29e5a1a0e6c3dd6320c9da80f275fbe4ff6e392617c303a2085c435e" dependencies = [ - "solana-account", + "solana-account 3.4.0", "solana-hash 3.1.0", "solana-nonce", "solana-sdk-ids", @@ -2333,7 +2346,7 @@ dependencies = [ "percentage", "rand 0.8.6", "serde", - "solana-account", + "solana-account 3.4.0", "solana-account-info", "solana-clock", "solana-epoch-rewards", @@ -2345,8 +2358,8 @@ dependencies = [ "solana-loader-v3-interface", "solana-program-entrypoint", "solana-pubkey 3.0.0", - "solana-rent", - "solana-sbpf", + "solana-rent 3.1.0", + "solana-sbpf 0.13.1", "solana-sdk-ids", "solana-slot-hashes", "solana-stable-layout", @@ -2361,7 +2374,7 @@ dependencies = [ "solana-system-interface", "solana-sysvar", "solana-sysvar-id", - "solana-transaction-context", + "solana-transaction-context 3.1.14", "thiserror 2.0.18", ] @@ -2396,6 +2409,15 @@ dependencies = [ "solana-sysvar-id", ] +[[package]] +name = "solana-rent" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9809b081e99bc142ce803bcd7ee18306759ce3b30a96a9da3f6f41c45e50ef0" +dependencies = [ + "solana-sdk-macro", +] + [[package]] name = "solana-sanitize" version = "3.0.1" @@ -2419,6 +2441,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "solana-sbpf" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f84c593fa3d4131045b606dec5acf9d8eac73791bc786ca9911057aec8f43ec" +dependencies = [ + "byteorder", + "combine", + "hash32", + "log", + "rustc-demangle", + "thiserror 2.0.18", +] + [[package]] name = "solana-sdk-ids" version = "3.1.0" @@ -2459,6 +2495,7 @@ dependencies = [ "solana-sdk-ids", "solana-secp256k1-recover", "solana-signature", + "solana-transaction-context 4.2.0-alpha.0", "solana-zero-copy", ] @@ -2568,7 +2605,7 @@ version = "3.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "012617d16d2994673d98792f7f6d93f612dea00b1b747a3c4aec24c12547875b" dependencies = [ - "solana-account", + "solana-account 3.4.0", "solana-clock", "solana-precompile-error", "solana-pubkey 3.0.0", @@ -2653,7 +2690,7 @@ dependencies = [ "bincode", "log", "serde", - "solana-account", + "solana-account 3.4.0", "solana-bincode", "solana-fee-calculator", "solana-instruction", @@ -2667,7 +2704,7 @@ dependencies = [ "solana-svm-type-overrides", "solana-system-interface", "solana-sysvar", - "solana-transaction-context", + "solana-transaction-context 3.1.14", ] [[package]] @@ -2694,7 +2731,7 @@ dependencies = [ "solana-program-error", "solana-program-memory", "solana-pubkey 4.2.0", - "solana-rent", + "solana-rent 3.1.0", "solana-sdk-ids", "solana-sdk-macro", "solana-slot-hashes", @@ -2738,12 +2775,26 @@ dependencies = [ "bincode", "qualifier_attr", "serde", - "solana-account", + "solana-account 3.4.0", "solana-instruction", "solana-instructions-sysvar", "solana-pubkey 3.0.0", - "solana-rent", - "solana-sbpf", + "solana-rent 3.1.0", + "solana-sbpf 0.13.1", + "solana-sdk-ids", +] + +[[package]] +name = "solana-transaction-context" +version = "4.2.0-alpha.0" +source = "git+https://github.com/LucasSte/agave?branch=abiv2-return-buffer#6e33faf6a023391b0a4cac81519524bffb980afc" +dependencies = [ + "solana-account 4.3.0", + "solana-instruction", + "solana-instructions-sysvar", + "solana-pubkey 4.2.0", + "solana-rent 4.2.0", + "solana-sbpf 0.21.0", "solana-sdk-ids", ] diff --git a/Cargo.toml b/Cargo.toml index fe3101b..c72cc3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,4 +24,10 @@ solana-pubkey = "4.1.0" solana-sdk-ids = "3.1.0" solana-secp256k1-recover = "3.1.1" solana-signature = "3.4.0" +solana-transaction-context = { version = "=4.2.0-alpha.0", features = [ + "agave-unstable-api", +] } solana-zero-copy = "1.1.1" + +[patch.crates-io] +solana-transaction-context = { git = "https://github.com/LucasSte/agave", branch = "abiv2-return-buffer" } diff --git a/program/Cargo.toml b/program/Cargo.toml index 27a43e5..04d78c2 100644 --- a/program/Cargo.toml +++ b/program/Cargo.toml @@ -23,6 +23,7 @@ solana-program-error = { workspace = true } solana-pubkey = { workspace = true } solana-sdk-ids = { workspace = true, optional = true } solana-secp256k1-recover = { workspace = true } +solana-transaction-context = { workspace = true } solana-zero-copy = { workspace = true } [target.'cfg(not(any(target_os = "solana", target_arch = "bpf")))'.dependencies] diff --git a/program/src/instruction_data.rs b/program/src/instruction_data.rs index 33cf375..4c98679 100644 --- a/program/src/instruction_data.rs +++ b/program/src/instruction_data.rs @@ -100,8 +100,8 @@ fn signature_offsets_from_bytes(input: &[u8]) -> Result, Pr /// /// `offset` is a `u16` to match the field widths in `SecpSignatureOffsets`; /// the arithmetic is promoted to `usize` with overflow protection. -fn get_instruction_data_slice( - input: &[u8], +fn read_slice_from_payload( + payload: &[u8], offset: u16, length: usize, ) -> Result<&[u8], ProgramError> { @@ -109,20 +109,71 @@ fn get_instruction_data_slice( let end = offset .checked_add(length) .ok_or(ProgramError::InvalidInstructionData)?; - input + payload .get(offset..end) .ok_or(ProgramError::InvalidInstructionData) } -fn get_instruction_data_array( - input: &[u8], +/// Reads a fixed-size array from the provided instruction payload. +fn read_array_from_payload( + payload: &[u8], offset: u16, ) -> Result<&[u8; N], ProgramError> { - get_instruction_data_slice(input, offset, N)? + read_slice_from_payload(payload, offset, N)? .try_into() .map_err(|_| ProgramError::InvalidInstructionData) } +/// Fetches the raw instruction data payload for a given instruction index. +/// +/// On-chain (SBF), this uses ABIv2 memory mappings to read sibling instructions. +/// Off-chain (native/tests), this requires the target index to be 0 (the current instruction). +pub(crate) fn fetch_instruction_payload<'a>( + index: u8, + _current_instruction_data: &'a [u8], +) -> Result<&'a [u8], ProgramError> { + #[cfg(any(target_os = "solana", target_arch = "bpf"))] + { + use solana_transaction_context::{ + instruction::InstructionFrame, + transaction::TransactionFrame, + vm_addresses::{INSTRUCTION_TRACE_AREA, TRANSACTION_FRAME_ADDRESS}, + }; + + // Grab the transaction frame + let tx_frame = unsafe { &*(TRANSACTION_FRAME_ADDRESS as *const TransactionFrame) }; + + // Map the instruction trace + let instruction_trace = unsafe { + core::slice::from_raw_parts( + INSTRUCTION_TRACE_AREA as *const InstructionFrame, + tx_frame.total_number_of_instructions_in_trace as usize, + ) + }; + + // Grab the target instruction's data + let frame = instruction_trace + .get(index as usize) + .ok_or(ProgramError::InvalidInstructionData)?; + + let ptr = frame.instruction_data.ptr() as *const u8; + let len = frame.instruction_data.len() as usize; + + Ok(unsafe { core::slice::from_raw_parts(ptr, len) }) + } + + #[cfg(not(any(target_os = "solana", target_arch = "bpf")))] + { + // For native testing, ABIv2 memory maps are unavailable. + // Tests currently only simulate the `secp256k1` instruction at index 0. + if index == 0 { + Ok(_current_instruction_data) + } else { + Err(ProgramError::InvalidInstructionData) + } + } +} + /// Extracts all signature fields for one entry from raw /// `instruction_data` using the byte positions in `offsets`. /// @@ -132,23 +183,27 @@ pub(crate) fn get_signature_fields<'a>( instruction_data: &'a [u8], offsets: &SignatureOffsets<'_>, ) -> Result, ProgramError> { + let sig_data = + fetch_instruction_payload(offsets.signature_instruction_index(), instruction_data)?; + let eth_data = + fetch_instruction_payload(offsets.eth_address_instruction_index(), instruction_data)?; + let msg_data = + fetch_instruction_payload(offsets.message_instruction_index(), instruction_data)?; + let recovery_id_offset = usize::from(offsets.signature_offset()) .checked_add(SIGNATURE_SERIALIZED_SIZE) .ok_or(ProgramError::InvalidInstructionData)?; - let recovery_id = instruction_data + let recovery_id = sig_data .get(recovery_id_offset) .copied() .ok_or(ProgramError::InvalidInstructionData)?; Ok(SignatureFields { - signature: get_instruction_data_array(instruction_data, offsets.signature_offset())?, + signature: read_array_from_payload(instruction_data, offsets.signature_offset())?, recovery_id: validate_recovery_id(recovery_id)?, - expected_address: get_instruction_data_array( - instruction_data, - offsets.eth_address_offset(), - )?, - message: get_instruction_data_slice( - instruction_data, + expected_address: read_array_from_payload(eth_data, offsets.eth_address_offset())?, + message: read_slice_from_payload( + msg_data, offsets.message_data_offset(), usize::from(offsets.message_data_size()), )?, diff --git a/program/src/lib.rs b/program/src/lib.rs index c34bf79..ff026bd 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -171,67 +171,38 @@ pub use instruction::{ }; pub use processor::process_instruction; -#[cfg(target_os = "solana")] -use solana_program_entrypoint::ProgramResult; -#[cfg(target_os = "solana")] -use solana_program_error::ProgramError; - -/// Program entry point for the version 2 instruction-data pointer interface. -#[cfg(all(target_os = "solana", not(feature = "no-entrypoint")))] +/// Program entrypoint for the version 2 instruction-data pointer interface. #[unsafe(no_mangle)] -pub extern "C" fn entrypoint(input: *mut u8, instruction_data_addr: *const u8) -> u64 { - match unsafe { process_entrypoint(input, instruction_data_addr) } { - Ok(()) => solana_program_entrypoint::SUCCESS, - Err(error) => error.into(), - } -} +pub unsafe extern "C" fn entrypoint() -> u64 { + use solana_transaction_context::{ + instruction::InstructionFrame, + transaction::TransactionFrame, + vm_addresses::{INSTRUCTION_TRACE_AREA, TRANSACTION_FRAME_ADDRESS}, + }; -/// Processes the version 2 instruction-data pointer interface. -/// -/// # Safety -/// -/// The Solana runtime must pass `input` as the serialized accounts buffer and -/// `instruction_data_addr` as the pointer to instruction data with its length -/// stored in the preceding 8 bytes. -#[cfg(all(target_os = "solana", not(feature = "no-entrypoint")))] -unsafe fn process_entrypoint(input: *mut u8, instruction_data_addr: *const u8) -> ProgramResult { - if processor::in_cpi() { - return Err(ProgramError::InvalidArgument); - } + // 1. Grab the Transaction Frame + let tx_frame = &*(TRANSACTION_FRAME_ADDRESS as *const TransactionFrame); - let num_accounts = unsafe { *(input as *const u64) }; - if num_accounts != 0 { - return Err(ProgramError::InvalidArgument); - } + // 2. Map the Instruction Trace + let instruction_trace = core::slice::from_raw_parts( + INSTRUCTION_TRACE_AREA as *const InstructionFrame, + tx_frame.total_number_of_instructions_in_trace as usize, + ); - let instruction_data_len_addr = (instruction_data_addr as usize) - .checked_sub(core::mem::size_of::()) - .ok_or(ProgramError::InvalidInstructionData)?; - let instruction_data_len = unsafe { *(instruction_data_len_addr as *const u64) }; - let instruction_data = unsafe { - core::slice::from_raw_parts(instruction_data_addr, instruction_data_len as usize) - }; + // 3. Grab the current Instruction Frame + let current_frame = &instruction_trace[tx_frame.current_executing_instruction as usize]; - processor::verify_secp256k1_instruction(instruction_data) -} + // 4. Extract clean data + let num_accounts = current_frame.instruction_accounts.len() as usize; + let ptr = current_frame.instruction_data.ptr() as *const u8; + let len = current_frame.instruction_data.len() as usize; + let instruction_data = core::slice::from_raw_parts(ptr, len); -#[cfg(not(feature = "no-entrypoint"))] -solana_program_entrypoint::custom_heap_default!(); -#[cfg(not(feature = "no-entrypoint"))] -solana_program_entrypoint::custom_panic_default!(); + // If the caller is not u16::MAX, we are inside a CPI + let in_cpi = current_frame.index_of_caller_instruction != u16::MAX; -#[cfg(all(target_os = "solana", not(feature = "no-entrypoint")))] -#[unsafe(no_mangle)] -pub extern "C" fn abort() -> ! { - let message = "abort"; - let file = file!(); - unsafe { - solana_program_entrypoint::__log(message.as_ptr(), message.len() as u64); - solana_program_entrypoint::__panic( - file.as_ptr(), - file.len() as u64, - line!() as u64, - column!() as u64, - ) + match process_instruction(num_accounts, instruction_data, in_cpi) { + Ok(()) => solana_program_entrypoint::SUCCESS, + Err(error) => error.into(), } } diff --git a/program/src/processor.rs b/program/src/processor.rs index 3718e2d..b1be5a3 100644 --- a/program/src/processor.rs +++ b/program/src/processor.rs @@ -5,35 +5,12 @@ use { get_signature_fields, iter_signature_offsets, SignatureFields, SignatureOffsets, }, }, - solana_account_info::AccountInfo, solana_keccak_hasher::hash, solana_program_entrypoint::ProgramResult, solana_program_error::ProgramError, - solana_pubkey::Pubkey, solana_secp256k1_recover::secp256k1_recover, }; -/// Transaction index of the instruction whose data this program is verifying. -/// -/// An SBF program only receives its own instruction data, so all offset fields -/// in `SecpSignatureOffsets` must reference index 0. Supporting other indices -/// would require a runtime change to expose sibling instruction data. -const CURRENT_INSTRUCTION_INDEX: u8 = 0; - -pub(crate) fn in_cpi() -> bool { - #[cfg(target_os = "solana")] - { - use solana_instruction::{syscalls::sol_get_stack_height, TRANSACTION_LEVEL_STACK_HEIGHT}; - - unsafe { sol_get_stack_height() as usize > TRANSACTION_LEVEL_STACK_HEIGHT } - } - - #[cfg(not(target_os = "solana"))] - { - false - } -} - const SIGNATURE_SCALAR_LENGTH: usize = 32; const SECP256K1_ORDER: [u8; SIGNATURE_SCALAR_LENGTH] = [ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, @@ -61,38 +38,24 @@ pub(crate) fn verify_secp256k1_instruction(instruction_data: &[u8]) -> ProgramRe /// if any accounts are provided, or propagates errors from signature /// verification. pub fn process_instruction( - _program_id: &Pubkey, - accounts: &[AccountInfo], + num_accounts: usize, instruction_data: &[u8], + in_cpi: bool, ) -> ProgramResult { - if in_cpi() { + if in_cpi { return Err(ProgramError::InvalidArgument); } - - if !accounts.is_empty() { + if num_accounts > 0 { return Err(ProgramError::InvalidArgument); } - verify_secp256k1_instruction(instruction_data) } -/// Returns `true` when every offset field in `offsets` references the current -/// instruction (index 0) rather than a sibling instruction in the transaction. -fn references_current_instruction(offsets: &SignatureOffsets<'_>) -> bool { - offsets.signature_instruction_index() == CURRENT_INSTRUCTION_INDEX - && offsets.eth_address_instruction_index() == CURRENT_INSTRUCTION_INDEX - && offsets.message_instruction_index() == CURRENT_INSTRUCTION_INDEX -} - /// Validates a single signature entry described by `offsets`. /// /// Rejects offsets that reference instructions other than the current one, /// then extracts the raw fields and delegates to [`verify_signature_fields`]. fn verify_signature(instruction_data: &[u8], offsets: &SignatureOffsets<'_>) -> ProgramResult { - if !references_current_instruction(offsets) { - return Err(ProgramError::InvalidInstructionData); - } - let fields = get_signature_fields(instruction_data, offsets)?; verify_signature_fields(&fields) } @@ -155,13 +118,3 @@ fn subtract_s_from_order(s: &mut [u8]) { } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn host_is_not_in_cpi() { - assert!(!in_cpi()); - } -} diff --git a/program/tests/process_instruction.rs b/program/tests/process_instruction.rs index 8abf484..27497b2 100644 --- a/program/tests/process_instruction.rs +++ b/program/tests/process_instruction.rs @@ -2,7 +2,6 @@ use { common::{first_offsets, signed_instruction, write_offsets}, k256::ecdsa::Signature, solana_program_error::ProgramError, - solana_pubkey::Pubkey, solana_secp256k1_program::{process_instruction, DATA_START, SIGNATURE_SERIALIZED_SIZE}, }; @@ -10,75 +9,65 @@ mod common; #[test] fn verifies_matching_signature() { - let program_id = Pubkey::default(); let instruction = signed_instruction(&[b"hello secp256k1"]); - - assert_eq!(process_instruction(&program_id, &[], &instruction), Ok(())); + // 0 accounts, pass the data, not in CPI (false) + assert_eq!(process_instruction(0, &instruction, false), Ok(())); } #[test] fn verifies_multiple_signatures() { - let program_id = Pubkey::default(); let instruction = signed_instruction(&[b"hello secp256k1", b"second message"]); - assert_eq!(process_instruction(&program_id, &[], &instruction), Ok(())); + assert_eq!(process_instruction(0, &instruction, false), Ok(())); } #[test] fn rejects_wrong_address() { - let program_id = Pubkey::default(); let mut instruction = signed_instruction(&[b"hello secp256k1"]); let offsets = first_offsets(&instruction); instruction[usize::from(offsets.eth_address_offset)] ^= 1; assert_eq!( - process_instruction(&program_id, &[], &instruction), + process_instruction(0, &instruction, false), Err(ProgramError::InvalidArgument) ); } #[test] fn rejects_corrupted_signature() { - let program_id = Pubkey::default(); let mut instruction = signed_instruction(&[b"hello secp256k1"]); let offsets = first_offsets(&instruction); instruction[usize::from(offsets.signature_offset)] ^= 1; assert_eq!( - process_instruction(&program_id, &[], &instruction), + process_instruction(0, &instruction, false), Err(ProgramError::InvalidArgument) ); } #[test] fn rejects_short_instruction() { - let program_id = Pubkey::default(); - assert_eq!( - process_instruction(&program_id, &[], &[]), + process_instruction(0, &[], false), Err(ProgramError::InvalidInstructionData) ); assert_eq!( - process_instruction(&program_id, &[], &[1]), + process_instruction(0, &[1], false), Err(ProgramError::InvalidInstructionData) ); } #[test] fn accepts_zero_signatures_only_when_data_has_no_payload() { - let program_id = Pubkey::default(); - - assert_eq!(process_instruction(&program_id, &[], &[0]), Ok(())); + assert_eq!(process_instruction(0, &[0], false), Ok(())); assert_eq!( - process_instruction(&program_id, &[], &[0, 0]), + process_instruction(0, &[0, 0], false), Err(ProgramError::InvalidInstructionData) ); } #[test] fn passes_supported_overflow_recovery_ids_to_recover() { - let program_id = Pubkey::default(); - for recovery_id in [2, 3] { let mut instruction = signed_instruction(&[b"hello secp256k1"]); let offsets = first_offsets(&instruction); @@ -86,7 +75,7 @@ fn passes_supported_overflow_recovery_ids_to_recover() { recovery_id; assert_eq!( - process_instruction(&program_id, &[], &instruction), + process_instruction(0, &instruction, false), Err(ProgramError::InvalidArgument) ); } @@ -94,8 +83,6 @@ fn passes_supported_overflow_recovery_ids_to_recover() { #[test] fn rejects_invalid_recovery_ids() { - let program_id = Pubkey::default(); - for recovery_id in [4, 27, 28, 29, 30] { let mut instruction = signed_instruction(&[b"hello secp256k1"]); let offsets = first_offsets(&instruction); @@ -103,7 +90,7 @@ fn rejects_invalid_recovery_ids() { recovery_id; assert_eq!( - process_instruction(&program_id, &[], &instruction), + process_instruction(0, &instruction, false), Err(ProgramError::InvalidInstructionData) ); } @@ -111,7 +98,6 @@ fn rejects_invalid_recovery_ids() { #[test] fn accepts_malleable_high_s_signature() { - let program_id = Pubkey::default(); let mut instruction = signed_instruction(&[b"hello secp256k1"]); let offsets = first_offsets(&instruction); let signature_start = usize::from(offsets.signature_offset); @@ -124,31 +110,18 @@ fn accepts_malleable_high_s_signature() { instruction[signature_start..signature_end].copy_from_slice(&malleable_signature.to_bytes()); instruction[signature_end] ^= 1; - assert_eq!(process_instruction(&program_id, &[], &instruction), Ok(())); -} - -#[test] -fn rejects_offsets_to_other_instructions() { - let program_id = Pubkey::default(); - let mut instruction = signed_instruction(&[b"hello secp256k1"]); - instruction[1 + 2] = 1; - - assert_eq!( - process_instruction(&program_id, &[], &instruction), - Err(ProgramError::InvalidInstructionData) - ); + assert_eq!(process_instruction(0, &instruction, false), Ok(())); } #[test] fn rejects_out_of_bounds_offsets() { - let program_id = Pubkey::default(); let mut instruction = signed_instruction(&[b"hello secp256k1"]); let mut offsets = first_offsets(&instruction); offsets.message_data_size = u16::MAX; write_offsets(&mut instruction[1..DATA_START], &offsets); assert_eq!( - process_instruction(&program_id, &[], &instruction), + process_instruction(0, &instruction, false), Err(ProgramError::InvalidInstructionData) ); } From 3e81a5cd927bac339fc28b01342f169d3ef863b6 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Tue, 16 Jun 2026 18:15:57 +0900 Subject: [PATCH 2/2] CI --- .spellcheck.toml | 2 ++ Makefile | 4 ++-- program/src/instruction_data.rs | 6 +++--- program/src/lib.rs | 1 + 4 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 .spellcheck.toml diff --git a/.spellcheck.toml b/.spellcheck.toml new file mode 100644 index 0000000..dd4b34b --- /dev/null +++ b/.spellcheck.toml @@ -0,0 +1,2 @@ +[dictionary] +wordlist = ["entrypoint", "ABIv2", "secp256k1", "SBF", "pubkey"] diff --git a/Makefile b/Makefile index cffb4ff..9402098 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ RUST_TOOLCHAIN_NIGHTLY = nightly-2026-01-22 SOLANA_CLI_VERSION = v3.1.10 -SBF_ARCH = v2 +SBF_ARCH = v3 nightly = +${RUST_TOOLCHAIN_NIGHTLY} @@ -44,7 +44,7 @@ build-doc-%: $(ARGS) build-sbf-%: - cargo build-sbf --arch $(SBF_ARCH) --manifest-path $(call make-path,$*)/Cargo.toml -- --locked $(ARGS) + cargo build-sbf --arch $(SBF_ARCH) --abi-v2 --manifest-path $(call make-path,$*)/Cargo.toml -- --locked $(ARGS) test-%: @if [ -f target/deploy/$(call make-so,$*).so ]; then \ diff --git a/program/src/instruction_data.rs b/program/src/instruction_data.rs index 4c98679..a362622 100644 --- a/program/src/instruction_data.rs +++ b/program/src/instruction_data.rs @@ -128,10 +128,10 @@ fn read_array_from_payload( /// /// On-chain (SBF), this uses ABIv2 memory mappings to read sibling instructions. /// Off-chain (native/tests), this requires the target index to be 0 (the current instruction). -pub(crate) fn fetch_instruction_payload<'a>( +pub(crate) fn fetch_instruction_payload( index: u8, - _current_instruction_data: &'a [u8], -) -> Result<&'a [u8], ProgramError> { + _current_instruction_data: &[u8], +) -> Result<&[u8], ProgramError> { #[cfg(any(target_os = "solana", target_arch = "bpf"))] { use solana_transaction_context::{ diff --git a/program/src/lib.rs b/program/src/lib.rs index ff026bd..75fc398 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -172,6 +172,7 @@ pub use instruction::{ pub use processor::process_instruction; /// Program entrypoint for the version 2 instruction-data pointer interface. +#[allow(clippy::missing_safety_doc)] #[unsafe(no_mangle)] pub unsafe extern "C" fn entrypoint() -> u64 { use solana_transaction_context::{