light_token_interface/instructions/extensions/
compressible.rs

1use std::mem::MaybeUninit;
2
3use light_zero_copy::{ZeroCopy, ZeroCopyMut};
4use pinocchio::pubkey::Pubkey;
5use solana_pubkey::MAX_SEEDS;
6use tinyvec::ArrayVec;
7
8use crate::{AnchorDeserialize, AnchorSerialize, TokenError};
9
10#[derive(
11    Debug, Clone, PartialEq, Eq, AnchorSerialize, AnchorDeserialize, ZeroCopy, ZeroCopyMut,
12)]
13#[repr(C)]
14pub struct CompressibleExtensionInstructionData {
15    /// Version of the compressed token account when token account is
16    /// compressed and closed. (The version specifies the hashing scheme.)
17    pub token_account_version: u8,
18    /// Rent payment in epochs.
19    /// Paid once at initialization.
20    pub rent_payment: u8,
21    /// If non-zero, the compressed token account cannot be transferred, only decompressed.
22    /// Required for mints with restricted extensions (Pausable, PermanentDelegate, TransferFee, TransferHook).
23    /// Must be set for compressible ATAs.
24    pub compression_only: u8,
25    pub write_top_up: u32,
26    pub compress_to_account_pubkey: Option<CompressToPubkey>,
27}
28
29#[derive(
30    Debug, Clone, PartialEq, Eq, AnchorSerialize, AnchorDeserialize, ZeroCopy, ZeroCopyMut,
31)]
32#[repr(C)]
33pub struct CompressToPubkey {
34    pub bump: u8,
35    pub program_id: [u8; 32],
36    pub seeds: Vec<Vec<u8>>,
37}
38
39impl CompressToPubkey {
40    pub fn check_seeds(&self, pubkey: &Pubkey) -> Result<(), TokenError> {
41        if self.seeds.len() >= MAX_SEEDS {
42            return Err(TokenError::TooManySeeds(MAX_SEEDS - 1));
43        }
44        let mut references = ArrayVec::<[&[u8]; MAX_SEEDS]>::new();
45        for seed in self.seeds.iter() {
46            references.push(seed.as_slice());
47        }
48        let derived_pubkey = derive_address(references.as_slice(), self.bump, &self.program_id)?;
49        if derived_pubkey != *pubkey {
50            Err(TokenError::InvalidAccountData)
51        } else {
52            Ok(())
53        }
54    }
55}
56
57// Taken from pinocchio 0.9.2.
58// Modifications:
59// -  seeds: &[&[u8]; N], ->  seeds: &[&[u8]],
60// - if seeds.len() > MAX_SEEDS TokenError::InvalidAccountData
61pub fn derive_address(
62    seeds: &[&[u8]],
63    bump: u8,
64    program_id: &Pubkey,
65) -> Result<Pubkey, TokenError> {
66    const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
67    // Must be strictly less than MAX_SEEDS because we need space for:
68    // seeds + bump + program_id + PDA_MARKER in a [MAX_SEEDS + 2] array
69    if seeds.len() >= MAX_SEEDS {
70        return Err(TokenError::TooManySeeds(MAX_SEEDS - 1));
71    }
72    const UNINIT: MaybeUninit<&[u8]> = MaybeUninit::<&[u8]>::uninit();
73    let mut data = [UNINIT; MAX_SEEDS + 2];
74    let mut i = 0;
75
76    while i < seeds.len() {
77        // SAFETY: `data` is guaranteed to have enough space for `N` seeds,
78        // so `i` will always be within bounds.
79        unsafe {
80            data.get_unchecked_mut(i).write(seeds.get_unchecked(i));
81        }
82        i += 1;
83    }
84
85    // TODO: replace this with `as_slice` when the MSRV is upgraded
86    // to `1.84.0+`.
87    let bump_seed = [bump];
88
89    // SAFETY: `data` is guaranteed to have enough space for `MAX_SEEDS + 2`
90    // elements, and `MAX_SEEDS` is as large as `N`.
91    unsafe {
92        data.get_unchecked_mut(i).write(&bump_seed);
93        i += 1;
94
95        data.get_unchecked_mut(i).write(program_id.as_ref());
96        data.get_unchecked_mut(i + 1).write(PDA_MARKER.as_ref());
97    }
98
99    #[cfg(target_os = "solana")]
100    {
101        use pinocchio::syscalls::sol_sha256;
102        let mut pda = MaybeUninit::<[u8; 32]>::uninit();
103
104        // SAFETY: `data` has `i + 2` elements initialized.
105        unsafe {
106            sol_sha256(
107                data.as_ptr() as *const u8,
108                (i + 2) as u64,
109                pda.as_mut_ptr() as *mut u8,
110            );
111        }
112
113        // SAFETY: `pda` has been initialized by the syscall.
114        unsafe { Ok(pda.assume_init()) }
115    }
116
117    #[cfg(not(target_os = "solana"))]
118    unreachable!("deriving a pda is only available on target `solana`");
119}