Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feat : Add token program 2022 #36

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion programs/token/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pinocchio-token"
description = "Pinocchio helpers to invoke Token program instructions"
description = "Pinocchio helpers to invoke Legacy Token and Token-2022 instructions"
version = "0.2.0"
edition = { workspace = true }
license = { workspace = true }
Expand Down
14 changes: 10 additions & 4 deletions programs/token/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# <img width="229" alt="pinocchio-token" src="https://github.com/user-attachments/assets/12b0dc2a-94fb-4866-8e6a-60ac74e13b4f"/>
# <img width="229" alt="pinocchio-token2022" src="https://github.com/user-attachments/assets/12b0dc2a-94fb-4866-8e6a-60ac74e13b4f"/>

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

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

Expand All @@ -11,6 +11,7 @@ This is a `no_std` crate.
## Examples

Initializing a mint account:

```rust
// This example assumes that the instruction receives a writable `mint`
// account; `authority` is a `Pubkey`.
Expand All @@ -20,10 +21,13 @@ InitilizeMint {
decimals: 9,
mint_authority: authority,
freeze_authority: Some(authority),
}.invoke()?;
}.invoke(
TokenProgramVariant::Legacy,
)?;
```

Performing a transfer of tokens:

```rust
// This example assumes that the instruction receives writable `from` and `to`
// accounts, and a signer `authority` account.
Expand All @@ -32,7 +36,9 @@ Transfer {
to,
authority,
amount: 10,
}.invoke()?;
}.invoke(
TokenProgramVariant::Token2022,
)?;
```

## License
Expand Down
110 changes: 110 additions & 0 deletions programs/token/src/extensions/confidential_transfer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
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, TOKEN_2022_PROGRAM_ID, UNINIT_BYTE};

use super::ElagamalPubkey;

// Instructions

/// Initialize a new mint for a confidential transfer.
pub struct InitializeMint<'a> {
pub mint: &'a AccountInfo,
/// Authority to modify the `ConfidentialTransferMint` configuration and to
/// approve new accounts.
pub authority: Option<&'a Pubkey>,
/// Determines if newly configured accounts must be approved by the
/// `authority` before they may be used by the user.
pub auto_approve_new_accounts: bool,
/// New authority to decode any transfer amount in a confidential transfer.
pub auditor_elgamal_pubkey: Option<&'a ElagamalPubkey>,
}

impl<'a> InitializeMint<'a> {
#[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.mint.key())];

// Instruction data layout:
// - [0]: instruction discriminator (1 byte, u8)
let mut instruction_data = [UNINIT_BYTE; 1];

// Set discriminator as u8 at offset [0]
write_bytes(&mut instruction_data, &[0]);

write_bytes(
&mut instruction_data[1..2],
&[self.auto_approve_new_accounts as u8],
);

if let Some(authority) = self.authority {
// Set authority as Pubkey at offset [2..34]
write_bytes(&mut instruction_data[2..3], &[1]);
write_bytes(&mut instruction_data[2..34], authority);
} else {
write_bytes(&mut instruction_data[2..3], &[0]);
}

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

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

pub struct UpdateMint<'a> {
/// Mint Account.
pub mint: &'a AccountInfo,
/// `ConfidentialTransfer` transfer mint authority..
pub mint_authority: &'a Pubkey,
/// Determines if newly configured accounts must be approved by the
/// `authority` before they may be used by the user.
pub auto_approve_new_accounts: bool,
/// New authority to decode any transfer amount in a confidential transfer.
pub auditor_elgamal_pubkey: Option<&'a ElagamalPubkey>,
}

