Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
resolver = "2"
members = [
"examples/cpi-call",
"programs/associated-token-account",
"programs/memo",
"programs/system",
Expand Down
15 changes: 15 additions & 0 deletions examples/cpi-call/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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" }
23 changes: 23 additions & 0 deletions examples/cpi-call/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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()
}
147 changes: 147 additions & 0 deletions programs/system/src/callback.rs
Original file line number Diff line number Diff line change
@@ -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<T> 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<Accounts = [&'a AccountInfo; ACCOUNTS_LEN]>,
{
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<Account> = MaybeUninit::<Account>::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,
)
}
}
50 changes: 23 additions & 27 deletions programs/system/src/instructions/advance_nonce_account.rs
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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],
)
}
}
41 changes: 21 additions & 20 deletions programs/system/src/instructions/allocate.rs
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -17,30 +16,32 @@ 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
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,
};

invoke_signed(&instruction, &[self.account], signers)
invoke(
&crate::ID,
&[self.account],
&[AccountMeta::writable_signer(self.account.key())],
&instruction_data,
)
}
}
Loading