Skip to content
Merged
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
8 changes: 8 additions & 0 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
Expand Up @@ -5,6 +5,7 @@ members = [
"programs/memo",
"programs/system",
"programs/token",
"programs/token-2022",
"sdk/log/crate",
"sdk/log/macro",
"sdk/pinocchio",
Expand Down
15 changes: 15 additions & 0 deletions programs/token-2022/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "pinocchio-token-2022"
description = "Pinocchio helpers to invoke Token-2022 program instructions"
version = "0.1.0"
edition = { workspace = true }
license = { workspace = true }
readme = "./README.md"
repository = { workspace = true }

[lib]
crate-type = ["rlib"]

[dependencies]
pinocchio = { workspace = true }
pinocchio-pubkey = { workspace = true }
59 changes: 59 additions & 0 deletions programs/token-2022/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<p align="center">
<img alt="pinocchio-token-2022" src="https://github.com/user-attachments/assets/4048fe96-9096-4441-85c3-5deffeb089a6" height="100"/>
</p>
<h3 align="center">
<code>pinocchio-token-2022</code>
</h3>
<p align="center">
<a href="https://crates.io/crates/pinocchio-token-2022"><img src="https://img.shields.io/crates/v/pinocchio-token-2022?logo=rust" /></a>
<a href="https://docs.rs/pinocchio-token-2022"><img src="https://img.shields.io/docsrs/pinocchio-token-2022?logo=docsdotrs" /></a>
</p>

## Overview

This crate contains [`pinocchio`](https://crates.io/crates/pinocchio) helpers to perform cross-program invocations (CPIs) for SPL Token-2022 instructions.

Each instruction defines a `struct` with the accounts and parameters required. Once all values are set, you can call directly `invoke` or `invoke_signed` to perform the CPI.

Instruction that are common to both SPL Token and SPL Token-2022 programs expect the program address, so they can be used to invoke either token program.

This is a `no_std` crate.

> **Note:** The API defined in this crate is subject to change.

## Examples

Initializing a mint account:

```rust
// This example assumes that the instruction receives a writable `mint`
// account; `authority` is a `Pubkey`.
// The SPL Token program is being invoked.
InitializeMint {
mint,
rent_sysvar,
decimals: 9,
mint_authority: authority,
freeze_authority: Some(authority),
token_program: Pubkey::from_str("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
}.invoke()?;
```

Performing a transfer of tokens:

```rust
// This example assumes that the instruction receives writable `from` and `to`
// accounts, and a signer `authority` account.
// The SPL Token-2022 is being invoked.
Transfer {
from,
to,
authority,
amount: 10,
token_program: Pubkey::from_str("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb")
}.invoke()?;
```

## License

The code is licensed under the [Apache License Version 2.0](../LICENSE)
69 changes: 69 additions & 0 deletions programs/token-2022/src/instructions/approve.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use core::slice::from_raw_parts;

use pinocchio::{
account_info::AccountInfo,
instruction::{AccountMeta, Instruction, Signer},
program::invoke_signed,
pubkey::Pubkey,
ProgramResult,
};

use crate::{write_bytes, UNINIT_BYTE};

/// Approves a delegate.
///
/// ### Accounts:
/// 0. `[WRITE]` The token account.
/// 1. `[]` The delegate.
/// 2. `[SIGNER]` The source account owner.
pub struct Approve<'a, 'b> {
/// Source Account.
pub source: &'a AccountInfo,
/// Delegate Account
pub delegate: &'a AccountInfo,
/// Source Owner Account
pub authority: &'a AccountInfo,
/// Amount
pub amount: u64,
/// Token Program
pub token_program: &'b Pubkey,
}

impl Approve<'_, '_> {
#[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.source.key()),
AccountMeta::readonly(self.delegate.key()),
AccountMeta::readonly_signer(self.authority.key()),
];

// Instruction data
// - [0]: instruction discriminator (1 byte, u8)
// - [1..9]: amount (8 bytes, u64)
let mut instruction_data = [UNINIT_BYTE; 9];

// Set discriminator as u8 at offset [0]
write_bytes(&mut instruction_data, &[4]);
// Set amount as u64 at offset [1..9]
write_bytes(&mut instruction_data[1..], &self.amount.to_le_bytes());

let instruction = Instruction {
program_id: self.token_program,
accounts: &account_metas,
data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) },
};

invoke_signed(
&instruction,
&[self.source, self.delegate, self.authority],
signers,
)
}
}
78 changes: 78 additions & 0 deletions programs/token-2022/src/instructions/approve_checked.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use core::slice::from_raw_parts;

use pinocchio::{
account_info::AccountInfo,
instruction::{AccountMeta, Instruction, Signer},
program::invoke_signed,
pubkey::Pubkey,
ProgramResult,
};

use crate::{write_bytes, UNINIT_BYTE};

