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/allocate_with_seed.rs b/programs/system/src/instructions/allocate_with_seed.rs index 082d8c89..d79a9d26 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, TruncatedInstructionData}; /// Allocate space for and assign an account at an address derived /// from a base public key and a seed. @@ -33,42 +29,40 @@ pub struct AllocateWithSeed<'a, 'b, 'c> { pub owner: &'c Pubkey, } -impl AllocateWithSeed<'_, '_, '_> { - #[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) - // - [.. +8]: account space - // - [.. +32]: owner pubkey - let mut instruction_data = [0; 112]; - 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()); +const N_ACCOUNTS: usize = 2; +const DATA_LEN: usize = 112; - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &instruction_data[..offset + 40], - }; +impl<'a, 'b, 'c> From> + for InvokeParts<'a, N_ACCOUNTS, TruncatedInstructionData> +{ + 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()), + 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)); - invoke_signed(&instruction, &[self.account, self.base], signers) + 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()); + TruncatedInstructionData::new(instruction_data, offset + 40) + }, + } } } 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.rs b/programs/system/src/instructions/transfer.rs index d62a7740..e1d69fae 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::{FullInstructionData, InvokeParts}; /// Transfer lamports. /// @@ -21,32 +18,27 @@ pub struct Transfer<'a> { pub lamports: u64, } -impl Transfer<'_> { - #[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(self.to.key()), - ]; - - // 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) +const N_ACCOUNTS: usize = 2; +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()), + 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()); + 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) + }, + } } } diff --git a/programs/system/src/lib.rs b/programs/system/src/lib.rs index 35268bc1..f1b1261c 100644 --- a/programs/system/src/lib.rs +++ b/programs/system/src/lib.rs @@ -1,5 +1,154 @@ #![no_std] +use core::usize; + +use pinocchio::{ + account_info::AccountInfo, + cpi, + instruction::{AccountMeta, Instruction, Signer}, + pubkey::Pubkey, +}; 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], + pub account_metas: [AccountMeta<'a>; ACCOUNTS], + 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], +} + +impl FullInstructionData { + pub fn new(data: [u8; N]) -> Self { + Self { data } + } +} + +/// 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, +} + +impl TruncatedInstructionData { + pub fn new(data: [u8; N], size: usize) -> Self { + 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; +} + +impl InstructionData for FullInstructionData { + fn as_slice(&self) -> &[u8] { + &self.data + } + + fn len(&self) -> usize { + N + } +} + +impl InstructionData for TruncatedInstructionData { + fn as_slice(&self) -> &[u8] { + &self.data[..self.size] + } + + fn len(&self) -> usize { + self.size + } +} + +/// 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, +{ + fn invoke(self) -> pinocchio::ProgramResult { + self.invoke_signed(&[]) + } + + fn invoke_signed(self, signers: &[Signer]) -> pinocchio::ProgramResult { + invoke_invoker(self.into(), 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 { + invoke_invoker(self.into(), signers, |ix, acc, sigs| unsafe { + cpi::invoke_signed_access_unchecked(&ix, acc, sigs) + }) + } +} + +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>( + invoke_parts: InvokeParts<'a, ACCOUNTS, impl InstructionData>, + signers: &[Signer], + invoker: impl FnOnce(Instruction, &[&AccountInfo; ACCOUNTS], &[Signer]) -> pinocchio::ProgramResult, +) -> pinocchio::ProgramResult { + let instruction = Instruction { + program_id: &invoke_parts.program_id, + accounts: &invoke_parts.account_metas, + data: invoke_parts.instruction_data.as_slice(), + }; + invoker(instruction, &invoke_parts.accounts, signers) +} diff --git a/sdk/pinocchio/src/cpi.rs b/sdk/pinocchio/src/cpi.rs index 47adcdc6..6815e1a1 100644 --- a/sdk/pinocchio/src/cpi.rs +++ b/sdk/pinocchio/src/cpi.rs @@ -182,6 +182,116 @@ 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 { + 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 { + 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.