Skip to content

Commit b274635

Browse files
authored
fix: ctoken create mint cpi context address tree check (#2039)
* fix: ctoken address Merkle tree check with cpi context * test: failing write to cpi context
1 parent d83518e commit b274635

File tree

14 files changed

+610
-57
lines changed

14 files changed

+610
-57
lines changed
Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
1-
use std::slice;
2-
31
use aligned_sized::aligned_sized;
42
use anchor_lang::prelude::*;
5-
use light_compressed_account::instruction_data::{
6-
invoke_cpi::InstructionDataInvokeCpi, zero_copy::ZInstructionDataInvokeCpi,
7-
};
8-
use light_zero_copy::{errors::ZeroCopyError, traits::ZeroCopyAt};
9-
use zerocopy::{little_endian::U32, Ref};
3+
use light_compressed_account::instruction_data::invoke_cpi::InstructionDataInvokeCpi;
104

115
/// Collects instruction data without executing a compressed transaction.
126
/// Signer checks are performed on instruction data.
@@ -40,33 +34,3 @@ impl CpiContextAccount {
4034
self.context = Vec::new();
4135
}
4236
}
43-
44-
pub struct ZCpiContextAccount2<'a> {
45-
pub fee_payer: Ref<&'a mut [u8], light_compressed_account::pubkey::Pubkey>,
46-
pub associated_merkle_tree: Ref<&'a mut [u8], light_compressed_account::pubkey::Pubkey>,
47-
pub context: Vec<ZInstructionDataInvokeCpi<'a>>,
48-
}
49-
50-
pub fn deserialize_cpi_context_account<'info, 'a>(
51-
account_info: &AccountInfo<'info>,
52-
) -> std::result::Result<ZCpiContextAccount2<'a>, ZeroCopyError> {
53-
let mut account_data = account_info.try_borrow_mut_data().unwrap();
54-
let data = unsafe { slice::from_raw_parts_mut(account_data.as_mut_ptr(), account_data.len()) };
55-
let (fee_payer, data) =
56-
Ref::<&'a mut [u8], light_compressed_account::pubkey::Pubkey>::from_prefix(&mut data[8..])?;
57-
let (associated_merkle_tree, data) =
58-
Ref::<&'a mut [u8], light_compressed_account::pubkey::Pubkey>::from_prefix(data)?;
59-
let (len, data) = Ref::<&'a mut [u8], U32>::from_prefix(data)?;
60-
let mut data = &*data;
61-
let mut context = Vec::new();
62-
for _ in 0..(u64::from(*len)) as usize {
63-
let (context_item, new_data) = ZInstructionDataInvokeCpi::zero_copy_at(data)?;
64-
context.push(context_item);
65-
data = new_data;
66-
}
67-
Ok(ZCpiContextAccount2 {
68-
fee_payer,
69-
associated_merkle_tree,
70-
context,
71-
})
72-
}

program-libs/ctoken-types/src/instructions/mint_action/cpi_context.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
use light_compressed_account::instruction_data::zero_copy_set::CompressedCpiContextTrait;
22
use light_zero_copy::{ZeroCopy, ZeroCopyMut};
33

4-
use crate::{AnchorDeserialize, AnchorSerialize};
4+
use crate::{AnchorDeserialize, AnchorSerialize, CMINT_ADDRESS_TREE};
55