/// Approves a delegate.
///
/// ### Accounts:
/// 0. `[WRITE]` The source account.
/// 1. `[]` The token mint.
/// 2. `[]` The delegate.
/// 3. `[SIGNER]` The source account owner.
pub struct ApproveChecked<'a, 'b> {
/// Source Account.
pub source: &'a AccountInfo,
/// Mint Account.
pub mint: &'a AccountInfo,
/// Delegate Account.
pub delegate: &'a AccountInfo,
/// Source Owner Account.
pub authority: &'a AccountInfo,
/// Amount.
pub amount: u64,
/// Decimals.
pub decimals: u8,
/// Token Program
pub token_program: &'b Pubkey,
}

impl ApproveChecked<'_, '_> {
#[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; 4] = [
AccountMeta::writable(self.source.key()),
AccountMeta::readonly(self.mint.key()),
AccountMeta::readonly(self.delegate.key()),
AccountMeta::readonly_signer(self.authority.key()),
];

// Instruction data
// - [0] : instruction discriminator (1 byte, u8)
// - [1..9]: amount (8 bytes, u64)
// - [9] : decimals (1 byte, u8)
let mut instruction_data = [UNINIT_BYTE; 10];

// Set discriminator as u8 at offset [0]
write_bytes(&mut instruction_data, &[13]);
// Set amount as u64 at offset [1..9]
write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes());
// Set decimals as u8 at offset [9]
write_bytes(&mut instruction_data[9..], &[self.decimals]);

let instruction = Instruction {
program_id: self.token_program,
accounts: &account_metas,
data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 10) },
};

invoke_signed(
&instruction,
&[self.source, self.mint, self.delegate, self.authority],
signers,
)
}
}
69 changes: 69 additions & 0 deletions programs/token-2022/src/instructions/burn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use core::slice::from_raw_parts;

use pinocchio::{
account_info::AccountInfo,
instruction::{AccountMeta, Instruction, Signer},
program::invoke_signed,
pubkey::Pubkey,
ProgramResult,
};

use crate::{write_bytes, UNINIT_BYTE};

/// Burns tokens by removing them from an account.
///
/// ### Accounts:
/// 0. `[WRITE]` The account to burn from.
/// 1. `[WRITE]` The token mint.
/// 2. `[SIGNER]` The account's owner/delegate.
pub struct Burn<'a, 'b> {
/// Source of the Burn Account
pub account: &'a AccountInfo,
/// Mint Account
pub mint: &'a AccountInfo,
/// Owner of the Token Account
pub authority: &'a AccountInfo,
/// Amount
pub amount: u64,
/// Token Program
pub token_program: &'b Pubkey,
}

impl Burn<'_, '_> {
#[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::writable(self.mint.key()),
AccountMeta::readonly_signer(self.authority.key()),
];

// Instruction data
// - [0]: instruction discriminator (1 byte, u8)
// - [1..9]: amount (8 bytes, u64)
let mut instruction_data = [UNINIT_BYTE; 9];

// Set discriminator as u8 at offset [0]
write_bytes(&mut instruction_data, &[8]);
// Set amount as u64 at offset [1..9]
write_bytes(&mut instruction_data[1..], &self.amount.to_le_bytes());

let instruction = Instruction {
program_id: self.token_program,
accounts: &account_metas,
data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) },
};

invoke_signed(
&instruction,
&[self.account, self.mint, self.authority],
signers,
)
}
}
73 changes: 73 additions & 0 deletions programs/token-2022/src/instructions/burn_checked.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use core::slice::from_raw_parts;

use crate::{write_bytes, UNINIT_BYTE};
use pinocchio::{
account_info::AccountInfo,
instruction::{AccountMeta, Instruction, Signer},
program::invoke_signed,
pubkey::Pubkey,
ProgramResult,
};

/// Burns tokens by removing them from an account.
///
/// ### Accounts:
/// 0. `[WRITE]` The account to burn from.
/// 1. `[WRITE]` The token mint.
/// 2. `[SIGNER]` The account's owner/delegate.
pub struct BurnChecked<'a, 'b> {
/// Source of the Burn Account
pub account: &'a AccountInfo,
/// Mint Account
pub mint: &'a AccountInfo,
/// Owner of the Token Account
pub authority: &'a AccountInfo,
/// Amount
pub amount: u64,
/// Decimals
pub decimals: u8,
/// Token Program
pub token_program: &'b Pubkey,
}

impl BurnChecked<'_, '_> {
#[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::writable(self.mint.key()),
AccountMeta::readonly_signer(self.authority.key()),
];

// Instruction data
// - [0]: instruction discriminator (1 byte, u8)
// - [1..9]: amount (8 bytes, u64)
// - [9]: decimals (1 byte, u8)
let mut instruction_data = [UNINIT_BYTE; 10];

// Set discriminator as u8 at offset [0]
write_bytes(&mut instruction_data, &[15]);
// Set amount as u64 at offset [1..9]
write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes());
// Set decimals as u8 at offset [9]
write_bytes(&mut instruction_data[9..], &[self.decimals]);

let instruction = Instruction {
program_id: self.token_program,
accounts: &account_metas,
data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 10) },
};

invoke_signed(
&instruction,
&[self.account, self.mint, self.authority],
signers,
)
}
}
Loading