From b737888612095393efa143e1f426d9a504099d81 Mon Sep 17 00:00:00 2001 From: "Flavio B." Date: Fri, 11 Apr 2025 16:21:18 +0100 Subject: [PATCH 01/11] adds traits and updates transfer call --- programs/system/src/instructions/transfer.rs | 42 +++++++++--------- programs/system/src/lib.rs | 46 ++++++++++++++++++++ 2 files changed, 66 insertions(+), 22 deletions(-) diff --git a/programs/system/src/instructions/transfer.rs b/programs/system/src/instructions/transfer.rs index d62a7740..e3b03490 100644 --- a/programs/system/src/instructions/transfer.rs +++ b/programs/system/src/instructions/transfer.rs @@ -1,9 +1,6 @@ -use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, instruction::AccountMeta}; + +use crate::InstructionParts; /// Transfer lamports. /// @@ -21,32 +18,33 @@ pub struct Transfer<'a> { pub lamports: u64, } -impl Transfer<'_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) +const N_ACCOUNTS: usize = 2; +const N_ACCOUNT_METAS: usize = 2; +const DATA_LEN: usize = 12; + +impl<'a> InstructionParts for Transfer<'a> { + type Accounts = [&'a AccountInfo; N_ACCOUNTS]; + type AccountMetas = [AccountMeta<'a>; N_ACCOUNT_METAS]; + type InstructionData = [u8; DATA_LEN]; + + fn accounts(&self) -> Self::Accounts { + [self.to, self.from] } - pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { - // account metadata - let account_metas: [AccountMeta; 2] = [ + fn account_metas(&self) -> Self::AccountMetas { + [ AccountMeta::writable_signer(self.from.key()), AccountMeta::writable(self.to.key()), - ]; + ] + } + fn instruction_data(&self) -> Self::InstructionData { // instruction data // - [0..4 ]: instruction discriminator // - [4..12]: lamports amount let mut instruction_data = [0; 12]; instruction_data[0] = 2; instruction_data[4..12].copy_from_slice(&self.lamports.to_le_bytes()); - - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &instruction_data, - }; - - invoke_signed(&instruction, &[self.from, self.to], signers) + instruction_data } } diff --git a/programs/system/src/lib.rs b/programs/system/src/lib.rs index 35268bc1..118f4587 100644 --- a/programs/system/src/lib.rs +++ b/programs/system/src/lib.rs @@ -1,5 +1,51 @@ #![no_std] +use pinocchio::{ + account_info::AccountInfo, + cpi, + instruction::{AccountMeta, Instruction, Signer}, +}; pub mod instructions; pinocchio_pubkey::declare_id!("11111111111111111111111111111111"); + +pub trait InstructionParts { + type Accounts; + type AccountMetas; + type InstructionData; + + fn accounts(&self) -> Self::Accounts; + fn account_metas(&self) -> Self::AccountMetas; + fn instruction_data(&self) -> Self::InstructionData; + + fn instruction_data_modifer(data: &[u8]) -> &[u8] { + data + } +} + +pub trait Invoke: Sized { + fn invoke(self) -> pinocchio::ProgramResult { + self.invoke_signed(&[]) + } + + fn invoke_signed(self, signers: &[Signer]) -> pinocchio::ProgramResult; +} + +impl<'a, const N: usize, const M: usize, const J: usize, T> Invoke for T +where + T: InstructionParts< + Accounts = [&'a AccountInfo; N], + AccountMetas = [AccountMeta<'a>; M], + InstructionData = [u8; J], + >, +{ + fn invoke_signed(self, signers: &[Signer]) -> pinocchio::ProgramResult { + let data = self.instruction_data(); + let instruction = Instruction { + program_id: &crate::ID, + accounts: &self.account_metas(), + data: Self::instruction_data_modifer(&data), + }; + cpi::invoke_signed(&instruction, &self.accounts(), signers) + } +} From bde479e7887640642558936e4c99c118117e22ab Mon Sep 17 00:00:00 2001 From: "Flavio B." Date: Fri, 11 Apr 2025 17:02:07 +0100 Subject: [PATCH 02/11] adds unchecked variant --- programs/system/src/lib.rs | 11 ++++ sdk/pinocchio/src/cpi.rs | 114 +++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/programs/system/src/lib.rs b/programs/system/src/lib.rs index 118f4587..ff194432 100644 --- a/programs/system/src/lib.rs +++ b/programs/system/src/lib.rs @@ -29,6 +29,7 @@ pub trait Invoke: Sized { } fn invoke_signed(self, signers: &[Signer]) -> pinocchio::ProgramResult; + unsafe fn invoke_singed_access_unchecked(self, signers: &[Signer]) -> pinocchio::ProgramResult; } impl<'a, const N: usize, const M: usize, const J: usize, T> Invoke for T @@ -48,4 +49,14 @@ where }; cpi::invoke_signed(&instruction, &self.accounts(), signers) } + + unsafe fn invoke_singed_access_unchecked(self, signers: &[Signer]) -> pinocchio::ProgramResult { + let data = self.instruction_data(); + let instruction = Instruction { + program_id: &crate::ID, + accounts: &self.account_metas(), + data: Self::instruction_data_modifer(&data), + }; + cpi::invoke_signed_access_unchecked(&instruction, &self.accounts(), signers) + } } diff --git a/sdk/pinocchio/src/cpi.rs b/sdk/pinocchio/src/cpi.rs index 47adcdc6..a61d306e 100644 --- a/sdk/pinocchio/src/cpi.rs +++ b/sdk/pinocchio/src/cpi.rs @@ -182,6 +182,120 @@ pub fn slice_invoke_signed( Ok(()) } +/// Invoke a cross-program instruction with signatures. +/// +/// # Important +/// +/// The accounts on the `account_infos` slice must be in the same order as the +/// `accounts` field of the `instruction`. +/// +/// This function does not check that [`AccountInfo`]s are properly borrowable. +/// Those checks consume CPU cycles that this function avoids. +/// +/// # Safety +/// +/// If any of the writable accounts passed to the callee contain data that is +/// borrowed within the calling program, and that data is written to by the +/// callee, then Rust's aliasing rules will be violated and cause undefined +/// behavior. +pub unsafe fn invoke_signed_access_unchecked( + instruction: &Instruction, + account_infos: &[&AccountInfo; ACCOUNTS], + signers_seeds: &[Signer], +) -> ProgramResult { + if instruction.accounts.len() < ACCOUNTS { + return Err(ProgramError::NotEnoughAccountKeys); + } + + const UNINIT: MaybeUninit = MaybeUninit::::uninit(); + let mut accounts = [UNINIT; ACCOUNTS]; + + for index in 0..ACCOUNTS { + let account_info = account_infos[index]; + let account_meta = &instruction.accounts[index]; + + if account_info.key() != account_meta.pubkey + || !account_info.is_writable() & account_meta.is_writable + { + return Err(ProgramError::InvalidArgument); + } + + accounts[index].write(Account::from(account_infos[index])); + } + + unsafe { + invoke_signed_unchecked( + instruction, + core::slice::from_raw_parts(accounts.as_ptr() as _, ACCOUNTS), + signers_seeds, + ); + } + + Ok(()) +} + +/// Invoke a cross-program instruction with signatures. +/// +/// # Important +/// +/// The accounts on the `account_infos` slice must be in the same order as the +/// `accounts` field of the `instruction`. +/// +/// This function does not check that [`AccountInfo`]s are properly borrowable. +/// Those checks consume CPU cycles that this function avoids. +/// +/// # Safety +/// +/// If any of the writable accounts passed to the callee contain data that is +/// borrowed within the calling program, and that data is written to by the +/// callee, then Rust's aliasing rules will be violated and cause undefined +/// behavior. +pub unsafe fn slice_invoke_signed_access_unchecked( + instruction: &Instruction, + account_infos: &[&AccountInfo], + signers_seeds: &[Signer], +) -> ProgramResult { + if instruction.accounts.len() < account_infos.len() { + return Err(ProgramError::NotEnoughAccountKeys); + } + + if account_infos.len() > MAX_CPI_ACCOUNTS { + return Err(ProgramError::InvalidArgument); + } + + const UNINIT: MaybeUninit = MaybeUninit::::uninit(); + let mut accounts = [UNINIT; MAX_CPI_ACCOUNTS]; + let mut len = 0; + + for (account_info, account_meta) in account_infos.iter().zip(instruction.accounts.iter()) { + if account_info.key() != account_meta.pubkey + || !account_info.is_writable() & account_meta.is_writable + { + return Err(ProgramError::InvalidArgument); + } + + // SAFETY: The number of accounts has been validated to be less than + // `MAX_CPI_ACCOUNTS`. + unsafe { + accounts + .get_unchecked_mut(len) + .write(Account::from(*account_info)); + } + + len += 1; + } + // SAFETY: The accounts have been validated. + unsafe { + invoke_signed_unchecked( + instruction, + core::slice::from_raw_parts(accounts.as_ptr() as _, len), + signers_seeds, + ); + } + + Ok(()) +} + /// Invoke a cross-program instruction but don't enforce Rust's aliasing rules. /// /// This function does not check that [`Account`]s are properly borrowable. From bd4974e8df0e48e8e389e36c18806765e167caf5 Mon Sep 17 00:00:00 2001 From: "Flavio B." Date: Fri, 11 Apr 2025 19:24:23 +0100 Subject: [PATCH 03/11] improve abstraction --- .../src/instructions/allocate_with_seed.rs | 45 +++++++------- programs/system/src/instructions/transfer.rs | 10 ++-- programs/system/src/lib.rs | 59 +++++++++++-------- 3 files changed, 60 insertions(+), 54 deletions(-) diff --git a/programs/system/src/instructions/allocate_with_seed.rs b/programs/system/src/instructions/allocate_with_seed.rs index 082d8c89..6abeaaaf 100644 --- a/programs/system/src/instructions/allocate_with_seed.rs +++ b/programs/system/src/instructions/allocate_with_seed.rs @@ -1,10 +1,6 @@ -use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - pubkey::Pubkey, - ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey}; + +use crate::InvokeParts; /// Allocate space for and assign an account at an address derived /// from a base public key and a seed. @@ -33,19 +29,27 @@ pub struct AllocateWithSeed<'a, 'b, 'c> { pub owner: &'c Pubkey, } -impl AllocateWithSeed<'_, '_, '_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) +const N_ACCOUNTS: usize = 2; +const N_ACCOUNT_METAS: usize = 2; +const DATA_LEN: usize = 112; + +impl<'a, 'b, 'c> InvokeParts for AllocateWithSeed<'a, 'b, 'c> { + type Accounts = [&'a AccountInfo; N_ACCOUNTS]; + type AccountMetas = [AccountMeta<'a>; N_ACCOUNT_METAS]; + type InstructionData = [u8; DATA_LEN]; + + fn accounts(&self) -> Self::Accounts { + [self.account, self.base] } - pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { - // account metadata - let account_metas: [AccountMeta; 2] = [ + fn account_metas(&self) -> Self::AccountMetas { + [ AccountMeta::writable_signer(self.account.key()), AccountMeta::readonly_signer(self.base.key()), - ]; + ] + } + fn instruction_data(&self) -> (Self::InstructionData, usize) { // instruction data // - [0..4 ]: instruction discriminator // - [4..36 ]: base pubkey @@ -53,7 +57,7 @@ impl AllocateWithSeed<'_, '_, '_> { // - [44.. ]: seed (max 32) // - [.. +8]: account space // - [.. +32]: owner pubkey - let mut instruction_data = [0; 112]; + let mut instruction_data = [0; DATA_LEN]; instruction_data[0] = 9; instruction_data[4..36].copy_from_slice(self.base.key()); instruction_data[36..44].copy_from_slice(&u64::to_le_bytes(self.seed.len() as u64)); @@ -62,13 +66,6 @@ impl AllocateWithSeed<'_, '_, '_> { instruction_data[44..offset].copy_from_slice(self.seed.as_bytes()); instruction_data[offset..offset + 8].copy_from_slice(&self.space.to_le_bytes()); instruction_data[offset + 8..offset + 40].copy_from_slice(self.owner.as_ref()); - - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &instruction_data[..offset + 40], - }; - - invoke_signed(&instruction, &[self.account, self.base], signers) + (instruction_data, offset + 40) } } diff --git a/programs/system/src/instructions/transfer.rs b/programs/system/src/instructions/transfer.rs index e3b03490..25721347 100644 --- a/programs/system/src/instructions/transfer.rs +++ b/programs/system/src/instructions/transfer.rs @@ -1,6 +1,6 @@ use pinocchio::{account_info::AccountInfo, instruction::AccountMeta}; -use crate::InstructionParts; +use crate::InvokeParts; /// Transfer lamports. /// @@ -22,7 +22,7 @@ const N_ACCOUNTS: usize = 2; const N_ACCOUNT_METAS: usize = 2; const DATA_LEN: usize = 12; -impl<'a> InstructionParts for Transfer<'a> { +impl<'a> InvokeParts for Transfer<'a> { type Accounts = [&'a AccountInfo; N_ACCOUNTS]; type AccountMetas = [AccountMeta<'a>; N_ACCOUNT_METAS]; type InstructionData = [u8; DATA_LEN]; @@ -38,13 +38,13 @@ impl<'a> InstructionParts for Transfer<'a> { ] } - fn instruction_data(&self) -> Self::InstructionData { + fn instruction_data(&self) -> (Self::InstructionData, usize) { // instruction data // - [0..4 ]: instruction discriminator // - [4..12]: lamports amount - let mut instruction_data = [0; 12]; + let mut instruction_data = [0; DATA_LEN]; instruction_data[0] = 2; instruction_data[4..12].copy_from_slice(&self.lamports.to_le_bytes()); - instruction_data + (instruction_data, DATA_LEN) } } diff --git a/programs/system/src/lib.rs b/programs/system/src/lib.rs index ff194432..9103f01d 100644 --- a/programs/system/src/lib.rs +++ b/programs/system/src/lib.rs @@ -1,4 +1,6 @@ #![no_std] +use core::usize; + use pinocchio::{ account_info::AccountInfo, cpi, @@ -9,54 +11,61 @@ pub mod instructions; pinocchio_pubkey::declare_id!("11111111111111111111111111111111"); -pub trait InstructionParts { +pub trait InvokeParts { type Accounts; type AccountMetas; type InstructionData; fn accounts(&self) -> Self::Accounts; fn account_metas(&self) -> Self::AccountMetas; - fn instruction_data(&self) -> Self::InstructionData; - - fn instruction_data_modifer(data: &[u8]) -> &[u8] { - data - } + fn instruction_data(&self) -> (Self::InstructionData, usize); } -pub trait Invoke: Sized { +pub trait Invoke: Sized { fn invoke(self) -> pinocchio::ProgramResult { self.invoke_signed(&[]) } - fn invoke_signed(self, signers: &[Signer]) -> pinocchio::ProgramResult; - unsafe fn invoke_singed_access_unchecked(self, signers: &[Signer]) -> pinocchio::ProgramResult; + fn invoke_signed(self, signers: &[Signer]) -> pinocchio::ProgramResult { + self.invoke_invoker(signers, |ix, acc, sigs| cpi::invoke_signed(&ix, acc, sigs)) + } + + unsafe fn invoke_access_unchecked(self) -> pinocchio::ProgramResult { + self.invoke_singed_access_unchecked(&[]) + } + + unsafe fn invoke_singed_access_unchecked(self, signers: &[Signer]) -> pinocchio::ProgramResult { + self.invoke_invoker(signers, |ix, acc, sigs| unsafe { + cpi::invoke_signed_access_unchecked(&ix, acc, sigs) + }) + } + + fn invoke_invoker( + self, + signers: &[Signer], + invoker: impl FnOnce(Instruction, &[&AccountInfo; N], &[Signer]) -> pinocchio::ProgramResult, + ) -> pinocchio::ProgramResult; } -impl<'a, const N: usize, const M: usize, const J: usize, T> Invoke for T +impl<'a, const N: usize, const M: usize, const J: usize, T> Invoke for T where - T: InstructionParts< + T: InvokeParts< Accounts = [&'a AccountInfo; N], AccountMetas = [AccountMeta<'a>; M], InstructionData = [u8; J], >, { - fn invoke_signed(self, signers: &[Signer]) -> pinocchio::ProgramResult { - let data = self.instruction_data(); - let instruction = Instruction { - program_id: &crate::ID, - accounts: &self.account_metas(), - data: Self::instruction_data_modifer(&data), - }; - cpi::invoke_signed(&instruction, &self.accounts(), signers) - } - - unsafe fn invoke_singed_access_unchecked(self, signers: &[Signer]) -> pinocchio::ProgramResult { - let data = self.instruction_data(); + fn invoke_invoker( + self, + signers: &[Signer], + invoker: impl FnOnce(Instruction, &[&AccountInfo; N], &[Signer]) -> pinocchio::ProgramResult, + ) -> pinocchio::ProgramResult { + let (data, end) = self.instruction_data(); let instruction = Instruction { program_id: &crate::ID, accounts: &self.account_metas(), - data: Self::instruction_data_modifer(&data), + data: &data[..end], }; - cpi::invoke_signed_access_unchecked(&instruction, &self.accounts(), signers) + invoker(instruction, &self.accounts(), signers) } } From 755df53ab3c717633c2bd8a70f923b37d5b6c041 Mon Sep 17 00:00:00 2001 From: "Flavio B." Date: Sat, 12 Apr 2025 00:49:47 +0100 Subject: [PATCH 04/11] use concrete type over trait --- .../src/instructions/allocate_with_seed.rs | 65 ++++++++-------- programs/system/src/instructions/transfer.rs | 44 +++++------ programs/system/src/lib.rs | 75 ++++++++++--------- 3 files changed, 89 insertions(+), 95 deletions(-) diff --git a/programs/system/src/instructions/allocate_with_seed.rs b/programs/system/src/instructions/allocate_with_seed.rs index 6abeaaaf..d3d65367 100644 --- a/programs/system/src/instructions/allocate_with_seed.rs +++ b/programs/system/src/instructions/allocate_with_seed.rs @@ -1,6 +1,6 @@ use pinocchio::{account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey}; -use crate::InvokeParts; +use crate::{InstructionData, InvokeParts}; /// Allocate space for and assign an account at an address derived /// from a base public key and a seed. @@ -33,39 +33,36 @@ const N_ACCOUNTS: usize = 2; const N_ACCOUNT_METAS: usize = 2; const DATA_LEN: usize = 112; -impl<'a, 'b, 'c> InvokeParts for AllocateWithSeed<'a, 'b, 'c> { - type Accounts = [&'a AccountInfo; N_ACCOUNTS]; - type AccountMetas = [AccountMeta<'a>; N_ACCOUNT_METAS]; - type InstructionData = [u8; DATA_LEN]; +impl<'a, 'b, 'c> From> + for InvokeParts<'a, N_ACCOUNTS, N_ACCOUNT_METAS, DATA_LEN> +{ + fn from(value: AllocateWithSeed<'a, 'b, 'c>) -> Self { + Self { + accounts: [value.account, value.base], + account_metas: [ + AccountMeta::writable_signer(value.account.key()), + AccountMeta::readonly_signer(value.base.key()), + ], + instruction_data: { + // instruction data + // - [0..4 ]: instruction discriminator + // - [4..36 ]: base pubkey + // - [36..44]: seed length + // - [44.. ]: seed (max 32) + // - [.. +8]: account space + // - [.. +32]: owner pubkey + let mut instruction_data = [0; DATA_LEN]; + instruction_data[0] = 9; + instruction_data[4..36].copy_from_slice(value.base.key()); + instruction_data[36..44] + .copy_from_slice(&u64::to_le_bytes(value.seed.len() as u64)); - fn accounts(&self) -> Self::Accounts { - [self.account, self.base] - } - - fn account_metas(&self) -> Self::AccountMetas { - [ - AccountMeta::writable_signer(self.account.key()), - AccountMeta::readonly_signer(self.base.key()), - ] - } - - fn instruction_data(&self) -> (Self::InstructionData, usize) { - // instruction data - // - [0..4 ]: instruction discriminator - // - [4..36 ]: base pubkey - // - [36..44]: seed length - // - [44.. ]: seed (max 32) - // - [.. +8]: account space - // - [.. +32]: owner pubkey - let mut instruction_data = [0; DATA_LEN]; - instruction_data[0] = 9; - instruction_data[4..36].copy_from_slice(self.base.key()); - instruction_data[36..44].copy_from_slice(&u64::to_le_bytes(self.seed.len() as u64)); - - let offset = 44 + self.seed.len(); - instruction_data[44..offset].copy_from_slice(self.seed.as_bytes()); - instruction_data[offset..offset + 8].copy_from_slice(&self.space.to_le_bytes()); - instruction_data[offset + 8..offset + 40].copy_from_slice(self.owner.as_ref()); - (instruction_data, offset + 40) + let offset = 44 + value.seed.len(); + instruction_data[44..offset].copy_from_slice(value.seed.as_bytes()); + instruction_data[offset..offset + 8].copy_from_slice(&value.space.to_le_bytes()); + instruction_data[offset + 8..offset + 40].copy_from_slice(value.owner.as_ref()); + InstructionData::Truncated((instruction_data, offset + 40)) + }, + } } } diff --git a/programs/system/src/instructions/transfer.rs b/programs/system/src/instructions/transfer.rs index 25721347..aba8d7c7 100644 --- a/programs/system/src/instructions/transfer.rs +++ b/programs/system/src/instructions/transfer.rs @@ -1,6 +1,6 @@ use pinocchio::{account_info::AccountInfo, instruction::AccountMeta}; -use crate::InvokeParts; +use crate::{InstructionData, InvokeParts}; /// Transfer lamports. /// @@ -22,29 +22,23 @@ const N_ACCOUNTS: usize = 2; const N_ACCOUNT_METAS: usize = 2; const DATA_LEN: usize = 12; -impl<'a> InvokeParts for Transfer<'a> { - type Accounts = [&'a AccountInfo; N_ACCOUNTS]; - type AccountMetas = [AccountMeta<'a>; N_ACCOUNT_METAS]; - type InstructionData = [u8; DATA_LEN]; - - fn accounts(&self) -> Self::Accounts { - [self.to, self.from] - } - - fn account_metas(&self) -> Self::AccountMetas { - [ - AccountMeta::writable_signer(self.from.key()), - AccountMeta::writable(self.to.key()), - ] - } - - fn instruction_data(&self) -> (Self::InstructionData, usize) { - // instruction data - // - [0..4 ]: instruction discriminator - // - [4..12]: lamports amount - let mut instruction_data = [0; DATA_LEN]; - instruction_data[0] = 2; - instruction_data[4..12].copy_from_slice(&self.lamports.to_le_bytes()); - (instruction_data, DATA_LEN) +impl<'a> From> for InvokeParts<'a, N_ACCOUNTS, N_ACCOUNT_METAS, DATA_LEN> { + fn from(value: Transfer<'a>) -> Self { + InvokeParts { + accounts: [value.to, value.from], + account_metas: [ + AccountMeta::writable_signer(value.from.key()), + AccountMeta::writable(value.to.key()), + ], + instruction_data: { + // instruction data + // - [0..4 ]: instruction discriminator + // - [4..12]: lamports amount + let mut instruction_data = [0; DATA_LEN]; + instruction_data[0] = 2; + instruction_data[4..12].copy_from_slice(&value.lamports.to_le_bytes()); + InstructionData::Full(instruction_data) + }, + } } } diff --git a/programs/system/src/lib.rs b/programs/system/src/lib.rs index 9103f01d..89dd280e 100644 --- a/programs/system/src/lib.rs +++ b/programs/system/src/lib.rs @@ -11,23 +11,37 @@ pub mod instructions; pinocchio_pubkey::declare_id!("11111111111111111111111111111111"); -pub trait InvokeParts { - type Accounts; - type AccountMetas; - type InstructionData; +pub struct InvokeParts<'a, const N: usize, const M: usize, const J: usize> { + pub accounts: [&'a AccountInfo; N], + pub account_metas: [AccountMeta<'a>; M], + pub instruction_data: InstructionData, +} - fn accounts(&self) -> Self::Accounts; - fn account_metas(&self) -> Self::AccountMetas; - fn instruction_data(&self) -> (Self::InstructionData, usize); +pub enum InstructionData { + Full([u8; N]), + Truncated(([u8; N], usize)), } -pub trait Invoke: Sized { +impl InstructionData { + pub fn data(&self) -> &[u8] { + match *self { + InstructionData::Full(ref data) => data, + InstructionData::Truncated((ref data, end)) => &data[..end], + } + } +} + +pub trait Invoke<'a, const N: usize, const M: usize, const J: usize>: + Into> +{ fn invoke(self) -> pinocchio::ProgramResult { self.invoke_signed(&[]) } fn invoke_signed(self, signers: &[Signer]) -> pinocchio::ProgramResult { - self.invoke_invoker(signers, |ix, acc, sigs| cpi::invoke_signed(&ix, acc, sigs)) + invoke_invoker(self.into(), signers, |ix, acc, sigs| { + cpi::invoke_signed(&ix, acc, sigs) + }) } unsafe fn invoke_access_unchecked(self) -> pinocchio::ProgramResult { @@ -35,37 +49,26 @@ pub trait Invoke: Sized { } unsafe fn invoke_singed_access_unchecked(self, signers: &[Signer]) -> pinocchio::ProgramResult { - self.invoke_invoker(signers, |ix, acc, sigs| unsafe { + invoke_invoker(self.into(), signers, |ix, acc, sigs| unsafe { cpi::invoke_signed_access_unchecked(&ix, acc, sigs) }) } - - fn invoke_invoker( - self, - signers: &[Signer], - invoker: impl FnOnce(Instruction, &[&AccountInfo; N], &[Signer]) -> pinocchio::ProgramResult, - ) -> pinocchio::ProgramResult; } -impl<'a, const N: usize, const M: usize, const J: usize, T> Invoke for T -where - T: InvokeParts< - Accounts = [&'a AccountInfo; N], - AccountMetas = [AccountMeta<'a>; M], - InstructionData = [u8; J], - >, +impl<'a, const N: usize, const M: usize, const J: usize, T> Invoke<'a, N, M, J> for T where + T: Into> { - fn invoke_invoker( - self, - signers: &[Signer], - invoker: impl FnOnce(Instruction, &[&AccountInfo; N], &[Signer]) -> pinocchio::ProgramResult, - ) -> pinocchio::ProgramResult { - let (data, end) = self.instruction_data(); - let instruction = Instruction { - program_id: &crate::ID, - accounts: &self.account_metas(), - data: &data[..end], - }; - invoker(instruction, &self.accounts(), signers) - } +} + +fn invoke_invoker<'a, const N: usize, const M: usize, const J: usize>( + invoke_parts: InvokeParts<'a, N, M, J>, + signers: &[Signer], + invoker: impl FnOnce(Instruction, &[&AccountInfo; N], &[Signer]) -> pinocchio::ProgramResult, +) -> pinocchio::ProgramResult { + let instruction = Instruction { + program_id: &crate::ID, + accounts: &invoke_parts.account_metas, + data: invoke_parts.instruction_data.data(), + }; + invoker(instruction, &invoke_parts.accounts, signers) } From 6f11f52ec7e650f68f005d0a76c35082c05e1900 Mon Sep 17 00:00:00 2001 From: "Flavio B." Date: Sun, 13 Apr 2025 09:26:15 +0100 Subject: [PATCH 05/11] a bit of cleanup --- .../src/instructions/allocate_with_seed.rs | 7 +--- programs/system/src/instructions/transfer.rs | 5 +-- programs/system/src/lib.rs | 37 ++++++++++++------- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/programs/system/src/instructions/allocate_with_seed.rs b/programs/system/src/instructions/allocate_with_seed.rs index d3d65367..f3b71613 100644 --- a/programs/system/src/instructions/allocate_with_seed.rs +++ b/programs/system/src/instructions/allocate_with_seed.rs @@ -30,12 +30,9 @@ pub struct AllocateWithSeed<'a, 'b, 'c> { } const N_ACCOUNTS: usize = 2; -const N_ACCOUNT_METAS: usize = 2; const DATA_LEN: usize = 112; -impl<'a, 'b, 'c> From> - for InvokeParts<'a, N_ACCOUNTS, N_ACCOUNT_METAS, DATA_LEN> -{ +impl<'a, 'b, 'c> From> for InvokeParts<'a, N_ACCOUNTS, DATA_LEN> { fn from(value: AllocateWithSeed<'a, 'b, 'c>) -> Self { Self { accounts: [value.account, value.base], @@ -61,7 +58,7 @@ impl<'a, 'b, 'c> From> instruction_data[44..offset].copy_from_slice(value.seed.as_bytes()); instruction_data[offset..offset + 8].copy_from_slice(&value.space.to_le_bytes()); instruction_data[offset + 8..offset + 40].copy_from_slice(value.owner.as_ref()); - InstructionData::Truncated((instruction_data, offset + 40)) + InstructionData::truncated(instruction_data, offset + 40) }, } } diff --git a/programs/system/src/instructions/transfer.rs b/programs/system/src/instructions/transfer.rs index aba8d7c7..e5b51b3c 100644 --- a/programs/system/src/instructions/transfer.rs +++ b/programs/system/src/instructions/transfer.rs @@ -19,13 +19,12 @@ pub struct Transfer<'a> { } const N_ACCOUNTS: usize = 2; -const N_ACCOUNT_METAS: usize = 2; const DATA_LEN: usize = 12; -impl<'a> From> for InvokeParts<'a, N_ACCOUNTS, N_ACCOUNT_METAS, DATA_LEN> { +impl<'a> From> for InvokeParts<'a, N_ACCOUNTS, DATA_LEN> { fn from(value: Transfer<'a>) -> Self { InvokeParts { - accounts: [value.to, value.from], + accounts: [value.from, value.to], account_metas: [ AccountMeta::writable_signer(value.from.key()), AccountMeta::writable(value.to.key()), diff --git a/programs/system/src/lib.rs b/programs/system/src/lib.rs index 89dd280e..24e32030 100644 --- a/programs/system/src/lib.rs +++ b/programs/system/src/lib.rs @@ -11,10 +11,10 @@ pub mod instructions; pinocchio_pubkey::declare_id!("11111111111111111111111111111111"); -pub struct InvokeParts<'a, const N: usize, const M: usize, const J: usize> { - pub accounts: [&'a AccountInfo; N], - pub account_metas: [AccountMeta<'a>; M], - pub instruction_data: InstructionData, +pub struct InvokeParts<'a, const ACCOUNTS: usize, const DATA_LEN: usize> { + pub accounts: [&'a AccountInfo; ACCOUNTS], + pub account_metas: [AccountMeta<'a>; ACCOUNTS], + pub instruction_data: InstructionData, } pub enum InstructionData { @@ -23,16 +23,27 @@ pub enum InstructionData { } impl InstructionData { - pub fn data(&self) -> &[u8] { + pub fn truncated(data: [u8; N], end: usize) -> Self { + InstructionData::Truncated((data, end)) + } + + pub fn as_slice(&self) -> &[u8] { match *self { InstructionData::Full(ref data) => data, InstructionData::Truncated((ref data, end)) => &data[..end], } } + + pub fn len(&self) -> usize { + match *self { + InstructionData::Full(_) => N, + InstructionData::Truncated((_, len)) => len, + } + } } -pub trait Invoke<'a, const N: usize, const M: usize, const J: usize>: - Into> +pub trait Invoke<'a, const ACCOUNTS: usize, const DATA_LEN: usize>: + Into> { fn invoke(self) -> pinocchio::ProgramResult { self.invoke_signed(&[]) @@ -55,20 +66,20 @@ pub trait Invoke<'a, const N: usize, const M: usize, const J: usize>: } } -impl<'a, const N: usize, const M: usize, const J: usize, T> Invoke<'a, N, M, J> for T where - T: Into> +impl<'a, const ACCOUNTS: usize, const DATA_LEN: usize, T> Invoke<'a, ACCOUNTS, DATA_LEN> for T where + T: Into> { } -fn invoke_invoker<'a, const N: usize, const M: usize, const J: usize>( - invoke_parts: InvokeParts<'a, N, M, J>, +fn invoke_invoker<'a, const ACCOUNTS: usize, const DATA_LEN: usize>( + invoke_parts: InvokeParts<'a, ACCOUNTS, DATA_LEN>, signers: &[Signer], - invoker: impl FnOnce(Instruction, &[&AccountInfo; N], &[Signer]) -> pinocchio::ProgramResult, + invoker: impl FnOnce(Instruction, &[&AccountInfo; ACCOUNTS], &[Signer]) -> pinocchio::ProgramResult, ) -> pinocchio::ProgramResult { let instruction = Instruction { program_id: &crate::ID, accounts: &invoke_parts.account_metas, - data: invoke_parts.instruction_data.data(), + data: invoke_parts.instruction_data.as_slice(), }; invoker(instruction, &invoke_parts.accounts, signers) } From f981f8cca6ee7041dc57c17d583dae6a116fb3b9 Mon Sep 17 00:00:00 2001 From: "Flavio B." Date: Sun, 13 Apr 2025 14:39:09 +0100 Subject: [PATCH 06/11] guarantee monomorphization --- .../src/instructions/allocate_with_seed.rs | 8 +- programs/system/src/instructions/transfer.rs | 6 +- programs/system/src/lib.rs | 77 +++++++++++++------ 3 files changed, 60 insertions(+), 31 deletions(-) diff --git a/programs/system/src/instructions/allocate_with_seed.rs b/programs/system/src/instructions/allocate_with_seed.rs index f3b71613..cddbece3 100644 --- a/programs/system/src/instructions/allocate_with_seed.rs +++ b/programs/system/src/instructions/allocate_with_seed.rs @@ -1,6 +1,6 @@ use pinocchio::{account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey}; -use crate::{InstructionData, InvokeParts}; +use crate::{InvokeParts, TruncatedInstructionData}; /// Allocate space for and assign an account at an address derived /// from a base public key and a seed. @@ -32,7 +32,9 @@ pub struct AllocateWithSeed<'a, 'b, 'c> { const N_ACCOUNTS: usize = 2; const DATA_LEN: usize = 112; -impl<'a, 'b, 'c> From> for InvokeParts<'a, N_ACCOUNTS, DATA_LEN> { +impl<'a, 'b, 'c> From> + for InvokeParts<'a, N_ACCOUNTS, TruncatedInstructionData> +{ fn from(value: AllocateWithSeed<'a, 'b, 'c>) -> Self { Self { accounts: [value.account, value.base], @@ -58,7 +60,7 @@ impl<'a, 'b, 'c> From> for InvokeParts<'a, N_ACCOUN instruction_data[44..offset].copy_from_slice(value.seed.as_bytes()); instruction_data[offset..offset + 8].copy_from_slice(&value.space.to_le_bytes()); instruction_data[offset + 8..offset + 40].copy_from_slice(value.owner.as_ref()); - InstructionData::truncated(instruction_data, offset + 40) + TruncatedInstructionData::new(instruction_data, offset + 40) }, } } diff --git a/programs/system/src/instructions/transfer.rs b/programs/system/src/instructions/transfer.rs index e5b51b3c..0446458e 100644 --- a/programs/system/src/instructions/transfer.rs +++ b/programs/system/src/instructions/transfer.rs @@ -1,6 +1,6 @@ use pinocchio::{account_info::AccountInfo, instruction::AccountMeta}; -use crate::{InstructionData, InvokeParts}; +use crate::{FullInstructionData, InvokeParts}; /// Transfer lamports. /// @@ -21,7 +21,7 @@ pub struct Transfer<'a> { const N_ACCOUNTS: usize = 2; const DATA_LEN: usize = 12; -impl<'a> From> for InvokeParts<'a, N_ACCOUNTS, DATA_LEN> { +impl<'a> From> for InvokeParts<'a, N_ACCOUNTS, FullInstructionData> { fn from(value: Transfer<'a>) -> Self { InvokeParts { accounts: [value.from, value.to], @@ -36,7 +36,7 @@ impl<'a> From> for InvokeParts<'a, N_ACCOUNTS, DATA_LEN> { let mut instruction_data = [0; DATA_LEN]; instruction_data[0] = 2; instruction_data[4..12].copy_from_slice(&value.lamports.to_le_bytes()); - InstructionData::Full(instruction_data) + FullInstructionData::new(instruction_data) }, } } diff --git a/programs/system/src/lib.rs b/programs/system/src/lib.rs index 24e32030..6bc3364f 100644 --- a/programs/system/src/lib.rs +++ b/programs/system/src/lib.rs @@ -11,39 +11,61 @@ pub mod instructions; pinocchio_pubkey::declare_id!("11111111111111111111111111111111"); -pub struct InvokeParts<'a, const ACCOUNTS: usize, const DATA_LEN: usize> { +pub struct InvokeParts<'a, const ACCOUNTS: usize, Data> { pub accounts: [&'a AccountInfo; ACCOUNTS], pub account_metas: [AccountMeta<'a>; ACCOUNTS], - pub instruction_data: InstructionData, + pub instruction_data: Data, } -pub enum InstructionData { - Full([u8; N]), - Truncated(([u8; N], usize)), +pub struct FullInstructionData { + data: [u8; N], } -impl InstructionData { - pub fn truncated(data: [u8; N], end: usize) -> Self { - InstructionData::Truncated((data, end)) +impl FullInstructionData { + pub fn new(data: [u8; N]) -> Self { + Self { data } } +} + +pub struct TruncatedInstructionData { + data: [u8; N], + size: usize, +} + +impl TruncatedInstructionData { + pub fn new(data: [u8; N], size: usize) -> Self { + Self { data, size } + } +} + +pub trait InstructionData { + fn as_slice(&self) -> &[u8]; + fn len(&self) -> usize; +} - pub fn as_slice(&self) -> &[u8] { - match *self { - InstructionData::Full(ref data) => data, - InstructionData::Truncated((ref data, end)) => &data[..end], - } +impl InstructionData for FullInstructionData { + fn as_slice(&self) -> &[u8] { + &self.data } - pub fn len(&self) -> usize { - match *self { - InstructionData::Full(_) => N, - InstructionData::Truncated((_, len)) => len, - } + fn len(&self) -> usize { + N } } -pub trait Invoke<'a, const ACCOUNTS: usize, const DATA_LEN: usize>: - Into> +impl InstructionData for TruncatedInstructionData { + fn as_slice(&self) -> &[u8] { + &self.data[..self.size] + } + + fn len(&self) -> usize { + self.size + } +} + +pub trait Invoke<'a, const ACCOUNTS: usize, Data>: Into> +where + Data: InstructionData, { fn invoke(self) -> pinocchio::ProgramResult { self.invoke_signed(&[]) @@ -66,16 +88,21 @@ pub trait Invoke<'a, const ACCOUNTS: usize, const DATA_LEN: usize>: } } -impl<'a, const ACCOUNTS: usize, const DATA_LEN: usize, T> Invoke<'a, ACCOUNTS, DATA_LEN> for T where - T: Into> +impl<'a, const ACCOUNTS: usize, T, Data> Invoke<'a, ACCOUNTS, Data> for T +where + T: Into>, + Data: InstructionData, { } -fn invoke_invoker<'a, const ACCOUNTS: usize, const DATA_LEN: usize>( - invoke_parts: InvokeParts<'a, ACCOUNTS, DATA_LEN>, +fn invoke_invoker<'a, const ACCOUNTS: usize, Data>( + invoke_parts: InvokeParts<'a, ACCOUNTS, Data>, signers: &[Signer], invoker: impl FnOnce(Instruction, &[&AccountInfo; ACCOUNTS], &[Signer]) -> pinocchio::ProgramResult, -) -> pinocchio::ProgramResult { +) -> pinocchio::ProgramResult +where + Data: InstructionData, +{ let instruction = Instruction { program_id: &crate::ID, accounts: &invoke_parts.account_metas, From 75babeaba8d7ad8010e254a3dee2949239c2810d Mon Sep 17 00:00:00 2001 From: "Flavio B." Date: Sun, 13 Apr 2025 15:41:27 +0100 Subject: [PATCH 07/11] complete generalization --- .../src/instructions/allocate_with_seed.rs | 1 + programs/system/src/instructions/transfer.rs | 1 + programs/system/src/lib.rs | 27 ++++++++++++++----- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/programs/system/src/instructions/allocate_with_seed.rs b/programs/system/src/instructions/allocate_with_seed.rs index cddbece3..d79a9d26 100644 --- a/programs/system/src/instructions/allocate_with_seed.rs +++ b/programs/system/src/instructions/allocate_with_seed.rs @@ -37,6 +37,7 @@ impl<'a, 'b, 'c> From> { fn from(value: AllocateWithSeed<'a, 'b, 'c>) -> Self { Self { + program_id: crate::ID, accounts: [value.account, value.base], account_metas: [ AccountMeta::writable_signer(value.account.key()), diff --git a/programs/system/src/instructions/transfer.rs b/programs/system/src/instructions/transfer.rs index 0446458e..e1d69fae 100644 --- a/programs/system/src/instructions/transfer.rs +++ b/programs/system/src/instructions/transfer.rs @@ -24,6 +24,7 @@ const DATA_LEN: usize = 12; impl<'a> From> for InvokeParts<'a, N_ACCOUNTS, FullInstructionData> { fn from(value: Transfer<'a>) -> Self { InvokeParts { + program_id: crate::ID, accounts: [value.from, value.to], account_metas: [ AccountMeta::writable_signer(value.from.key()), diff --git a/programs/system/src/lib.rs b/programs/system/src/lib.rs index 6bc3364f..89bc77c8 100644 --- a/programs/system/src/lib.rs +++ b/programs/system/src/lib.rs @@ -5,6 +5,7 @@ use pinocchio::{ account_info::AccountInfo, cpi, instruction::{AccountMeta, Instruction, Signer}, + pubkey::Pubkey, }; pub mod instructions; @@ -12,6 +13,7 @@ pub mod instructions; pinocchio_pubkey::declare_id!("11111111111111111111111111111111"); pub struct InvokeParts<'a, const ACCOUNTS: usize, Data> { + pub program_id: Pubkey, pub accounts: [&'a AccountInfo; ACCOUNTS], pub account_metas: [AccountMeta<'a>; ACCOUNTS], pub instruction_data: Data, @@ -32,6 +34,10 @@ pub struct TruncatedInstructionData { size: usize, } +pub struct SliceInstructionData<'a> { + data: &'a [u8], +} + impl TruncatedInstructionData { pub fn new(data: [u8; N], size: usize) -> Self { Self { data, size } @@ -63,6 +69,16 @@ impl InstructionData for TruncatedInstructionData { } } +impl<'a> InstructionData for SliceInstructionData<'a> { + fn as_slice(&self) -> &[u8] { + self.data + } + + fn len(&self) -> usize { + self.data.len() + } +} + pub trait Invoke<'a, const ACCOUNTS: usize, Data>: Into> where Data: InstructionData, @@ -95,16 +111,13 @@ where { } -fn invoke_invoker<'a, const ACCOUNTS: usize, Data>( - invoke_parts: InvokeParts<'a, ACCOUNTS, Data>, +fn invoke_invoker<'a, const ACCOUNTS: usize>( + invoke_parts: InvokeParts<'a, ACCOUNTS, impl InstructionData>, signers: &[Signer], invoker: impl FnOnce(Instruction, &[&AccountInfo; ACCOUNTS], &[Signer]) -> pinocchio::ProgramResult, -) -> pinocchio::ProgramResult -where - Data: InstructionData, -{ +) -> pinocchio::ProgramResult { let instruction = Instruction { - program_id: &crate::ID, + program_id: &invoke_parts.program_id, accounts: &invoke_parts.account_metas, data: invoke_parts.instruction_data.as_slice(), }; From 96cf200e55b5373e817a9f674c51b94c3cb19113 Mon Sep 17 00:00:00 2001 From: "Flavio B." Date: Mon, 14 Apr 2025 09:33:01 +0100 Subject: [PATCH 08/11] small cleanup --- programs/system/src/lib.rs | 12 +++++++++--- sdk/pinocchio/src/cpi.rs | 8 ++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/programs/system/src/lib.rs b/programs/system/src/lib.rs index 89bc77c8..68291275 100644 --- a/programs/system/src/lib.rs +++ b/programs/system/src/lib.rs @@ -34,13 +34,19 @@ pub struct TruncatedInstructionData { size: usize, } +impl TruncatedInstructionData { + pub fn new(data: [u8; N], size: usize) -> Self { + Self { data, size } + } +} + pub struct SliceInstructionData<'a> { data: &'a [u8], } -impl TruncatedInstructionData { - pub fn new(data: [u8; N], size: usize) -> Self { - Self { data, size } +impl<'a> SliceInstructionData<'a> { + pub fn new(data: &'a [u8]) -> Self { + Self { data } } } diff --git a/sdk/pinocchio/src/cpi.rs b/sdk/pinocchio/src/cpi.rs index a61d306e..6815e1a1 100644 --- a/sdk/pinocchio/src/cpi.rs +++ b/sdk/pinocchio/src/cpi.rs @@ -214,9 +214,7 @@ pub unsafe fn invoke_signed_access_unchecked( let account_info = account_infos[index]; let account_meta = &instruction.accounts[index]; - if account_info.key() != account_meta.pubkey - || !account_info.is_writable() & account_meta.is_writable - { + if account_info.key() != account_meta.pubkey { return Err(ProgramError::InvalidArgument); } @@ -268,9 +266,7 @@ pub unsafe fn slice_invoke_signed_access_unchecked( let mut len = 0; for (account_info, account_meta) in account_infos.iter().zip(instruction.accounts.iter()) { - if account_info.key() != account_meta.pubkey - || !account_info.is_writable() & account_meta.is_writable - { + if account_info.key() != account_meta.pubkey { return Err(ProgramError::InvalidArgument); } From 736f759e9d67b1340d8b650eb7757bcd6636ef1f Mon Sep 17 00:00:00 2001 From: "Flavio B." Date: Tue, 15 Apr 2025 09:24:11 +0100 Subject: [PATCH 09/11] finish impl --- .../src/instructions/advance_nonce_account.rs | 54 +++++------- programs/system/src/instructions/allocate.rs | 50 +++++------ programs/system/src/instructions/assign.rs | 50 +++++------ .../src/instructions/assign_with_seed.rs | 75 ++++++++-------- .../instructions/authorize_nonce_account.rs | 62 ++++++------- .../system/src/instructions/create_account.rs | 68 +++++++------- .../instructions/create_account_with_seed.rs | 88 +++++++++---------- .../instructions/initialize_nonce_account.rs | 71 +++++++-------- .../src/instructions/transfer_with_seed.rs | 75 ++++++++-------- .../src/instructions/update_nonce_account.rs | 38 ++++---- .../instructions/withdraw_nonce_account.rs | 79 ++++++++--------- 11 files changed, 314 insertions(+), 396 deletions(-) diff --git a/programs/system/src/instructions/advance_nonce_account.rs b/programs/system/src/instructions/advance_nonce_account.rs index b40d2fe0..6175b7d5 100644 --- a/programs/system/src/instructions/advance_nonce_account.rs +++ b/programs/system/src/instructions/advance_nonce_account.rs @@ -1,9 +1,6 @@ -use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, instruction::AccountMeta}; + +use crate::{FullInstructionData, InvokeParts}; /// Consumes a stored nonce, replacing it with a successor. /// @@ -22,31 +19,26 @@ pub struct AdvanceNonceAccount<'a> { pub authority: &'a AccountInfo, } -impl AdvanceNonceAccount<'_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } - - pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { - // account metadata - let account_metas: [AccountMeta; 3] = [ - AccountMeta::writable(self.account.key()), - AccountMeta::readonly(self.recent_blockhashes_sysvar.key()), - AccountMeta::readonly_signer(self.authority.key()), - ]; - - // instruction - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &[4], - }; +const N_ACCOUNTS: usize = 3; +const DATA_LEN: usize = 1; - invoke_signed( - &instruction, - &[self.account, self.recent_blockhashes_sysvar, self.authority], - signers, - ) +impl<'a> From> + for InvokeParts<'a, N_ACCOUNTS, FullInstructionData> +{ + fn from(value: AdvanceNonceAccount<'a>) -> Self { + InvokeParts { + program_id: crate::ID, + accounts: [ + value.account, + value.recent_blockhashes_sysvar, + value.authority, + ], + account_metas: [ + AccountMeta::writable(value.account.key()), + AccountMeta::readonly(value.recent_blockhashes_sysvar.key()), + AccountMeta::readonly_signer(value.authority.key()), + ], + instruction_data: { FullInstructionData::new([4]) }, + } } } diff --git a/programs/system/src/instructions/allocate.rs b/programs/system/src/instructions/allocate.rs index 0a857cd4..355d7413 100644 --- a/programs/system/src/instructions/allocate.rs +++ b/programs/system/src/instructions/allocate.rs @@ -1,9 +1,6 @@ -use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, instruction::AccountMeta}; + +use crate::{FullInstructionData, InvokeParts}; /// Allocate space in a (possibly new) account without funding. /// @@ -17,29 +14,24 @@ pub struct Allocate<'a> { pub space: u64, } -impl Allocate<'_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } - - pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { - // account metadata - let account_metas: [AccountMeta; 1] = [AccountMeta::writable_signer(self.account.key())]; - - // instruction data - // - [0..4 ]: instruction discriminator - // - [4..12]: space - let mut instruction_data = [0; 12]; - instruction_data[0] = 8; - instruction_data[4..12].copy_from_slice(&self.space.to_le_bytes()); - - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &instruction_data, - }; +const N_ACCOUNTS: usize = 1; +const DATA_LEN: usize = 12; - invoke_signed(&instruction, &[self.account], signers) +impl<'a> From> for InvokeParts<'a, N_ACCOUNTS, FullInstructionData> { + fn from(value: Allocate<'a>) -> Self { + InvokeParts { + program_id: crate::ID, + accounts: [value.account], + account_metas: [AccountMeta::writable_signer(value.account.key())], + instruction_data: { + // instruction data + // - [0..4 ]: instruction discriminator + // - [4..12]: space + let mut instruction_data = [0; DATA_LEN]; + instruction_data[0] = 8; + instruction_data[4..12].copy_from_slice(&value.space.to_le_bytes()); + FullInstructionData::new(instruction_data) + }, + } } } diff --git a/programs/system/src/instructions/assign.rs b/programs/system/src/instructions/assign.rs index f6a1ee53..eabfac8f 100644 --- a/programs/system/src/instructions/assign.rs +++ b/programs/system/src/instructions/assign.rs @@ -1,10 +1,6 @@ -use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - pubkey::Pubkey, - ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey}; + +use crate::{FullInstructionData, InvokeParts}; /// Assign account to a program /// @@ -18,29 +14,25 @@ pub struct Assign<'a, 'b> { pub owner: &'b Pubkey, } -impl Assign<'_, '_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } - - pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { - // account metadata - let account_metas: [AccountMeta; 1] = [AccountMeta::writable_signer(self.account.key())]; - - // instruction data - // - [0..4 ]: instruction discriminator - // - [4..36]: owner pubkey - let mut instruction_data = [0; 36]; - instruction_data[0] = 1; - instruction_data[4..36].copy_from_slice(self.owner.as_ref()); +const N_ACCOUNTS: usize = 1; +const DATA_LEN: usize = 36; - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &instruction_data, - }; +impl<'a, 'b> From> for InvokeParts<'a, N_ACCOUNTS, FullInstructionData> { + fn from(value: Assign<'a, 'b>) -> Self { + InvokeParts { + program_id: crate::ID, + accounts: [value.account], + account_metas: [AccountMeta::writable_signer(value.account.key())], + instruction_data: { + // instruction data + // - [0..4 ]: instruction discriminator + // - [4..36]: owner pubkey + let mut instruction_data = [0; DATA_LEN]; + instruction_data[0] = 1; + instruction_data[4..36].copy_from_slice(value.owner.as_ref()); - invoke_signed(&instruction, &[self.account], signers) + FullInstructionData::new(instruction_data) + }, + } } } diff --git a/programs/system/src/instructions/assign_with_seed.rs b/programs/system/src/instructions/assign_with_seed.rs index d0b280a1..570349c2 100644 --- a/programs/system/src/instructions/assign_with_seed.rs +++ b/programs/system/src/instructions/assign_with_seed.rs @@ -1,10 +1,8 @@ -use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - pubkey::Pubkey, - ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey}; + +use crate::{InvokeParts, TruncatedInstructionData}; + +use super::AllocateWithSeed; /// Assign account to a program based on a seed. /// @@ -29,40 +27,39 @@ pub struct AssignWithSeed<'a, 'b, 'c> { pub owner: &'c Pubkey, } -impl AssignWithSeed<'_, '_, '_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } - - pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { - // account metadata - let account_metas: [AccountMeta; 2] = [ - AccountMeta::writable_signer(self.account.key()), - AccountMeta::readonly_signer(self.base.key()), - ]; - - // instruction data - // - [0..4 ]: instruction discriminator - // - [4..36 ]: base pubkey - // - [36..44]: seed length - // - [44.. ]: seed (max 32) - // - [.. +32]: owner pubkey - let mut instruction_data = [0; 104]; - instruction_data[0] = 10; - instruction_data[4..36].copy_from_slice(self.base.key()); - instruction_data[36..44].copy_from_slice(&u64::to_le_bytes(self.seed.len() as u64)); +const N_ACCOUNTS: usize = 2; +const DATA_LEN: usize = 104; - let offset = 44 + self.seed.len(); - instruction_data[44..offset].copy_from_slice(self.seed.as_bytes()); - instruction_data[offset..offset + 32].copy_from_slice(self.owner.as_ref()); +impl<'a, 'b, 'c> From> + for InvokeParts<'a, N_ACCOUNTS, TruncatedInstructionData> +{ + fn from(value: AllocateWithSeed<'a, 'b, 'c>) -> Self { + InvokeParts { + program_id: crate::ID, + accounts: [value.account, value.base], + account_metas: [ + AccountMeta::writable_signer(value.account.key()), + AccountMeta::readonly_signer(value.base.key()), + ], + instruction_data: { + // instruction data + // - [0..4 ]: instruction discriminator + // - [4..36 ]: base pubkey + // - [36..44]: seed length + // - [44.. ]: seed (max 32) + // - [.. +32]: owner pubkey + let mut instruction_data = [0; DATA_LEN]; + instruction_data[0] = 10; + instruction_data[4..36].copy_from_slice(value.base.key()); + instruction_data[36..44] + .copy_from_slice(&u64::to_le_bytes(value.seed.len() as u64)); - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &instruction_data[..offset + 32], - }; + let offset = 44 + value.seed.len(); + instruction_data[44..offset].copy_from_slice(value.seed.as_bytes()); + instruction_data[offset..offset + 32].copy_from_slice(value.owner.as_ref()); - invoke_signed(&instruction, &[self.account, self.base], signers) + TruncatedInstructionData::new(instruction_data, offset + 32) + }, + } } } diff --git a/programs/system/src/instructions/authorize_nonce_account.rs b/programs/system/src/instructions/authorize_nonce_account.rs index 597e0c0f..149bd13c 100644 --- a/programs/system/src/instructions/authorize_nonce_account.rs +++ b/programs/system/src/instructions/authorize_nonce_account.rs @@ -1,10 +1,6 @@ -use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - pubkey::Pubkey, - ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey}; + +use crate::{FullInstructionData, InvokeParts}; /// Change the entity authorized to execute nonce instructions on the account. /// @@ -24,32 +20,30 @@ pub struct AuthorizeNonceAccount<'a, 'b> { pub new_authority: &'b Pubkey, } -impl AuthorizeNonceAccount<'_, '_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } - - pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { - // account metadata - let account_metas: [AccountMeta; 2] = [ - AccountMeta::writable(self.account.key()), - AccountMeta::readonly_signer(self.authority.key()), - ]; - - // instruction data - // - [0..4 ]: instruction discriminator - // - [4..12]: lamports - let mut instruction_data = [0; 36]; - instruction_data[0] = 7; - instruction_data[4..36].copy_from_slice(self.new_authority); - - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &instruction_data, - }; - - invoke_signed(&instruction, &[self.account, self.authority], signers) +const N_ACCOUNTS: usize = 2; +const DATA_LEN: usize = 36; + +impl<'a, 'b> From> + for InvokeParts<'a, N_ACCOUNTS, FullInstructionData> +{ + fn from(value: AuthorizeNonceAccount<'a, 'b>) -> Self { + InvokeParts { + program_id: crate::ID, + accounts: [value.account, value.authority], + account_metas: [ + AccountMeta::writable(value.account.key()), + AccountMeta::readonly_signer(value.authority.key()), + ], + instruction_data: { + // instruction data + // - [0..4 ]: instruction discriminator + // - [4..12]: lamports + let mut instruction_data = [0; DATA_LEN]; + instruction_data[0] = 7; + instruction_data[4..36].copy_from_slice(value.new_authority); + + FullInstructionData::new(instruction_data) + }, + } } } diff --git a/programs/system/src/instructions/create_account.rs b/programs/system/src/instructions/create_account.rs index c76cab10..23b9d20f 100644 --- a/programs/system/src/instructions/create_account.rs +++ b/programs/system/src/instructions/create_account.rs @@ -1,10 +1,6 @@ -use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - pubkey::Pubkey, - ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey}; + +use crate::{FullInstructionData, InvokeParts}; /// Create a new account. /// @@ -28,36 +24,32 @@ pub struct CreateAccount<'a> { pub owner: &'a Pubkey, } -impl CreateAccount<'_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } - - pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { - // account metadata - let account_metas: [AccountMeta; 2] = [ - AccountMeta::writable_signer(self.from.key()), - AccountMeta::writable_signer(self.to.key()), - ]; - - // instruction data - // - [0..4 ]: instruction discriminator - // - [4..12 ]: lamports - // - [12..20]: account space - // - [20..52]: owner pubkey - let mut instruction_data = [0; 52]; - // create account instruction has a '0' discriminator - instruction_data[4..12].copy_from_slice(&self.lamports.to_le_bytes()); - instruction_data[12..20].copy_from_slice(&self.space.to_le_bytes()); - instruction_data[20..52].copy_from_slice(self.owner.as_ref()); - - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &instruction_data, - }; - - invoke_signed(&instruction, &[self.from, self.to], signers) +const N_ACCOUNTS: usize = 2; +const DATA_LEN: usize = 52; + +impl<'a> From> for InvokeParts<'a, N_ACCOUNTS, FullInstructionData> { + fn from(value: CreateAccount<'a>) -> Self { + InvokeParts { + program_id: crate::ID, + accounts: [value.from, value.to], + account_metas: [ + AccountMeta::writable_signer(value.from.key()), + AccountMeta::writable_signer(value.to.key()), + ], + instruction_data: { + // instruction data + // - [0..4 ]: instruction discriminator + // - [4..12 ]: lamports + // - [12..20]: account space + // - [20..52]: owner pubkey + let mut instruction_data = [0; DATA_LEN]; + // create account instruction has a '0' discriminator + instruction_data[4..12].copy_from_slice(&value.lamports.to_le_bytes()); + instruction_data[12..20].copy_from_slice(&value.space.to_le_bytes()); + instruction_data[20..52].copy_from_slice(value.owner.as_ref()); + + FullInstructionData::new(instruction_data) + }, + } } } diff --git a/programs/system/src/instructions/create_account_with_seed.rs b/programs/system/src/instructions/create_account_with_seed.rs index 929c838e..3974cf53 100644 --- a/programs/system/src/instructions/create_account_with_seed.rs +++ b/programs/system/src/instructions/create_account_with_seed.rs @@ -1,10 +1,6 @@ -use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - pubkey::Pubkey, - ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey}; + +use crate::{InvokeParts, TruncatedInstructionData}; /// Create a new account at an address derived from a base pubkey and a seed. /// @@ -40,49 +36,45 @@ pub struct CreateAccountWithSeed<'a, 'b, 'c> { pub owner: &'c Pubkey, } -impl CreateAccountWithSeed<'_, '_, '_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } - - pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { - // account metadata - let account_metas: [AccountMeta; 3] = [ - AccountMeta::writable_signer(self.from.key()), - AccountMeta::writable(self.to.key()), - AccountMeta::readonly_signer(self.base.unwrap_or(self.from).key()), - ]; - - // instruction data - // - [0..4 ]: instruction discriminator - // - [4..36 ]: base pubkey - // - [36..44]: seed length - // - [44.. ]: seed (max 32) - // - [.. +8]: lamports - // - [.. +8]: account space - // - [.. +32]: owner pubkey - let mut instruction_data = [0; 120]; - instruction_data[0] = 3; - instruction_data[4..36].copy_from_slice(self.base.unwrap_or(self.from).key()); - instruction_data[36..44].copy_from_slice(&u64::to_le_bytes(self.seed.len() as u64)); +const N_ACCOUNTS: usize = 3; +const DATA_LEN: usize = 120; - let offset = 44 + self.seed.len(); - instruction_data[44..offset].copy_from_slice(self.seed.as_bytes()); - instruction_data[offset..offset + 8].copy_from_slice(&self.lamports.to_le_bytes()); - instruction_data[offset + 8..offset + 16].copy_from_slice(&self.space.to_le_bytes()); - instruction_data[offset + 16..offset + 48].copy_from_slice(self.owner.as_ref()); +impl<'a, 'b, 'c> From> + for InvokeParts<'a, N_ACCOUNTS, TruncatedInstructionData> +{ + fn from(value: CreateAccountWithSeed<'a, 'b, 'c>) -> Self { + InvokeParts { + program_id: crate::ID, + accounts: [value.from, value.to, value.base.unwrap_or(value.from)], + account_metas: [ + AccountMeta::writable_signer(value.from.key()), + AccountMeta::writable(value.to.key()), + AccountMeta::readonly_signer(value.base.unwrap_or(value.from).key()), + ], + instruction_data: { + // instruction data + // - [0..4 ]: instruction discriminator + // - [4..36 ]: base pubkey + // - [36..44]: seed length + // - [44.. ]: seed (max 32) + // - [.. +8]: lamports + // - [.. +8]: account space + // - [.. +32]: owner pubkey + let mut instruction_data = [0; DATA_LEN]; + instruction_data[0] = 3; + instruction_data[4..36].copy_from_slice(value.base.unwrap_or(value.from).key()); + instruction_data[36..44] + .copy_from_slice(&u64::to_le_bytes(value.seed.len() as u64)); - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &instruction_data[..offset + 48], - }; + let offset = 44 + value.seed.len(); + instruction_data[44..offset].copy_from_slice(value.seed.as_bytes()); + instruction_data[offset..offset + 8].copy_from_slice(&value.lamports.to_le_bytes()); + instruction_data[offset + 8..offset + 16] + .copy_from_slice(&value.space.to_le_bytes()); + instruction_data[offset + 16..offset + 48].copy_from_slice(value.owner.as_ref()); - invoke_signed( - &instruction, - &[self.from, self.to, self.base.unwrap_or(self.from)], - signers, - ) + TruncatedInstructionData::new(instruction_data, offset + 48) + }, + } } } diff --git a/programs/system/src/instructions/initialize_nonce_account.rs b/programs/system/src/instructions/initialize_nonce_account.rs index 29a3a5f7..d887eda4 100644 --- a/programs/system/src/instructions/initialize_nonce_account.rs +++ b/programs/system/src/instructions/initialize_nonce_account.rs @@ -1,10 +1,6 @@ -use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - pubkey::Pubkey, - ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey}; + +use crate::{FullInstructionData, InvokeParts}; /// Drive state of Uninitialized nonce account to Initialized, setting the nonce value. /// @@ -35,41 +31,34 @@ pub struct InitializeNonceAccount<'a, 'b> { pub authority: &'b Pubkey, } -impl InitializeNonceAccount<'_, '_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } - - pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { - // account metadata - let account_metas: [AccountMeta; 3] = [ - AccountMeta::writable(self.account.key()), - AccountMeta::readonly(self.recent_blockhashes_sysvar.key()), - AccountMeta::readonly(self.rent_sysvar.key()), - ]; +const N_ACCOUNTS: usize = 3; +const DATA_LEN: usize = 36; - // instruction data - // - [0..4 ]: instruction discriminator - // - [4..36]: authority pubkey - let mut instruction_data = [0; 36]; - instruction_data[0] = 6; - instruction_data[4..36].copy_from_slice(self.authority); - - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &instruction_data, - }; - - invoke_signed( - &instruction, - &[ - self.account, - self.recent_blockhashes_sysvar, - self.rent_sysvar, +impl<'a, 'b> From> + for InvokeParts<'a, N_ACCOUNTS, FullInstructionData> +{ + fn from(value: InitializeNonceAccount<'a, 'b>) -> Self { + InvokeParts { + program_id: crate::ID, + accounts: [ + value.account, + value.recent_blockhashes_sysvar, + value.rent_sysvar, + ], + account_metas: [ + AccountMeta::writable(value.account.key()), + AccountMeta::readonly(value.recent_blockhashes_sysvar.key()), + AccountMeta::readonly(value.rent_sysvar.key()), ], - signers, - ) + instruction_data: { + // instruction data + // - [0..4 ]: instruction discriminator + // - [4..36]: authority pubkey + let mut instruction_data = [0; DATA_LEN]; + instruction_data[0] = 6; + instruction_data[4..36].copy_from_slice(value.authority); + FullInstructionData::new(instruction_data) + }, + } } } diff --git a/programs/system/src/instructions/transfer_with_seed.rs b/programs/system/src/instructions/transfer_with_seed.rs index bb943c8c..9bc3eb49 100644 --- a/programs/system/src/instructions/transfer_with_seed.rs +++ b/programs/system/src/instructions/transfer_with_seed.rs @@ -1,10 +1,6 @@ -use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - pubkey::Pubkey, - ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey}; + +use crate::{InvokeParts, TruncatedInstructionData}; /// Transfer lamports from a derived address. /// @@ -36,41 +32,40 @@ pub struct TransferWithSeed<'a, 'b, 'c> { pub owner: &'c Pubkey, } -impl TransferWithSeed<'_, '_, '_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } - - pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { - // account metadata - let account_metas: [AccountMeta; 3] = [ - AccountMeta::writable(self.from.key()), - AccountMeta::readonly_signer(self.base.key()), - AccountMeta::writable(self.to.key()), - ]; - - // instruction data - // - [0..4 ]: instruction discriminator - // - [4..12 ]: lamports amount - // - [12..20]: seed length - // - [20.. ]: seed (max 32) - // - [.. +32]: owner pubkey - let mut instruction_data = [0; 80]; - instruction_data[0] = 11; - instruction_data[4..12].copy_from_slice(&self.lamports.to_le_bytes()); - instruction_data[12..20].copy_from_slice(&u64::to_le_bytes(self.seed.len() as u64)); +const N_ACCOUNTS: usize = 3; +const DATA_LEN: usize = 80; - let offset = 20 + self.seed.len(); - instruction_data[20..offset].copy_from_slice(self.seed.as_bytes()); - instruction_data[offset..offset + 32].copy_from_slice(self.owner.as_ref()); +impl<'a, 'b, 'c> From> + for InvokeParts<'a, N_ACCOUNTS, TruncatedInstructionData> +{ + fn from(value: TransferWithSeed<'a, 'b, 'c>) -> Self { + InvokeParts { + program_id: crate::ID, + accounts: [value.from, value.base, value.to], + account_metas: [ + AccountMeta::writable(value.from.key()), + AccountMeta::readonly_signer(value.base.key()), + AccountMeta::writable(value.to.key()), + ], + instruction_data: { + // instruction data + // - [0..4 ]: instruction discriminator + // - [4..12 ]: lamports amount + // - [12..20]: seed length + // - [20.. ]: seed (max 32) + // - [.. +32]: owner pubkey + let mut instruction_data = [0; DATA_LEN]; + instruction_data[0] = 11; + instruction_data[4..12].copy_from_slice(&value.lamports.to_le_bytes()); + instruction_data[12..20] + .copy_from_slice(&u64::to_le_bytes(value.seed.len() as u64)); - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &instruction_data[..offset + 32], - }; + let offset = 20 + value.seed.len(); + instruction_data[20..offset].copy_from_slice(value.seed.as_bytes()); + instruction_data[offset..offset + 32].copy_from_slice(value.owner.as_ref()); - invoke_signed(&instruction, &[self.from, self.base, self.to], signers) + TruncatedInstructionData::new(instruction_data, offset + 32) + }, + } } } diff --git a/programs/system/src/instructions/update_nonce_account.rs b/programs/system/src/instructions/update_nonce_account.rs index b890c8c2..b8c4042d 100644 --- a/programs/system/src/instructions/update_nonce_account.rs +++ b/programs/system/src/instructions/update_nonce_account.rs @@ -1,9 +1,6 @@ -use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, instruction::AccountMeta}; + +use crate::{FullInstructionData, InvokeParts}; /// One-time idempotent upgrade of legacy nonce versions in order to bump /// them out of chain blockhash domain. @@ -15,23 +12,18 @@ pub struct UpdateNonceAccount<'a> { pub account: &'a AccountInfo, } -impl UpdateNonceAccount<'_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } - - pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { - // account metadata - let account_metas: [AccountMeta; 1] = [AccountMeta::writable(self.account.key())]; - - // instruction - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &[12], - }; +const N_ACCOUNTS: usize = 1; +const DATA_LEN: usize = 1; - invoke_signed(&instruction, &[self.account], signers) +impl<'a> From> + for InvokeParts<'a, N_ACCOUNTS, FullInstructionData> +{ + fn from(value: UpdateNonceAccount<'a>) -> Self { + InvokeParts { + program_id: crate::ID, + accounts: [value.account], + account_metas: [AccountMeta::writable(value.account.key())], + instruction_data: FullInstructionData::new([12]), + } } } diff --git a/programs/system/src/instructions/withdraw_nonce_account.rs b/programs/system/src/instructions/withdraw_nonce_account.rs index e45185e6..05b7ea63 100644 --- a/programs/system/src/instructions/withdraw_nonce_account.rs +++ b/programs/system/src/instructions/withdraw_nonce_account.rs @@ -1,9 +1,6 @@ -use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - ProgramResult, -}; +use pinocchio::{account_info::AccountInfo, instruction::AccountMeta}; + +use crate::{FullInstructionData, InvokeParts}; /// Withdraw funds from a nonce account. /// @@ -39,45 +36,39 @@ pub struct WithdrawNonceAccount<'a> { pub lamports: u64, } -impl WithdrawNonceAccount<'_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } - - pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { - // account metadata - let account_metas: [AccountMeta; 5] = [ - AccountMeta::writable(self.account.key()), - AccountMeta::writable(self.recipient.key()), - AccountMeta::readonly(self.recent_blockhashes_sysvar.key()), - AccountMeta::readonly(self.rent_sysvar.key()), - AccountMeta::readonly_signer(self.authority.key()), - ]; - - // instruction data - // - [0..4 ]: instruction discriminator - // - [4..12]: lamports - let mut instruction_data = [0; 12]; - instruction_data[0] = 5; - instruction_data[4..12].copy_from_slice(&self.lamports.to_le_bytes()); +const N_ACCOUNTS: usize = 5; +const DATA_LEN: usize = 12; - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &instruction_data, - }; - - invoke_signed( - &instruction, - &[ - self.account, - self.recipient, - self.recent_blockhashes_sysvar, - self.rent_sysvar, - self.authority, +impl<'a> From> + for InvokeParts<'a, N_ACCOUNTS, FullInstructionData> +{ + fn from(value: WithdrawNonceAccount<'a>) -> Self { + InvokeParts { + program_id: crate::ID, + accounts: [ + value.account, + value.recipient, + value.recent_blockhashes_sysvar, + value.rent_sysvar, + value.authority, + ], + account_metas: [ + AccountMeta::writable(value.account.key()), + AccountMeta::writable(value.recipient.key()), + AccountMeta::readonly(value.recent_blockhashes_sysvar.key()), + AccountMeta::readonly(value.rent_sysvar.key()), + AccountMeta::readonly_signer(value.authority.key()), ], - signers, - ) + instruction_data: { + // instruction data + // - [0..4 ]: instruction discriminator + // - [4..12]: lamports + let mut instruction_data = [0; DATA_LEN]; + instruction_data[0] = 5; + instruction_data[4..12].copy_from_slice(&value.lamports.to_le_bytes()); + + FullInstructionData::new(instruction_data) + }, + } } } From cce3db72f170ceff61da09e0ad5b8015cd239e17 Mon Sep 17 00:00:00 2001 From: "Flavio B." Date: Tue, 15 Apr 2025 09:41:14 +0100 Subject: [PATCH 10/11] remove slice ix data -- not needed for sys program --- programs/system/src/lib.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/programs/system/src/lib.rs b/programs/system/src/lib.rs index 68291275..9cac1143 100644 --- a/programs/system/src/lib.rs +++ b/programs/system/src/lib.rs @@ -40,16 +40,6 @@ impl TruncatedInstructionData { } } -pub struct SliceInstructionData<'a> { - data: &'a [u8], -} - -impl<'a> SliceInstructionData<'a> { - pub fn new(data: &'a [u8]) -> Self { - Self { data } - } -} - pub trait InstructionData { fn as_slice(&self) -> &[u8]; fn len(&self) -> usize; @@ -75,16 +65,6 @@ impl InstructionData for TruncatedInstructionData { } } -impl<'a> InstructionData for SliceInstructionData<'a> { - fn as_slice(&self) -> &[u8] { - self.data - } - - fn len(&self) -> usize { - self.data.len() - } -} - pub trait Invoke<'a, const ACCOUNTS: usize, Data>: Into> where Data: InstructionData, From d29b7aef1547da03e7b5468925d45a1d21fd8ebf Mon Sep 17 00:00:00 2001 From: "Flavio B." Date: Tue, 15 Apr 2025 10:00:27 +0100 Subject: [PATCH 11/11] adds docs --- programs/system/src/lib.rs | 45 +++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/programs/system/src/lib.rs b/programs/system/src/lib.rs index 9cac1143..f1b1261c 100644 --- a/programs/system/src/lib.rs +++ b/programs/system/src/lib.rs @@ -12,6 +12,10 @@ pub mod instructions; pinocchio_pubkey::declare_id!("11111111111111111111111111111111"); +/// Represents the components required to construct and invoke a Solana instruction. +/// +/// `InvokeParts` is a helper structure that encapsulates all parts of a Solana instruction call, +/// including the program ID, account references, account metadata, and the instruction data payload. pub struct InvokeParts<'a, const ACCOUNTS: usize, Data> { pub program_id: Pubkey, pub accounts: [&'a AccountInfo; ACCOUNTS], @@ -19,6 +23,10 @@ pub struct InvokeParts<'a, const ACCOUNTS: usize, Data> { pub instruction_data: Data, } +/// A fixed-size container for raw instruction data in Solana programs. +/// +/// `FullInstructionData` holds a byte array of length `N` that represents the full +/// serialized instruction data to be sent in a Solana instruction. pub struct FullInstructionData { data: [u8; N], } @@ -29,6 +37,11 @@ impl FullInstructionData { } } +/// A fixed-capacity, variable-length container for instruction data in Solana programs. +/// +/// `TruncatedInstructionData` stores instruction data in a `[u8; N]` buffer, with an explicit `size` +/// indicating how many bytes are actually valid. This allows efficient handling of instruction data +/// without heap allocations, while still supporting "dynamically" sized payloads up to a maximum of `N` bytes. pub struct TruncatedInstructionData { data: [u8; N], size: usize, @@ -39,9 +52,19 @@ impl TruncatedInstructionData { Self { data, size } } } - +/// A trait for types that can provide a view into their instruction data as a byte slice. +/// +/// This trait abstracts over different instruction data representations—fixed-size, truncated, +/// or dynamically sized—allowing them to be used interchangeably in contexts where a raw byte +/// slice is needed (e.g., for constructing or invoking Solana instructions). pub trait InstructionData { + /// Returns a byte slice of the instruction data. fn as_slice(&self) -> &[u8]; + + /// Returns the number of valid bytes in the instruction data. + /// + /// This is equivalent to `self.as_slice().len()`, and can be used to avoid + /// calling `as_slice()` if only the length is needed. fn len(&self) -> usize; } @@ -65,6 +88,26 @@ impl InstructionData for TruncatedInstructionData { } } +/// A trait for types that represent invocable Solana instructions. +/// +/// This trait abstracts over any type that can be converted into [`InvokeParts`], +/// allowing it to be invoked via Solana's CPI (cross-program invocation) mechanisms. +/// +/// It provides safe and unsafe methods for both standard and signed invocations, +/// with support for unchecked access when required. +/// +/// # Blanket Implementation +/// +/// Any type that implements `Into>` automatically +/// implements `Invoke`, as long as `Data: InstructionData`. +/// +/// This makes it easy to define lightweight instruction structs that carry the required +/// invocation data and still support the full CPI interface via this trait. +/// +/// # Safety +/// +/// Unsafe methods are provided for advanced use cases where the caller can guarantee +/// account safety outside of runtime checks. pub trait Invoke<'a, const ACCOUNTS: usize, Data>: Into> where Data: InstructionData,