66
#[repr(C)]
7-
#[derive(
8-
Debug, Clone, AnchorSerialize, Default, AnchorDeserialize, ZeroCopy, ZeroCopyMut, PartialEq,
9-
)]
7+
#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize, ZeroCopy, ZeroCopyMut, PartialEq)]
108
pub struct CpiContext {
119
pub set_context: bool,
1210
pub first_set_context: bool,
@@ -20,6 +18,23 @@ pub struct CpiContext {
2018
/// Placeholder to enable cmints in multiple address trees.
2119
/// Currently set to 0.
2220
pub read_only_address_trees: [u8; 4],
21+
pub address_tree_pubkey: [u8; 32],
22+
}
23+
24+
impl Default for CpiContext {
25+
fn default() -> Self {
26+
Self {
27+
set_context: false,
28+
first_set_context: false,
29+
in_tree_index: 0,
30+
in_queue_index: 0,
31+
out_queue_index: 0,
32+
token_out_queue_index: 0,
33+
assigned_account_index: 0,
34+
read_only_address_trees: [0; 4],
35+
address_tree_pubkey: CMINT_ADDRESS_TREE,
36+
}
37+
}
2338
}
2439

2540
impl CompressedCpiContextTrait for ZCpiContext<'_> {

program-tests/compressed-token-test/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ no-idl = []
1515
no-log-ix-name = []
1616
cpi = ["no-entrypoint"]
1717
test-sbf = []
18-
custom-heap = []
19-
default = ["custom-heap"]
18+
default = []
2019

2120
[dependencies]
21+
anchor-lang = { workspace = true }
22+
light-sdk = { workspace = true, features = ["anchor"] }
2223

2324
[dev-dependencies]
24-
anchor-lang = { workspace = true }
2525
light-compressed-token = { workspace = true }
2626
light-system-program-anchor = { workspace = true }
2727
account-compression = { workspace = true }
Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,70 @@
1-
// placeholder
1+
#![allow(clippy::too_many_arguments)]
2+
#![allow(unexpected_cfgs)]
3+
#![allow(deprecated)]
4+
5+
use anchor_lang::{prelude::*, solana_program::instruction::Instruction};
6+
7+
declare_id!("CompressedTokenTestProgram11111111111111111");
8+
9+
#[program]
10+
pub mod compressed_token_test {
11+
use super::*;
12+
13+
/// Wrapper for write_to_cpi_context mode mint_action CPI
14+
/// All accounts are in remaining_accounts (unchecked)
15+
pub fn write_to_cpi_context_mint_action<'info>(
16+
ctx: Context<'_, '_, '_, 'info, MintActionCpiWrapper<'info>>,
17+
inputs: Vec<u8>,
18+
) -> Result<()> {
19+
execute_mint_action_cpi(ctx, inputs)
20+
}
21+
22+
/// Wrapper for execute_cpi_context mode mint_action CPI
23+
/// All accounts are in remaining_accounts (unchecked)
24+
pub fn execute_cpi_context_mint_action<'info>(
25+
ctx: Context<'_, '_, '_, 'info, MintActionCpiWrapper<'info>>,
26+
inputs: Vec<u8>,
27+
) -> Result<()> {
28+
execute_mint_action_cpi(ctx, inputs)
29+
}
30+
}
31+
32+
/// Minimal account structure - only compressed token program ID
33+
/// Everything else goes in remaining_accounts with no validation
34+
#[derive(Accounts)]
35+
pub struct MintActionCpiWrapper<'info> {
36+
/// CHECK: Compressed token program - no validation
37+
pub compressed_token_program: AccountInfo<'info>,
38+
}
39+
40+
/// Shared implementation for both wrapper instructions
41+
/// Passes through raw instruction bytes and accounts without any validation
42+
fn execute_mint_action_cpi<'info>(
43+
ctx: Context<'_, '_, '_, 'info, MintActionCpiWrapper<'info>>,
44+
inputs: Vec<u8>,
45+
) -> Result<()> {
46+
// Build account_metas from remaining_accounts - pass through as-is
47+
let account_metas: Vec<AccountMeta> = ctx
48+
.remaining_accounts
49+
.iter()
50+
.map(|acc| {
51+
if acc.is_writable {
52+
AccountMeta::new(*acc.key, acc.is_signer)
53+
} else {
54+
AccountMeta::new_readonly(*acc.key, acc.is_signer)
55+
}
56+
})
57+
.collect();
58+
59+
// Build instruction with raw bytes (no validation)
60+
let instruction = Instruction {
61+
program_id: *ctx.accounts.compressed_token_program.key,
62+
accounts: account_metas,
63+
data: inputs, // Pass through raw instruction bytes
64+
};
65+
66+
// Simple invoke without any signer seeds
67+
anchor_lang::solana_program::program::invoke(&instruction, ctx.remaining_accounts)?;
68+
69+
Ok(())
70+
}

program-tests/compressed-token-test/tests/mint.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
// This file serves as the entry point for the mint test module
33

44
// Declare submodules from the mint/ directory
5+
#[path = "mint/cpi_context.rs"]
6+
mod cpi_context;
7+
58
#[path = "mint/edge_cases.rs"]
69
mod edge_cases;
710

0 commit comments

Comments
 (0)