impl<'a> UpdateMint<'a> {
#[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.mint.key())];

// Instruction data layout:
// - [0]: instruction discriminator (1 byte, u8)
// - [1..33]: mint_authority (32 bytes, Pubkey)
let mut instruction_data = [UNINIT_BYTE; 33];

// Set discriminator as u8 at offset [0]
write_bytes(&mut instruction_data, &[27]);
// Set mint_authority as Pubkey at offset [1..33]
write_bytes(&mut instruction_data[1..33], self.mint_authority);

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

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

use pinocchio::{
account_info::{AccountInfo, Ref},
instruction::{AccountMeta, Instruction, Signer},
program::invoke_signed,
program_error::ProgramError,
};

use crate::{write_bytes, TOKEN_2022_PROGRAM_ID, UNINIT_BYTE};

pub struct CpiGuard {
/// Lock privileged token operations from happening via CPI
pub lock_cpi: bool,
}

impl CpiGuard {
/// The length of the `CpiGuard` account data.
pub const LEN: usize = core::mem::size_of::<CpiGuard>();

/// Return a `CpiGuard` from the given account info.
///
/// This method performs owner and length validation on `AccountInfo`, safe borrowing
/// the account data.
#[inline(always)]
pub fn from_account_info(account_info: &AccountInfo) -> Result<Ref<CpiGuard>, ProgramError> {
if account_info.data_len() != Self::LEN {
return Err(ProgramError::InvalidAccountData);
}
if account_info.owner() != &TOKEN_2022_PROGRAM_ID {
return Err(ProgramError::InvalidAccountOwner);
}
Ok(Ref::map(account_info.try_borrow_data()?, |data| unsafe {
Self::from_bytes(data)
}))
}

/// Return a `CpiGuard` from the given account info.
///
/// This method performs owner and length validation on `AccountInfo`, but does not
/// perform the borrow check.
///
/// # Safety
///
/// The caller must ensure that it is safe to borrow the account data – e.g., there are
/// no mutable borrows of the account data.
#[inline]
pub unsafe fn from_account_info_unchecked(
account_info: &AccountInfo,
) -> Result<&Self, ProgramError> {
if account_info.data_len() != Self::LEN {
return Err(ProgramError::InvalidAccountData);
}
if account_info.owner() != &TOKEN_2022_PROGRAM_ID {
return Err(ProgramError::InvalidAccountOwner);
}
Ok(Self::from_bytes(account_info.borrow_data_unchecked()))
}

/// Return a `CpiGuard` from the given bytes.
///
/// # Safety
///
/// The caller must ensure that `bytes` contains a valid representation of `CpiGuard`.
#[inline(always)]
pub unsafe fn from_bytes(bytes: &[u8]) -> &Self {
&*(bytes.as_ptr() as *const &CpiGuard)
}
}

// Instructions
pub struct EnableCpiGuard<'a> {
/// Account to enable the CPI guard
pub account: &'a AccountInfo,
/// The account's owner
pub account_owner: &'a AccountInfo,
}

impl<'a> EnableCpiGuard<'a> {
#[inline(always)]
pub fn invoke(&self) -> Result<(), ProgramError> {
self.invoke_signed(&[])
}

pub fn invoke_signed(&self, signers: &[Signer]) -> Result<(), ProgramError> {
let account_metas = [
AccountMeta::writable(self.account.key()),
AccountMeta::readonly_signer(self.account_owner.key()),
];

// Instruction data Layout:
// - [0]: instruction discriminator (1 byte, u8)
let mut instruction_data = [UNINIT_BYTE; 2];

// Set discriminator as u8 at offset [0]
write_bytes(&mut instruction_data[0..1], &[34]);

// Enable the CPI guard
write_bytes(&mut instruction_data[1..2], &[0]);

let instruction = Instruction {
program_id: &TOKEN_2022_PROGRAM_ID,
accounts: &account_metas,
data: unsafe { core::slice::from_raw_parts(instruction_data.as_ptr() as _, 2) },
};

invoke_signed(&instruction, &[self.account, self.account_owner], signers)?;

Ok(())
}
}

pub struct DisableCpiGuard<'a> {
/// Account to disable the CPI guard
pub account: &'a AccountInfo,
/// The account's owner
pub account_owner: &'a AccountInfo,
}

impl<'a> DisableCpiGuard<'a> {
#[inline(always)]
pub fn invoke(&self) -> Result<(), ProgramError> {
self.invoke_signed(&[])
}

pub fn invoke_signed(&self, signers: &[Signer]) -> Result<(), ProgramError> {
let account_metas = [
AccountMeta::writable(self.account.key()),
AccountMeta::readonly_signer(self.account_owner.key()),
];

// Instruction data Layout:
// - [0]: instruction discriminator (1 byte, u8)
let mut instruction_data = [UNINIT_BYTE; 2];

// Set discriminator as u8 at offset [0]
write_bytes(&mut instruction_data[0..1], &[34]);

// Disable the CPI guard
write_bytes(&mut instruction_data[1..2], &[1]);

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

invoke_signed(&instruction, &[self.account, self.account_owner], signers)?;

Ok(())
}
}
Loading