diff --git a/Cargo.lock b/Cargo.lock index 5388b80b..510b3f8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "cpi-call" +version = "0.1.0" +dependencies = [ + "pinocchio", + "pinocchio-pubkey", + "pinocchio-system", +] + [[package]] name = "five8_const" version = "0.1.4" @@ -95,9 +104,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] diff --git a/Cargo.toml b/Cargo.toml index 85159322..2f5fa51d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "2" members = [ + "examples/cpi-call", "programs/associated-token-account", "programs/memo", "programs/system", diff --git a/examples/cpi-call/Cargo.toml b/examples/cpi-call/Cargo.toml new file mode 100644 index 00000000..70b08349 --- /dev/null +++ b/examples/cpi-call/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "cpi-call" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[lib] +crate-type = ["cdylib", "lib"] + +[dependencies] +pinocchio = { workspace = true } +pinocchio-pubkey = { workspace = true } +pinocchio-system = { path = "../../programs/system" } diff --git a/examples/cpi-call/src/lib.rs b/examples/cpi-call/src/lib.rs new file mode 100644 index 00000000..008f8c23 --- /dev/null +++ b/examples/cpi-call/src/lib.rs @@ -0,0 +1,23 @@ +#![no_std] + +use pinocchio::{program_error::ProgramError, pubkey::Pubkey}; +use pinocchio_system::{callback::Invoke, instructions::Transfer}; + +use pinocchio::{no_allocator, nostd_panic_handler, program_entrypoint}; + +program_entrypoint!(crate::dispatch); +nostd_panic_handler!(); +no_allocator!(); + +pub fn dispatch<'info>( + _program_id: &Pubkey, + accounts: &'info [pinocchio::account_info::AccountInfo], + _payload: &[u8], +) -> Result<(), ProgramError> { + Transfer { + from: &accounts[0], + to: &accounts[1], + lamports: 1_000_000_000, + } + .invoke() +} diff --git a/programs/system/src/callback.rs b/programs/system/src/callback.rs new file mode 100644 index 00000000..f5f53871 --- /dev/null +++ b/programs/system/src/callback.rs @@ -0,0 +1,147 @@ +use core::{mem::MaybeUninit, slice}; + +use pinocchio::{ + account_info::AccountInfo, + cpi::{self, MAX_CPI_ACCOUNTS}, + instruction::{Account, AccountMeta, Instruction, Signer}, + pubkey::Pubkey, + ProgramResult, +}; + +use crate::instructions::{Transfer, TRANSFER_ACCOUNTS_LEN}; + +mod sealed { + pub trait Sealed {} + impl Sealed for T where T: super::CanInvoke {} +} + +pub trait CanInvoke { + type Accounts; + + fn invoke_via( + &self, + invoke: impl FnOnce( + /* program_id: */ &Pubkey, + /* accounts: */ &Self::Accounts, + /* account_metas: */ &[AccountMeta], + /* data: */ &[u8], + ) -> ProgramResult, + slice_invoke: impl FnOnce( + /* program_id: */ &Pubkey, + /* accounts: */ &[&AccountInfo], + /* account_metas: */ &[AccountMeta], + /* data: */ &[u8], + ) -> ProgramResult, + ) -> ProgramResult; +} + +pub trait Invoke: sealed::Sealed { + fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult; + + unsafe fn invoke_unchecked(&self) { + self.invoke_signed_unchecked(&[]) + } + + unsafe fn invoke_signed_unchecked(&self, signers: &[Signer]); +} + +impl<'a, const ACCOUNTS_LEN: usize, T> Invoke for T +where + T: CanInvoke, +{ + fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + self.invoke_via( + |program_id, accounts, account_metas, data| { + let instruction = Instruction { + program_id, + accounts: &account_metas, + data, + }; + cpi::invoke_signed(&instruction, accounts, signers) + }, + |program_id, accounts, account_metas, data| { + let instruction = Instruction { + program_id, + accounts: &account_metas, + data, + }; + cpi::slice_invoke_signed(&instruction, accounts, signers) + }, + ) + } + unsafe fn invoke_signed_unchecked(&self, signers: &[Signer]) { + self.invoke_via( + |program_id, accounts, account_metas, data| unsafe { + let instruction = Instruction { + program_id, + accounts: &account_metas, + data, + }; + cpi::invoke_signed_unchecked(&instruction, &accounts.map(Account::from), signers); + Ok(()) + }, + |program_id, accounts, account_metas, data| unsafe { + const UNINIT: MaybeUninit = MaybeUninit::::uninit(); + let mut ix_accounts = [UNINIT; MAX_CPI_ACCOUNTS]; + + accounts.iter().enumerate().for_each(|(i, account)| { + ix_accounts[i] = MaybeUninit::new(Account::from(*account)) + }); + + let instruction = Instruction { + program_id, + accounts: &account_metas, + data, + }; + cpi::invoke_signed_unchecked( + &instruction, + slice::from_raw_parts(ix_accounts.as_ptr() as _, accounts.len()), + signers, + ); + Ok(()) + }, + ) + .unwrap(); + } +} + +impl<'a> CanInvoke for Transfer<'a> { + type Accounts = [&'a AccountInfo; TRANSFER_ACCOUNTS_LEN]; + + fn invoke_via( + &self, + invoke: impl FnOnce( + /* program_id: */ &Pubkey, + /* accounts: */ &Self::Accounts, + /* account_metas: */ &[AccountMeta], + /* data: */ &[u8], + ) -> ProgramResult, + _slice_invoke: impl FnOnce( + /* program_id: */ &Pubkey, + /* accounts: */ &[&'a AccountInfo], + /* account_metas: */ &[AccountMeta], + /* data: */ &[u8], + ) -> ProgramResult, + ) -> ProgramResult { + // 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()); + + invoke( + &crate::ID, + &[self.from, self.to], + &[ + AccountMeta::writable_signer(self.from.key()), + AccountMeta::writable(self.to.key()), + ], + &instruction_data, + ) + } +} diff --git a/programs/system/src/instructions/advance_nonce_account.rs b/programs/system/src/instructions/advance_nonce_account.rs index 9b26b1c9..58702a8e 100644 --- a/programs/system/src/instructions/advance_nonce_account.rs +++ b/programs/system/src/instructions/advance_nonce_account.rs @@ -1,10 +1,9 @@ use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - ProgramResult, + account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey, ProgramResult, }; +use crate::CanInvoke; + /// Consumes a stored nonce, replacing it with a successor. /// /// ### Accounts: @@ -22,32 +21,29 @@ pub struct AdvanceNonceAccount<'a> { pub authority: &'a AccountInfo, } -impl AdvanceNonceAccount<'_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } - - #[inline(always)] - 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()), - ]; +const ACCOUNTS_LEN: usize = 3; - // instruction - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &[4], - }; +impl<'a> CanInvoke for AdvanceNonceAccount<'a> { + type Accounts = [&'a AccountInfo; ACCOUNTS_LEN]; - invoke_signed( - &instruction, + fn invoke_via( + &self, + invoke: impl FnOnce( + /* program_id: */ &Pubkey, + /* accounts: */ &Self::Accounts, + /* account_metas: */ &[AccountMeta], + /* data: */ &[u8], + ) -> ProgramResult, + ) -> ProgramResult { + invoke( + &crate::ID, &[self.account, self.recent_blockhashes_sysvar, self.authority], - signers, + &[ + AccountMeta::writable(self.account.key()), + AccountMeta::readonly(self.recent_blockhashes_sysvar.key()), + AccountMeta::readonly_signer(self.authority.key()), + ], + &[4], ) } } diff --git a/programs/system/src/instructions/allocate.rs b/programs/system/src/instructions/allocate.rs index 9ebc57f1..efa44791 100644 --- a/programs/system/src/instructions/allocate.rs +++ b/programs/system/src/instructions/allocate.rs @@ -1,10 +1,9 @@ use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - ProgramResult, + account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey, ProgramResult, }; +use crate::CanInvoke; + /// Allocate space in a (possibly new) account without funding. /// /// ### Accounts: @@ -17,17 +16,20 @@ pub struct Allocate<'a> { pub space: u64, } -impl Allocate<'_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } +const ACCOUNTS_LEN: usize = 1; - #[inline(always)] - pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { - // account metadata - let account_metas: [AccountMeta; 1] = [AccountMeta::writable_signer(self.account.key())]; +impl<'a> CanInvoke for Allocate<'a> { + type Accounts = [&'a AccountInfo; ACCOUNTS_LEN]; + fn invoke_via( + &self, + invoke: impl FnOnce( + /* program_id: */ &Pubkey, + /* accounts: */ &Self::Accounts, + /* account_metas: */ &[AccountMeta], + /* data: */ &[u8], + ) -> ProgramResult, + ) -> ProgramResult { // instruction data // - [0..4 ]: instruction discriminator // - [4..12]: space @@ -35,12 +37,11 @@ impl Allocate<'_> { 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, - }; - - invoke_signed(&instruction, &[self.account], signers) + invoke( + &crate::ID, + &[self.account], + &[AccountMeta::writable_signer(self.account.key())], + &instruction_data, + ) } } diff --git a/programs/system/src/instructions/allocate_with_seed.rs b/programs/system/src/instructions/allocate_with_seed.rs index 423f85b4..3829306a 100644 --- a/programs/system/src/instructions/allocate_with_seed.rs +++ b/programs/system/src/instructions/allocate_with_seed.rs @@ -1,11 +1,9 @@ use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - pubkey::Pubkey, - ProgramResult, + account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey, ProgramResult, }; +use crate::CanInvoke; + /// Allocate space for and assign an account at an address derived /// from a base public key and a seed. /// @@ -33,20 +31,20 @@ pub struct AllocateWithSeed<'a, 'b, 'c> { pub owner: &'c Pubkey, } -impl AllocateWithSeed<'_, '_, '_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } +const ACCOUNTS_LEN: usize = 2; - #[inline(always)] - pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { - // account metadata - let account_metas: [AccountMeta; 2] = [ - AccountMeta::writable(self.account.key()), - AccountMeta::readonly_signer(self.base.key()), - ]; +impl<'a, 'b, 'c> CanInvoke for AllocateWithSeed<'a, 'b, 'c> { + type Accounts = [&'a AccountInfo; ACCOUNTS_LEN]; + fn invoke_via( + &self, + invoke: impl FnOnce( + /* program_id: */ &Pubkey, + /* accounts: */ &Self::Accounts, + /* account_metas: */ &[AccountMeta], + /* data: */ &[u8], + ) -> ProgramResult, + ) -> ProgramResult { // instruction data // - [0..4 ]: instruction discriminator // - [4..36 ]: base pubkey @@ -64,12 +62,14 @@ impl AllocateWithSeed<'_, '_, '_> { 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) + invoke( + &crate::ID, + &[&self.account, &self.base], + &[ + AccountMeta::writable(self.account.key()), + AccountMeta::readonly_signer(self.base.key()), + ], + &instruction_data[..offset + 40], + ) } } diff --git a/programs/system/src/instructions/assign.rs b/programs/system/src/instructions/assign.rs index 327a5d3b..f82ce4e6 100644 --- a/programs/system/src/instructions/assign.rs +++ b/programs/system/src/instructions/assign.rs @@ -1,11 +1,9 @@ use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - pubkey::Pubkey, - ProgramResult, + account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey, ProgramResult, }; +use crate::CanInvoke; + /// Assign account to a program /// /// ### Accounts: @@ -18,17 +16,20 @@ pub struct Assign<'a, 'b> { pub owner: &'b Pubkey, } -impl Assign<'_, '_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } +const ACCOUNTS_LEN: usize = 1; - #[inline(always)] - pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { - // account metadata - let account_metas: [AccountMeta; 1] = [AccountMeta::writable_signer(self.account.key())]; +impl<'a, 'b> CanInvoke for Assign<'a, 'b> { + type Accounts = [&'a AccountInfo; ACCOUNTS_LEN]; + fn invoke_via( + &self, + invoke: impl FnOnce( + /* program_id: */ &Pubkey, + /* accounts: */ &Self::Accounts, + /* account_metas: */ &[AccountMeta], + /* data: */ &[u8], + ) -> ProgramResult, + ) -> ProgramResult { // instruction data // - [0..4 ]: instruction discriminator // - [4..36]: owner pubkey @@ -36,12 +37,11 @@ impl Assign<'_, '_> { instruction_data[0] = 1; instruction_data[4..36].copy_from_slice(self.owner.as_ref()); - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &instruction_data, - }; - - invoke_signed(&instruction, &[self.account], signers) + invoke( + &crate::ID, + &[self.account], + &[AccountMeta::writable_signer(self.account.key())], + &instruction_data, + ) } } diff --git a/programs/system/src/instructions/assign_with_seed.rs b/programs/system/src/instructions/assign_with_seed.rs index 97d27e03..03d824b6 100644 --- a/programs/system/src/instructions/assign_with_seed.rs +++ b/programs/system/src/instructions/assign_with_seed.rs @@ -1,11 +1,9 @@ use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - pubkey::Pubkey, - ProgramResult, + account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey, ProgramResult, }; +use crate::CanInvoke; + /// Assign account to a program based on a seed. /// /// ### Accounts: @@ -29,20 +27,20 @@ pub struct AssignWithSeed<'a, 'b, 'c> { pub owner: &'c Pubkey, } -impl AssignWithSeed<'_, '_, '_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } +const ACCOUNTS_LEN: usize = 2; - #[inline(always)] - pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { - // account metadata - let account_metas: [AccountMeta; 2] = [ - AccountMeta::writable(self.account.key()), - AccountMeta::readonly_signer(self.base.key()), - ]; +impl<'a, 'b, 'c> CanInvoke for AssignWithSeed<'a, 'b, 'c> { + type Accounts = [&'a AccountInfo; ACCOUNTS_LEN]; + fn invoke_via( + &self, + invoke: impl FnOnce( + /* program_id: */ &Pubkey, + /* accounts: */ &Self::Accounts, + /* account_metas: */ &[AccountMeta], + /* data: */ &[u8], + ) -> ProgramResult, + ) -> ProgramResult { // instruction data // - [0..4 ]: instruction discriminator // - [4..36 ]: base pubkey @@ -58,12 +56,14 @@ impl AssignWithSeed<'_, '_, '_> { instruction_data[44..offset].copy_from_slice(self.seed.as_bytes()); instruction_data[offset..offset + 32].copy_from_slice(self.owner.as_ref()); - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &instruction_data[..offset + 32], - }; - - invoke_signed(&instruction, &[self.account, self.base], signers) + invoke( + &crate::ID, + &[self.account, self.base], + &[ + AccountMeta::writable(self.account.key()), + AccountMeta::readonly_signer(self.base.key()), + ], + &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 3402b1d9..1a6568d6 100644 --- a/programs/system/src/instructions/authorize_nonce_account.rs +++ b/programs/system/src/instructions/authorize_nonce_account.rs @@ -1,11 +1,9 @@ use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - pubkey::Pubkey, - ProgramResult, + account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey, ProgramResult, }; +use crate::CanInvoke; + /// Change the entity authorized to execute nonce instructions on the account. /// /// The `Pubkey` parameter identifies the entity to authorize. @@ -24,20 +22,20 @@ pub struct AuthorizeNonceAccount<'a, 'b> { pub new_authority: &'b Pubkey, } -impl AuthorizeNonceAccount<'_, '_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } +const ACCOUNTS_LEN: usize = 2; - #[inline(always)] - 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()), - ]; +impl<'a, 'b> CanInvoke for AuthorizeNonceAccount<'a, 'b> { + type Accounts = [&'a AccountInfo; ACCOUNTS_LEN]; + fn invoke_via( + &self, + invoke: impl FnOnce( + /* program_id: */ &Pubkey, + /* accounts: */ &Self::Accounts, + /* account_metas: */ &[AccountMeta], + /* data: */ &[u8], + ) -> ProgramResult, + ) -> ProgramResult { // instruction data // - [0..4 ]: instruction discriminator // - [4..12]: lamports @@ -45,12 +43,14 @@ impl AuthorizeNonceAccount<'_, '_> { 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) + invoke( + &crate::ID, + &[self.account, self.authority], + &[ + AccountMeta::writable(self.account.key()), + AccountMeta::readonly_signer(self.authority.key()), + ], + &instruction_data, + ) } } diff --git a/programs/system/src/instructions/create_account.rs b/programs/system/src/instructions/create_account.rs index fc804fe8..1279096d 100644 --- a/programs/system/src/instructions/create_account.rs +++ b/programs/system/src/instructions/create_account.rs @@ -1,11 +1,9 @@ use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - pubkey::Pubkey, - ProgramResult, + account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey, ProgramResult, }; +use crate::CanInvoke; + /// Create a new account. /// /// ### Accounts: @@ -28,37 +26,38 @@ pub struct CreateAccount<'a> { pub owner: &'a Pubkey, } -impl CreateAccount<'_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } +const ACCOUNTS_LEN: usize = 2; - #[inline(always)] - 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()), - ]; +impl<'a> CanInvoke for CreateAccount<'a> { + type Accounts = [&'a AccountInfo; ACCOUNTS_LEN]; + fn invoke_via( + &self, + invoke: impl FnOnce( + /* program_id: */ &Pubkey, + /* accounts: */ &Self::Accounts, + /* account_metas: */ &[AccountMeta], + /* data: */ &[u8], + ) -> ProgramResult, + ) -> ProgramResult { // 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) + invoke( + &crate::ID, + &[self.from, self.to], + &[ + AccountMeta::writable_signer(self.from.key()), + AccountMeta::writable_signer(self.to.key()), + ], + &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 dd5ad2c7..07f515ac 100644 --- a/programs/system/src/instructions/create_account_with_seed.rs +++ b/programs/system/src/instructions/create_account_with_seed.rs @@ -1,11 +1,9 @@ use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - pubkey::Pubkey, - ProgramResult, + account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey, ProgramResult, }; +use crate::CanInvoke; + /// Create a new account at an address derived from a base pubkey and a seed. /// /// ### Accounts: @@ -40,21 +38,20 @@ pub struct CreateAccountWithSeed<'a, 'b, 'c> { pub owner: &'c Pubkey, } -impl CreateAccountWithSeed<'_, '_, '_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } +const ACCOUNTS_LEN: usize = 3; - #[inline(always)] - 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()), - ]; +impl<'a, 'b, 'c> CanInvoke for CreateAccountWithSeed<'a, 'b, 'c> { + type Accounts = [&'a AccountInfo; ACCOUNTS_LEN]; + fn invoke_via( + &self, + invoke: impl FnOnce( + /* program_id: */ &Pubkey, + /* accounts: */ &Self::Accounts, + /* account_metas: */ &[AccountMeta], + /* data: */ &[u8], + ) -> ProgramResult, + ) -> ProgramResult { // instruction data // - [0..4 ]: instruction discriminator // - [4..36 ]: base pubkey @@ -74,16 +71,15 @@ impl CreateAccountWithSeed<'_, '_, '_> { 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()); - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &instruction_data[..offset + 48], - }; - - invoke_signed( - &instruction, + invoke( + &crate::ID, &[self.from, self.to, self.base.unwrap_or(self.from)], - signers, + &[ + AccountMeta::writable_signer(self.from.key()), + AccountMeta::writable(self.to.key()), + AccountMeta::readonly_signer(self.base.unwrap_or(self.from).key()), + ], + &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 9ff050da..dacbe0cc 100644 --- a/programs/system/src/instructions/initialize_nonce_account.rs +++ b/programs/system/src/instructions/initialize_nonce_account.rs @@ -1,11 +1,9 @@ use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - pubkey::Pubkey, - ProgramResult, + account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey, ProgramResult, }; +use crate::CanInvoke; + /// Drive state of Uninitialized nonce account to Initialized, setting the nonce value. /// /// The `Pubkey` parameter specifies the entity authorized to execute nonce @@ -35,21 +33,20 @@ pub struct InitializeNonceAccount<'a, 'b> { pub authority: &'b Pubkey, } -impl InitializeNonceAccount<'_, '_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } +const ACCOUNTS_LEN: usize = 3; - #[inline(always)] - 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()), - ]; +impl<'a, 'b> CanInvoke for InitializeNonceAccount<'a, 'b> { + type Accounts = [&'a AccountInfo; ACCOUNTS_LEN]; + fn invoke_via( + &self, + invoke: impl FnOnce( + /* program_id: */ &Pubkey, + /* accounts: */ &Self::Accounts, + /* account_metas: */ &[AccountMeta], + /* data: */ &[u8], + ) -> ProgramResult, + ) -> ProgramResult { // instruction data // - [0..4 ]: instruction discriminator // - [4..36]: authority pubkey @@ -57,20 +54,19 @@ impl InitializeNonceAccount<'_, '_> { 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, + invoke( + &crate::ID, &[ self.account, self.recent_blockhashes_sysvar, self.rent_sysvar, ], - signers, + &[ + AccountMeta::writable(self.account.key()), + AccountMeta::readonly(self.recent_blockhashes_sysvar.key()), + AccountMeta::readonly(self.rent_sysvar.key()), + ], + &instruction_data, ) } } diff --git a/programs/system/src/instructions/transfer.rs b/programs/system/src/instructions/transfer.rs index 0444714b..8b9566e4 100644 --- a/programs/system/src/instructions/transfer.rs +++ b/programs/system/src/instructions/transfer.rs @@ -1,9 +1,4 @@ -use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - ProgramResult, -}; +use pinocchio::account_info::AccountInfo; /// Transfer lamports. /// @@ -21,33 +16,5 @@ pub struct Transfer<'a> { pub lamports: u64, } -impl Transfer<'_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } - - #[inline(always)] - 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) - } -} +pub const TRANSFER_ACCOUNTS_LEN: usize = 2; +pub const TRANSFER_DATA_SIZE: usize = 12; diff --git a/programs/system/src/instructions/transfer_with_seed.rs b/programs/system/src/instructions/transfer_with_seed.rs index 38581078..cd55e8ff 100644 --- a/programs/system/src/instructions/transfer_with_seed.rs +++ b/programs/system/src/instructions/transfer_with_seed.rs @@ -1,11 +1,9 @@ use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - pubkey::Pubkey, - ProgramResult, + account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey, ProgramResult, }; +use crate::CanInvoke; + /// Transfer lamports from a derived address. /// /// ### Accounts: @@ -36,21 +34,20 @@ pub struct TransferWithSeed<'a, 'b, 'c> { pub owner: &'c Pubkey, } -impl TransferWithSeed<'_, '_, '_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } +const ACCOUNTS_LEN: usize = 3; - #[inline(always)] - 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()), - ]; +impl<'a, 'b, 'c> CanInvoke for TransferWithSeed<'a, 'b, 'c> { + type Accounts = [&'a AccountInfo; ACCOUNTS_LEN]; + fn invoke_via( + &self, + invoke: impl FnOnce( + /* program_id: */ &Pubkey, + /* accounts: */ &Self::Accounts, + /* account_metas: */ &[AccountMeta], + /* data: */ &[u8], + ) -> ProgramResult, + ) -> ProgramResult { // instruction data // - [0..4 ]: instruction discriminator // - [4..12 ]: lamports amount @@ -66,12 +63,15 @@ impl TransferWithSeed<'_, '_, '_> { instruction_data[20..offset].copy_from_slice(self.seed.as_bytes()); instruction_data[offset..offset + 32].copy_from_slice(self.owner.as_ref()); - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &instruction_data[..offset + 32], - }; - - invoke_signed(&instruction, &[self.from, self.base, self.to], signers) + invoke( + &crate::ID, + &[self.from, self.base, self.to], + &[ + AccountMeta::writable(self.from.key()), + AccountMeta::readonly_signer(self.base.key()), + AccountMeta::writable(self.to.key()), + ], + &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 92b69aa1..fea0bc26 100644 --- a/programs/system/src/instructions/update_nonce_account.rs +++ b/programs/system/src/instructions/update_nonce_account.rs @@ -1,10 +1,9 @@ use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - ProgramResult, + account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey, ProgramResult, }; +use crate::CanInvoke; + /// One-time idempotent upgrade of legacy nonce versions in order to bump /// them out of chain blockhash domain. /// @@ -15,24 +14,25 @@ pub struct UpdateNonceAccount<'a> { pub account: &'a AccountInfo, } -impl UpdateNonceAccount<'_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } - - #[inline(always)] - pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { - // account metadata - let account_metas: [AccountMeta; 1] = [AccountMeta::writable(self.account.key())]; +const ACCOUNTS_LEN: usize = 1; - // instruction - let instruction = Instruction { - program_id: &crate::ID, - accounts: &account_metas, - data: &[12], - }; +impl<'a> CanInvoke for UpdateNonceAccount<'a> { + type Accounts = [&'a AccountInfo; ACCOUNTS_LEN]; - invoke_signed(&instruction, &[self.account], signers) + fn invoke_via( + &self, + invoke: impl FnOnce( + /* program_id: */ &Pubkey, + /* accounts: */ &Self::Accounts, + /* account_metas: */ &[AccountMeta], + /* data: */ &[u8], + ) -> ProgramResult, + ) -> ProgramResult { + invoke( + &crate::ID, + &[self.account], + &[AccountMeta::writable(self.account.key())], + &[12], + ) } } diff --git a/programs/system/src/instructions/withdraw_nonce_account.rs b/programs/system/src/instructions/withdraw_nonce_account.rs index 2f21593c..8c4a0d99 100644 --- a/programs/system/src/instructions/withdraw_nonce_account.rs +++ b/programs/system/src/instructions/withdraw_nonce_account.rs @@ -1,10 +1,9 @@ use pinocchio::{ - account_info::AccountInfo, - instruction::{AccountMeta, Instruction, Signer}, - program::invoke_signed, - ProgramResult, + account_info::AccountInfo, instruction::AccountMeta, pubkey::Pubkey, ProgramResult, }; +use crate::CanInvoke; + /// Withdraw funds from a nonce account. /// /// The `u64` parameter is the lamports to withdraw, which must leave the @@ -39,23 +38,20 @@ pub struct WithdrawNonceAccount<'a> { pub lamports: u64, } -impl WithdrawNonceAccount<'_> { - #[inline(always)] - pub fn invoke(&self) -> ProgramResult { - self.invoke_signed(&[]) - } +const ACCOUNTS_LEN: usize = 5; - #[inline(always)] - 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()), - ]; +impl<'a> CanInvoke for WithdrawNonceAccount<'a> { + type Accounts = [&'a AccountInfo; ACCOUNTS_LEN]; + fn invoke_via( + &self, + invoke: impl FnOnce( + /* program_id: */ &Pubkey, + /* accounts: */ &Self::Accounts, + /* account_metas: */ &[AccountMeta], + /* data: */ &[u8], + ) -> ProgramResult, + ) -> ProgramResult { // instruction data // - [0..4 ]: instruction discriminator // - [4..12]: lamports @@ -63,14 +59,8 @@ impl WithdrawNonceAccount<'_> { instruction_data[0] = 5; 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, + invoke( + &crate::ID, &[ self.account, self.recipient, @@ -78,7 +68,14 @@ impl WithdrawNonceAccount<'_> { self.rent_sysvar, self.authority, ], - signers, + &[ + 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, ) } } diff --git a/programs/system/src/invoke_parts.rs b/programs/system/src/invoke_parts.rs new file mode 100644 index 00000000..ecdcdf84 --- /dev/null +++ b/programs/system/src/invoke_parts.rs @@ -0,0 +1,160 @@ +use core::{mem::MaybeUninit, slice}; + +use pinocchio::{ + account_info::AccountInfo, + cpi::{self, MAX_CPI_ACCOUNTS}, + instruction::{Account, AccountMeta, Instruction, Signer}, + pubkey::Pubkey, + ProgramResult, +}; + +use crate::instructions::{Transfer, TRANSFER_ACCOUNTS_LEN, TRANSFER_DATA_SIZE}; + +type SliceInvokeParts<'a> = InvokeParts<&'a [&'a AccountInfo], &'a [AccountMeta<'a>], &'a [u8]>; +type FixedInvokeParts<'a, const N: usize, const M: usize> = + InvokeParts<[&'a AccountInfo; N], [AccountMeta<'a>; N], [u8; M]>; + +pub trait InvokePartsType: sealed::Sealed {} +impl InvokePartsType for SliceInvokeParts<'_> {} +impl InvokePartsType for FixedInvokeParts<'_, N, M> {} + +pub struct InvokeParts { + pub program_id: Pubkey, + pub accounts: Accounts, + pub account_metas: Metas, + pub instruction_data: Data, +} + +pub trait IntoInvokeParts { + type Output: InvokePartsType; + fn into_invoke_parts(self) -> Self::Output; +} + +pub trait Invoke: sealed::Sealed + Sized { + #[inline] + fn invoke(self) -> ProgramResult { + self.invoke_signed(&[]) + } + + fn invoke_signed(self, signers: &[Signer]) -> ProgramResult; + + #[inline] + unsafe fn invoke_unchecked(self) { + self.invoke_signed_unchecked(&[]) + } + + unsafe fn invoke_signed_unchecked(self, signers: &[Signer]); +} + +impl Invoke for SliceInvokeParts<'_> { + #[inline] + fn invoke_signed(self, signers: &[Signer]) -> ProgramResult { + cpi::slice_invoke_signed( + &Instruction { + program_id: &self.program_id, + data: &self.instruction_data, + accounts: &self.account_metas, + }, + self.accounts, + signers, + ) + } + + #[inline] + unsafe fn invoke_signed_unchecked(self, signers: &[Signer]) { + const UNINIT: MaybeUninit = MaybeUninit::::uninit(); + let mut accounts = [UNINIT; MAX_CPI_ACCOUNTS]; + + self.accounts + .iter() + .enumerate() + .for_each(|(i, account)| accounts[i] = MaybeUninit::new(Account::from(*account))); + + cpi::invoke_signed_unchecked( + &Instruction { + program_id: &self.program_id, + data: &self.instruction_data, + accounts: &self.account_metas, + }, + slice::from_raw_parts(accounts.as_ptr() as _, self.accounts.len()), + signers, + ) + } +} + +impl Invoke for FixedInvokeParts<'_, N, M> { + #[inline] + fn invoke_signed(self, signers: &[Signer]) -> ProgramResult { + cpi::invoke_signed( + &Instruction { + program_id: &self.program_id, + data: &self.instruction_data, + accounts: &self.account_metas, + }, + &self.accounts, + signers, + ) + } + + #[inline] + unsafe fn invoke_signed_unchecked(self, signers: &[Signer]) { + let accounts = self.accounts.map(Account::from); + cpi::invoke_signed_unchecked( + &Instruction { + program_id: &self.program_id, + data: &self.instruction_data, + accounts: &self.account_metas, + }, + &accounts, + signers, + ) + } +} + +impl Invoke for T +where + T: IntoInvokeParts, + T::Output: Invoke, +{ + #[inline] + fn invoke_signed(self, signers: &[Signer]) -> ProgramResult { + self.into_invoke_parts().invoke_signed(signers) + } + + #[inline] + unsafe fn invoke_signed_unchecked(self, signers: &[Signer]) { + self.into_invoke_parts().invoke_signed_unchecked(signers) + } +} + +mod sealed { + use crate::invoke_parts::{FixedInvokeParts, IntoInvokeParts, SliceInvokeParts}; + + pub trait Sealed {} + impl<'a, const N: usize, const M: usize> Sealed for FixedInvokeParts<'a, N, M> {} + impl<'a> Sealed for SliceInvokeParts<'a> {} + impl Sealed for T where T: IntoInvokeParts {} +} + +impl<'a> IntoInvokeParts for Transfer<'a> { + type Output = FixedInvokeParts<'a, TRANSFER_ACCOUNTS_LEN, TRANSFER_DATA_SIZE>; + + #[inline] + fn into_invoke_parts(self) -> Self::Output { + // 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()); + FixedInvokeParts { + program_id: crate::ID, + accounts: [&self.from, &self.to], + account_metas: [ + AccountMeta::writable_signer(self.from.key()), + AccountMeta::writable(self.to.key()), + ], + instruction_data: instruction_data, + } + } +} diff --git a/programs/system/src/invoker.rs b/programs/system/src/invoker.rs new file mode 100644 index 00000000..85f28c1d --- /dev/null +++ b/programs/system/src/invoker.rs @@ -0,0 +1,139 @@ +use core::marker::PhantomData; + +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Signer}, + pubkey::Pubkey, + ProgramResult, +}; + +use crate::instructions::{Transfer, TRANSFER_ACCOUNTS_LEN}; + +pub type ConstAccounts<'a, const ACCOUNTS_LEN: usize> = [&'a AccountInfo; ACCOUNTS_LEN]; +pub type SliceAccounts<'a> = [&'a AccountInfo]; + +mod sealed { + pub trait Sealed {} + + impl<'a, const ACCOUNTS_LEN: usize> Sealed for super::ConstAccounts<'a, ACCOUNTS_LEN> {} + impl<'a> Sealed for super::SliceAccounts<'a> {} + impl<'a, T, Account> Sealed for super::Invoker<&'a T, Account> {} + impl Sealed for T where T: super::CanInvoke {} +} + +pub trait AccountType: sealed::Sealed {} + +impl<'a, const ACCOUNTS_LEN: usize> AccountType for ConstAccounts<'a, ACCOUNTS_LEN> {} +impl<'a> AccountType for SliceAccounts<'a> {} + +pub trait CanInvoke { + type Accounts: AccountType; + + fn invoke_via( + &self, + invoke: impl FnOnce( + /* program_id: */ &Pubkey, + /* accounts: */ &Self::Accounts, + /* account_metas: */ &[AccountMeta], + /* data: */ &[u8], + ) -> ProgramResult, + ) -> ProgramResult; + + #[inline] + fn as_invoker<'a>(&'a self) -> Invoker<&'a Self, &'a Self::Accounts> + where + Self: Sized, + { + Invoker { + inner: self, + account_ty: PhantomData, + } + } +} + +pub trait Invoke: sealed::Sealed { + fn invoke(&self) -> ProgramResult; + fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult; +} + +pub struct Invoker { + inner: T, + account_ty: PhantomData, +} + +impl<'a, const ACCOUNTS_LEN: usize, T> Invoke for Invoker<&'a T, &ConstAccounts<'a, ACCOUNTS_LEN>> +where + T: CanInvoke>, +{ + #[inline] + fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + #[inline] + fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + self.inner + .invoke_via(|program_id, accounts, account_metas, data| { + let instruction = Instruction { + program_id: program_id, + accounts: &account_metas, + data: data, + }; + pinocchio::cpi::invoke_signed(&instruction, accounts, signers) + }) + } +} + +impl<'a, T> Invoke for Invoker<&'a T, &SliceAccounts<'a>> +where + T: CanInvoke>, +{ + #[inline] + fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + #[inline] + fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { + self.inner + .invoke_via(|program_id, accounts, account_metas, data| { + let instruction = Instruction { + program_id: program_id, + accounts: &account_metas, + data: data, + }; + pinocchio::cpi::slice_invoke_signed(&instruction, accounts, signers) + }) + } +} + +impl<'a> CanInvoke for Transfer<'a> { + type Accounts = [&'a AccountInfo; TRANSFER_ACCOUNTS_LEN]; + + fn invoke_via( + &self, + invoke: impl FnOnce( + /* program_id: */ &Pubkey, + /* accounts: */ &Self::Accounts, + /* account_metas: */ &[AccountMeta], + /* data: */ &[u8], + ) -> ProgramResult, + ) -> ProgramResult { + // 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()); + + invoke( + &crate::ID, + &[self.from, self.to], + &[ + AccountMeta::writable_signer(self.from.key()), + AccountMeta::writable(self.to.key()), + ], + &instruction_data, + ) + } +} diff --git a/programs/system/src/lib.rs b/programs/system/src/lib.rs index 35268bc1..a43da36d 100644 --- a/programs/system/src/lib.rs +++ b/programs/system/src/lib.rs @@ -1,5 +1,10 @@ #![no_std] +pub use invoker::*; + +pub mod callback; pub mod instructions; +pub mod invoke_parts; +pub mod invoker; pinocchio_pubkey::declare_id!("11111111111111111111111111111111");