Skip to content
Draft
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
4,871 changes: 3,268 additions & 1,603 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions idl/spec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ pub struct IdlInstructionAccount {
pub signer: bool,
#[serde(default, skip_serializing_if = "is_default")]
pub optional: bool,
#[serde(default, skip_serializing_if = "is_default")]
pub compressible: bool,
#[serde(skip_serializing_if = "is_default")]
pub address: Option<String>,
#[serde(skip_serializing_if = "is_default")]
Expand Down
1 change: 1 addition & 0 deletions idl/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ mod legacy {
writable: acc.is_mut,
signer: acc.is_signer,
optional: acc.is_optional.unwrap_or_default(),
compressible: false,
address: Default::default(),
pda: acc
.pda
Expand Down
24 changes: 24 additions & 0 deletions lang/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@ idl-build = [
init-if-needed = ["anchor-derive-accounts/init-if-needed"]
interface-instructions = ["anchor-attribute-program/interface-instructions"]
lazy-account = ["anchor-attribute-account/lazy-account", "anchor-derive-serde/lazy-account"]
compressed-mint = []
compressed-mint-light = [
"compressed-mint",
"light-sdk",
"light-sdk-types",
"light-hasher",
"light-macros",
"light-sdk-macros",
# "light-compressed-account",
"light-compressed-token-sdk",
"light-ctoken-types",
"light-compressible"
]

[dependencies]
anchor-attribute-access-control = { path = "./attribute/access-control", version = "0.31.1" }
Expand All @@ -58,3 +71,14 @@ borsh = "0.10.3"
bytemuck = "1"
solana-program = "2"
thiserror = "1"

# Light Protocol dependencies (optional)
light-sdk = { path = "../../light-protocol/sdk-libs/sdk", features = ["anchor", "anchor-discriminator-compat", "v2"], optional = true }
light-sdk-types = { path = "../../light-protocol/sdk-libs/sdk-types", features = ["v2"], optional = true }
light-hasher = { path = "../../light-protocol/program-libs/hasher", features = ["solana"], optional = true }
light-macros = { path = "../../light-protocol/program-libs/macros", features = ["solana"], optional = true }
light-sdk-macros = { path = "../../light-protocol/sdk-libs/macros", features = ["anchor-discriminator-compat"], optional = true }
# light-compressed-account = { path = "../../light-protocol/program-libs/compressed-account", features = ["anchor"], optional = true }
light-compressed-token-sdk = { path = "../../light-protocol/sdk-libs/compressed-token-sdk", features = ["anchor"], optional = true }
light-ctoken-types = { path = "../../light-protocol/program-libs/ctoken-types", features = ["anchor"], optional = true }
light-compressible = { path = "../../light-protocol/program-libs/compressible", optional = true }
6 changes: 6 additions & 0 deletions lang/src/accounts/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,12 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfos<'in
}
}

impl<'info, B, T: AccountSerialize + AccountDeserialize + Clone> crate::AccountsFinalize<'info, B>
for Account<'info, T>
{
// Uses default implementation
}

impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AsRef<AccountInfo<'info>>
for Account<'info, T>
{
Expand Down
4 changes: 4 additions & 0 deletions lang/src/accounts/account_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ impl<'info> ToAccountInfos<'info> for AccountInfo<'info> {
}
}

impl<'info, B> crate::AccountsFinalize<'info, B> for AccountInfo<'info> {
// Uses default implementation
}

impl<'info> AccountsExit<'info> for AccountInfo<'info> {}

impl Key for AccountInfo<'_> {
Expand Down
4 changes: 4 additions & 0 deletions lang/src/accounts/account_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,10 @@ impl<'info, T: ZeroCopy + Owner> ToAccountInfos<'info> for AccountLoader<'info,
}
}

impl<'info, B, T: ZeroCopy + Owner> crate::AccountsFinalize<'info, B> for AccountLoader<'info, T> {
// Uses default implementation
}

impl<T: ZeroCopy + Owner> Key for AccountLoader<'_, T> {
fn key(&self) -> Pubkey {
*self.acc_info.key
Expand Down
12 changes: 12 additions & 0 deletions lang/src/accounts/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ impl<'info, T: ToAccountInfos<'info>> ToAccountInfos<'info> for Box<T> {
}
}

impl<'info, B, T: crate::AccountsFinalize<'info, B>> crate::AccountsFinalize<'info, B> for Box<T> {
fn finalize(
&mut self,
program_id: &Pubkey,
remaining_accounts: &[AccountInfo<'info>],
ix_data: &[u8],
bumps: &B,
) -> Result<()> {
T::finalize(self, program_id, remaining_accounts, ix_data, bumps)
}
}

