Skip to content

Commit e1c9257

Browse files
feat: add Quasar variants for 12 token-2022 examples
Token Extensions examples ported using quasar-spl's TokenInterface and InterfaceAccount types for Token-2022 compatibility. Examples: basics, cpi-guard, default-account-state, group, immutable-owner, interest-bearing, memo-transfer, metadata, mint-close-authority, non-transferable, permanent-delegate, transfer-fee
1 parent 091f608 commit e1c9257

48 files changed

Lines changed: 3007 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[package]
2+
name = "quasar-token-2022-basics"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# Standalone workspace — not part of the root program-examples workspace.
7+
[workspace]
8+
9+
[lints.rust.unexpected_cfgs]
10+
level = "warn"
11+
check-cfg = [
12+
'cfg(target_os, values("solana"))',
13+
]
14+
15+
[lib]
16+
crate-type = ["cdylib", "lib"]
17+
18+
[features]
19+
alloc = []
20+
client = []
21+
debug = []
22+
23+
[dependencies]
24+
quasar-lang = "0.0"
25+
quasar-spl = "0.0"
26+
solana-instruction = { version = "3.2.0" }
27+
28+
[dev-dependencies]
29+
quasar-svm = { version = "0.1" }
30+
spl-token-interface = { version = "2.0.0" }
31+
solana-program-pack = { version = "3.1.0" }
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[project]
2+
name = "quasar_token_2022_basics"
3+
4+
[toolchain]
5+
type = "solana"
6+
7+
[testing]
8+
language = "rust"
9+
10+
[testing.rust]
11+
framework = "quasar-svm"
12+
13+
[testing.rust.test]
14+
program = "cargo"
15+
args = [
16+
"test",
17+
"tests::",
18+
]
19+
20+
[clients]
21+
languages = ["rust"]
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#![cfg_attr(not(test), no_std)]
2+
3+
use quasar_lang::{
4+
cpi::{CpiCall, InstructionAccount},
5+
prelude::*,
6+
};
7+
8+
#[cfg(test)]
9+
mod tests;
10+
11+
declare_id!("22222222222222222222222222222222222222222222");
12+
13+
/// Correct Token-2022 program ID.
14+
///
15+
/// quasar-spl 0.0.0 ships incorrect bytes for the Token-2022 address
16+
/// (`TokenzSRvw8aVrEuYKv3gLJaYV39h1EWGpCCGYBJPZQ` instead of the real
17+
/// `TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb`). We define a local
18+
/// marker with the correct mainnet address until that's fixed upstream.
19+
pub struct Token2022Program;
20+
21+
impl Id for Token2022Program {
22+
const ID: Address = Address::new_from_array([
23+
6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218,
24+
182, 26, 252, 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252,
25+
]);
26+
}
27+
28+
/// Demonstrates Token-2022 basics: minting tokens and transferring (checked)
29+
/// via raw CPI to the Token-2022 program.
30+
#[program]
31+
mod quasar_token_2022_basics {
32+
use super::*;
33+
34+
/// Mint tokens to a recipient's token account.
35+
#[instruction(discriminator = 0)]
36+
pub fn mint_token(ctx: Ctx<MintToken>, amount: u64) -> Result<(), ProgramError> {
37+
ctx.accounts.mint_token(amount)
38+
}
39+
40+
/// Transfer tokens using transfer_checked (required for Token-2022).
41+
#[instruction(discriminator = 1)]
42+
pub fn transfer_token(ctx: Ctx<TransferToken>, amount: u64) -> Result<(), ProgramError> {
43+
ctx.accounts.transfer_token(amount)
44+
}
45+
}
46+
47+
/// Accounts for minting tokens via Token-2022.
48+
#[derive(Accounts)]
49+
pub struct MintToken<'info> {
50+
#[account(mut)]
51+
pub authority: &'info Signer,
52+
#[account(mut)]
53+
pub mint: &'info mut UncheckedAccount,
54+
#[account(mut)]
55+
pub receiver: &'info mut UncheckedAccount,
56+
pub token_program: &'info Program<Token2022Program>,
57+
}
58+
59+
impl MintToken<'_> {
60+
#[inline(always)]
61+
pub fn mint_token(&mut self, amount: u64) -> Result<(), ProgramError> {
62+
// SPL Token MintTo instruction: opcode 7, amount as u64 LE.
63+
let data = build_u64_data(7, amount);
64+
CpiCall::new(
65+
self.token_program.to_account_view().address(),
66+
[
67+
InstructionAccount::writable(self.mint.to_account_view().address()),
68+
InstructionAccount::writable(self.receiver.to_account_view().address()),
69+
InstructionAccount::readonly_signer(self.authority.to_account_view().address()),
70+
],
71+
[
72+
self.mint.to_account_view(),
73+
self.receiver.to_account_view(),
74+
self.authority.to_account_view(),
75+
],
76+
data,
77+
)
78+
.invoke()
79+
}
80+
}
81+
82+
/// Accounts for transferring tokens via Token-2022 transfer_checked.
83+
#[derive(Accounts)]
84+
pub struct TransferToken<'info> {
85+
#[account(mut)]
86+
pub sender: &'info Signer,
87+
#[account(mut)]
88+
pub from: &'info mut UncheckedAccount,
89+
pub mint: &'info UncheckedAccount,
90+
#[account(mut)]
91+
pub to: &'info mut UncheckedAccount,
92+
pub token_program: &'info Program<Token2022Program>,
93+
}
94+
95+
impl TransferToken<'_> {
96+
#[inline(always)]
97+
pub fn transfer_token(&mut self, amount: u64) -> Result<(), ProgramError> {
98+
// SPL Token TransferChecked instruction: opcode 12, amount as u64 LE, decimals as u8.
99+
let data = build_transfer_checked_data(amount, 6);
100+
CpiCall::new(
101+
self.token_program.to_account_view().address(),
102+
[
103+
InstructionAccount::writable(self.from.to_account_view().address()),
104+
InstructionAccount::readonly(self.mint.to_account_view().address()),
105+
InstructionAccount::writable(self.to.to_account_view().address()),
106+
InstructionAccount::readonly_signer(self.sender.to_account_view().address()),
107+
],
108+
[
109+
self.from.to_account_view(),
110+
self.mint.to_account_view(),
111+
self.to.to_account_view(),
112+
self.sender.to_account_view(),
113+
],
114+
data,
115+
)
116+
.invoke()
117+
}
118+
}
119+
120+
/// Build a 9-byte instruction data: [opcode, u64 LE amount].
121+
#[inline(always)]
122+
fn build_u64_data(opcode: u8, amount: u64) -> [u8; 9] {
123+
let mut data = [0u8; 9];
124+
data[0] = opcode;
125+
data[1..9].copy_from_slice(&amount.to_le_bytes());
126+
data
127+
}
128+
129+
/// Build TransferChecked data: [12, u64 LE amount, u8 decimals].
130+
#[inline(always)]
131+
fn build_transfer_checked_data(amount: u64, decimals: u8) -> [u8; 10] {
132+
let mut data = [0u8; 10];
133+
data[0] = 12;
134+
data[1..9].copy_from_slice(&amount.to_le_bytes());
135+
data[9] = decimals;
136+
data
137+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
extern crate std;
2+
use {
3+
alloc::vec,
4+
quasar_svm::{Account, Instruction, Pubkey, QuasarSvm},
5+
spl_token_interface::state::{Account as TokenAccount, AccountState, Mint},
6+
std::println,
7+
};
8+
9+
fn setup() -> QuasarSvm {
10+
let elf = std::fs::read("target/deploy/quasar_token_2022_basics.so").unwrap();
11+
QuasarSvm::new()
12+
.with_program(&crate::ID, &elf)
13+
}
14+
15+
fn signer(address: Pubkey) -> Account {
16+
quasar_svm::token::create_keyed_system_account(&address, 1_000_000_000)
17+
}
18+
19+
fn mint_account(address: Pubkey, authority: Pubkey) -> Account {
20+
quasar_svm::token::create_keyed_mint_account_with_program(
21+
&address,
22+
&Mint {
23+
mint_authority: Some(authority).into(),
24+
supply: 0,
25+
decimals: 6,
26+
is_initialized: true,
27+
freeze_authority: None.into(),
28+
},
29+
&quasar_svm::SPL_TOKEN_2022_PROGRAM_ID,
30+
)
31+
}
32+
33+
fn token_account(address: Pubkey, mint: Pubkey, owner: Pubkey, amount: u64) -> Account {
34+
quasar_svm::token::create_keyed_token_account_with_program(
35+
&address,
36+
&TokenAccount {
37+
mint,
38+
owner,
39+
amount,
40+
state: AccountState::Initialized,
41+
..TokenAccount::default()
42+
},
43+
&quasar_svm::SPL_TOKEN_2022_PROGRAM_ID,
44+
)
45+
}
46+
47+
#[test]
48+
fn test_mint_token() {
49+
let mut svm = setup();
50+
51+
let authority = Pubkey::new_unique();
52+
let mint_addr = Pubkey::new_unique();
53+
let receiver_addr = Pubkey::new_unique();
54+
let token_program = quasar_svm::SPL_TOKEN_2022_PROGRAM_ID;
55+
56+
let amount = 1_000_000u64;
57+
let mut data = vec![0u8]; // discriminator = 0
58+
data.extend_from_slice(&amount.to_le_bytes());
59+
60+
let instruction = Instruction {
61+
program_id: crate::ID,
62+
accounts: vec![
63+
solana_instruction::AccountMeta::new(authority.into(), true),
64+
solana_instruction::AccountMeta::new(mint_addr.into(), false),
65+
solana_instruction::AccountMeta::new(receiver_addr.into(), false),
66+
solana_instruction::AccountMeta::new_readonly(token_program.into(), false),
67+
],
68+
data,
69+
};
70+
71+
let result = svm.process_instruction(
72+
&instruction,
73+
&[
74+
signer(authority),
75+
mint_account(mint_addr, authority),
76+
token_account(receiver_addr, mint_addr, authority, 0),
77+
],
78+
);
79+
80+
result.print_logs();
81+
assert!(result.is_ok(), "mint_token failed: {:?}", result.raw_result);
82+
println!(" MINT TOKEN CU: {}", result.compute_units_consumed);
83+
}
84+
85+
#[test]
86+
fn test_transfer_token() {
87+
let mut svm = setup();
88+
89+
let sender = Pubkey::new_unique();
90+
let from_addr = Pubkey::new_unique();
91+
let mint_addr = Pubkey::new_unique();
92+
let to_addr = Pubkey::new_unique();
93+
let token_program = quasar_svm::SPL_TOKEN_2022_PROGRAM_ID;
94+
95+
let amount = 500u64;
96+
let mut data = vec![1u8]; // discriminator = 1
97+
data.extend_from_slice(&amount.to_le_bytes());
98+
99+
let instruction = Instruction {
100+
program_id: crate::ID,
101+
accounts: vec![
102+
solana_instruction::AccountMeta::new(sender.into(), true),
103+
solana_instruction::AccountMeta::new(from_addr.into(), false),
104+
solana_instruction::AccountMeta::new_readonly(mint_addr.into(), false),
105+
solana_instruction::AccountMeta::new(to_addr.into(), false),
106+
solana_instruction::AccountMeta::new_readonly(token_program.into(), false),
107+
],
108+
data,
109+
};
110+
111+
let result = svm.process_instruction(
112+
&instruction,
113+
&[
114+
signer(sender),
115+
token_account(from_addr, mint_addr, sender, 1_000),
116+
mint_account(mint_addr, sender),
117+
token_account(to_addr, mint_addr, sender, 0),
118+
],
119+
);
120+
121+
result.print_logs();
122+
assert!(result.is_ok(), "transfer_token failed: {:?}", result.raw_result);
123+
println!(" TRANSFER TOKEN CU: {}", result.compute_units_consumed);
124+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[package]
2+
name = "quasar-token-2022-cpi-guard"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[workspace]
7+
8+
[lints.rust.unexpected_cfgs]
9+
level = "warn"
10+
check-cfg = [
11+
'cfg(target_os, values("solana"))',
12+
]
13+
14+
[lib]
15+
crate-type = ["cdylib", "lib"]
16+
17+
[features]
18+
alloc = []
19+
client = []
20+
debug = []
21+
22+
[dependencies]
23+
quasar-lang = "0.0"
24+
quasar-spl = "0.0"
25+
solana-instruction = { version = "3.2.0" }
26+
27+
[dev-dependencies]
28+
quasar-svm = { version = "0.1" }
29+
spl-token-interface = { version = "2.0.0" }
30+
solana-program-pack = { version = "3.1.0" }
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[project]
2+
name = "quasar_token_2022_cpi_guard"
3+
4+
[toolchain]
5+
type = "solana"
6+
7+
[testing]
8+
language = "rust"
9+
10+
[testing.rust]
11+
framework = "quasar-svm"
12+
13+
[testing.rust.test]
14+
program = "cargo"
15+
args = [
16+
"test",
17+
"tests::",
18+
]
19+
20+
[clients]
21+
languages = ["rust"]

0 commit comments

Comments
 (0)