impl<T: ToAccountMetas> ToAccountMetas for Box<T> {
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
T::to_account_metas(self, is_signer)
Expand Down
101 changes: 101 additions & 0 deletions lang/src/accounts/cmint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use crate::{
AccountInfo, Accounts, AccountsFinalize, Key, Result, ToAccountInfo, ToAccountInfos,
ToAccountMetas,
};
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use std::cell::RefCell;
use std::collections::BTreeSet;

#[derive(Clone)]
pub struct MintAction<'info> {
pub recipient: AccountInfo<'info>,
pub amount: u64,
}

/// CMint is a compressed mint account wrapper.
/// It queues mint actions to be executed in a single batched invoke at finalize.
pub struct CMint<'info> {
info: AccountInfo<'info>,
actions: RefCell<Vec<MintAction<'info>>>,
// Constraints from account macro
pub authority: Option<Pubkey>,
pub decimals: Option<u8>,
pub mint_signer: Option<AccountInfo<'info>>,
pub mint_signer_seeds: Option<Vec<Vec<u8>>>,
pub mint_signer_bump: Option<u8>,
pub program_authority_seeds: Option<Vec<Vec<u8>>>,
pub program_authority_bump: Option<u8>,
}

impl<'info> CMint<'info> {
pub fn mint_to(&self, recipient: &AccountInfo<'info>, amount: u64) -> crate::Result<()> {
self.actions.borrow_mut().push(MintAction {
recipient: recipient.clone(),
amount,
});
Ok(())
}

pub fn take_actions(&self) -> Vec<MintAction<'info>> {
let mut b = self.actions.borrow_mut();
let actions = b.clone();
b.clear();
actions
}
}

impl<'info> ToAccountInfo<'info> for CMint<'info> {
fn to_account_info(&self) -> AccountInfo<'info> {
self.info.clone()
}
}

impl<'info> ToAccountInfos<'info> for CMint<'info> {
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
vec![self.info.clone()]
}
}

impl<'info> ToAccountMetas for CMint<'info> {
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
let signer = is_signer.unwrap_or(false);
vec![AccountMeta::new(self.key(), signer)]
}
}

impl<'info> Key for CMint<'info> {
fn key(&self) -> Pubkey {
*self.info.key
}
}

impl<'info, T> Accounts<'info, T> for CMint<'info> {
fn try_accounts(
_program_id: &Pubkey,
accounts: &mut &'info [AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut T,
_reallocs: &mut BTreeSet<Pubkey>,
) -> Result<Self> {
if accounts.is_empty() {
return Err(crate::error::ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];
Ok(CMint {
info: account.clone(),
actions: RefCell::new(Vec::new()),
authority: None,
decimals: None,
mint_signer: None,
mint_signer_seeds: None,
mint_signer_bump: None,
program_authority_seeds: None,
program_authority_bump: None,
})
}
}

// Note: the outer Accounts struct finalize will consume actions and perform the batched CPI.
impl<'info, B> AccountsFinalize<'info, B> for CMint<'info> {}
4 changes: 4 additions & 0 deletions lang/src/accounts/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ impl<'info, T> ToAccountInfos<'info> for Interface<'info, T> {
}
}

impl<'info, B, T> crate::AccountsFinalize<'info, B> for Interface<'info, T> {
// Uses default implementation
}

impl<'info, T: AccountDeserialize> AccountsExit<'info> for Interface<'info, T> {}

impl<T: AccountDeserialize> Key for Interface<'_, T> {
Expand Down
71 changes: 69 additions & 2 deletions lang/src/accounts/interface_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,43 @@ impl<'a, T: AccountSerialize + AccountDeserialize + CheckOwner + Clone> Interfac
let mut data: &[u8] = &info.try_borrow_data()?;
Ok(Self::new(info, T::try_deserialize_unchecked(&mut data)?))
}

/// Creates an `InterfaceAccount` for CToken compressed accounts without deserializing.
/// This is used when the account data is compressed off-chain.
/// Verifies the account is NOT initialized on-chain (CToken mints don't exist on-chain).
#[inline(never)]
pub fn try_from_ctoken(info: &'a AccountInfo<'a>) -> Result<Self>
where
T: Default,
{

if info.owner != &system_program::ID {
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
}

let data = info.try_borrow_data()?;
if data.len() != 0 {
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
}


Ok(Self::new(info, T::default()))
}

/// Deserializes based on runtime token_program value.
/// If token_program is CTOKEN_ID, treats as CToken (uninitialized).
/// Otherwise, deserializes as regular SPL/Token2022 mint.
#[inline(never)]
pub fn try_from_with_token_program(info: &'a AccountInfo<'a>, token_program: &Pubkey) -> Result<Self>
where
T: Default,
{
if token_program == &crate::CTOKEN_ID {
Self::try_from_ctoken(info)
} else {
Self::try_from(info)
}
}
}

impl<'info, B, T: AccountSerialize + AccountDeserialize + CheckOwner + Clone> Accounts<'info, B>
Expand Down Expand Up @@ -290,6 +327,12 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfos<'in
}
}

impl<'info, B, T: AccountSerialize + AccountDeserialize + Clone> crate::AccountsFinalize<'info, B>
for InterfaceAccount<'info, T>
{
// Uses default implementation
}

impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AsRef<AccountInfo<'info>>
for InterfaceAccount<'info, T>
{
Expand All @@ -304,16 +347,40 @@ impl<T: AccountSerialize + AccountDeserialize + Clone> AsRef<T> for InterfaceAcc
}
}

impl<T: AccountSerialize + AccountDeserialize + Clone> Deref for InterfaceAccount<'_, T> {
impl<T: AccountSerialize + AccountDeserialize + Clone + 'static> Deref for InterfaceAccount<'_, T> {
type Target = T;

fn deref(&self) -> &Self::Target {
// Special check for Mint types with CToken program
#[cfg(feature = "spl-token")]
{
use std::any::TypeId;
// Check if T is Mint and this is a CToken mint (uninitialized account)
if TypeId::of::<T>() == TypeId::of::<anchor_spl::token_interface::Mint>() {
// CToken mints have owner = system_program (since they're uninitialized)
if self.account.info.owner == &system_program::ID {
panic!("Cannot access mint data for CToken accounts - data is compressed off-chain! Only use the account's pubkey.");
}
}
}
self.account.deref()
}
}

impl<T: AccountSerialize + AccountDeserialize + Clone> DerefMut for InterfaceAccount<'_, T> {
impl<T: AccountSerialize + AccountDeserialize + Clone + 'static> DerefMut for InterfaceAccount<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
// Special check for Mint types with CToken program
#[cfg(feature = "spl-token")]
{
use std::any::TypeId;
// Check if T is Mint and this is a CToken mint (uninitialized account)
if TypeId::of::<T>() == TypeId::of::<anchor_spl::token_interface::Mint>() {
// CToken mints have owner = system_program (since they're uninitialized)
if self.account.info.owner == &system_program::ID {
panic!("Cannot mutate mint data for CToken accounts - data is compressed off-chain!");
}
}
}
self.account.deref_mut()
}
}
Expand Down
7 changes: 7 additions & 0 deletions lang/src/accounts/lazy_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,13 @@ where
}
}

impl<'info, B, T> crate::AccountsFinalize<'info, B> for LazyAccount<'info, T>
where
T: AccountSerialize + Discriminator + Owner + Clone,
{
// Uses default implementation
}

impl<'info, T> AsRef<AccountInfo<'info>> for LazyAccount<'info, T>
where
T: AccountSerialize + Discriminator + Owner + Clone,
Expand Down
4 changes: 4 additions & 0 deletions lang/src/accounts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,9 @@ pub mod system_account;
pub mod sysvar;
pub mod unchecked_account;

// Compressed mint account type (feature-gated until stabilized)
#[cfg(feature = "compressed-mint")]
pub mod cmint;

#[cfg(feature = "lazy-account")]
pub mod lazy_account;
17 changes: 17 additions & 0 deletions lang/src/accounts/option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,23 @@ impl<'info, T: ToAccountInfos<'info>> ToAccountInfos<'info> for Option<T> {
}
}

impl<'info, B, T: crate::AccountsFinalize<'info, B>> crate::AccountsFinalize<'info, B>
for Option<T>
{
fn finalize(
&mut self,
program_id: &Pubkey,
remaining_accounts: &[AccountInfo<'info>],
ix_data: &[u8],
bumps: &B,
) -> Result<()> {
if let Some(account) = self.as_mut() {
account.finalize(program_id, remaining_accounts, ix_data, bumps)?;
}
Ok(())
}
}

impl<T: ToAccountMetas> ToAccountMetas for Option<T> {
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
self.as_ref()
Expand Down
4 changes: 4 additions & 0 deletions lang/src/accounts/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ impl<'info, T> ToAccountInfos<'info> for Program<'info, T> {
}
}

impl<'info, B, T> crate::AccountsFinalize<'info, B> for Program<'info, T> {
// Uses default implementation
}

impl<'info, T> AsRef<AccountInfo<'info>> for Program<'info, T> {
fn as_ref(&self) -> &AccountInfo<'info> {
self.info
Expand Down
4 changes: 4 additions & 0 deletions lang/src/accounts/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ impl<'info> ToAccountInfos<'info> for Signer<'info> {
}
}

impl<'info, B> crate::AccountsFinalize<'info, B> for Signer<'info> {
// Uses default implementation
}

impl<'info> AsRef<AccountInfo<'info>> for Signer<'info> {
fn as_ref(&self) -> &AccountInfo<'info> {
self.info
Expand Down
4 changes: 4 additions & 0 deletions lang/src/accounts/system_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ impl<'info> ToAccountInfos<'info> for SystemAccount<'info> {
}
}

impl<'info, B> crate::AccountsFinalize<'info, B> for SystemAccount<'info> {
// Uses default implementation
}

impl<'info> AsRef<AccountInfo<'info>> for SystemAccount<'info> {
fn as_ref(&self) -> &AccountInfo<'info> {
self.info
Expand Down
Loading