Skip to content

Commit fcce2bd

Browse files
committed
fix: transfer-cost stack overflow and upgrade SPL crates to Solana 3.x
- Box large InterfaceAccount fields to avoid BPF stack frame overflow (12 accounts exceeded 4096-byte limit in Anchor 1.0's try_accounts) - Upgrade spl-discriminator 0.4.1→0.5.2, spl-tlv-account-resolution 0.9.0→0.11.1, spl-transfer-hook-interface 0.9.0→2.1.0 (now compatible with Solana 3.x) - Remove transmute_pubkey() hack (no longer needed with matching Pubkey types)
1 parent b5d4fe5 commit fcce2bd

2 files changed

Lines changed: 31 additions & 37 deletions

File tree

  • tokens/token-2022/transfer-hook/transfer-cost/anchor/programs/transfer-hook

tokens/token-2022/transfer-hook/transfer-cost/anchor/programs/transfer-hook/Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ custom-panic = []
2323
# Anchor 1.0.0-rc.5 — pin to RC until stable release
2424
anchor-lang = "1.0.0-rc.5"
2525
anchor-spl = "1.0.0-rc.5"
26-
spl-discriminator = "0.4.1"
27-
spl-tlv-account-resolution = "0.9.0"
28-
spl-transfer-hook-interface = "0.9.0"
26+
# SPL crates v3.x-compatible — uses solana-program-error 3.x matching anchor-lang 1.0
27+
spl-discriminator = "0.5.2"
28+
spl-tlv-account-resolution = "0.11.1"
29+
spl-transfer-hook-interface = "2.1.0"
2930

3031
[lints.rust]
3132
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] }

tokens/token-2022/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ use anchor_spl::{
1111
},
1212
token_interface::{transfer_checked, Mint, TokenAccount, TransferChecked},
1313
};
14+
use spl_discriminator::SplDiscriminate;
1415
use spl_tlv_account_resolution::{
1516
account::ExtraAccountMeta, seeds::Seed, state::ExtraAccountMetaList,
1617
};
17-
use spl_discriminator::SplDiscriminate;
1818
use spl_transfer_hook_interface::instruction::{
1919
ExecuteInstruction, InitializeExtraAccountMetaListInstruction,
2020
};
@@ -25,12 +25,6 @@ use std::{cell::RefMut, str::FromStr};
2525

2626
declare_id!("FjcHckEgXcBhFmSGai3FRpDLiT6hbpV893n8iTxVd81g");
2727

28-
/// Convert anchor-lang's Pubkey (solana-pubkey 3.x) to the spl crate's Pubkey (solana-pubkey 2.x).
29-
/// Both are #[repr(transparent)] over [u8; 32], so byte-level conversion is safe.
30-
fn transmute_pubkey(key: &Pubkey) -> spl_tlv_account_resolution::solana_pubkey::Pubkey {
31-
spl_tlv_account_resolution::solana_pubkey::Pubkey::from(key.to_bytes())
32-
}
33-
3428
#[error_code]
3529
pub enum TransferError {
3630
#[msg("Amount Too big")]
@@ -50,12 +44,11 @@ pub mod transfer_hook {
5044
let extra_account_metas = InitializeExtraAccountMetaList::extra_account_metas()?;
5145

5246
// initialize ExtraAccountMetaList account with extra accounts
53-
// .map_err() needed because spl-tlv-account-resolution uses solana-program-error 2.x
54-
// while anchor-lang 1.0 uses 3.x — structurally identical but different semver types
5547
ExtraAccountMetaList::init::<ExecuteInstruction>(
5648
&mut ctx.accounts.extra_account_meta_list.try_borrow_mut_data()?,
5749
&extra_account_metas,
58-
).map_err(|_| ProgramError::InvalidAccountData)?;
50+
)
51+
.map_err(|_| ProgramError::InvalidAccountData)?;
5952

6053
Ok(())
6154
}
@@ -67,7 +60,6 @@ pub mod transfer_hook {
6760

6861
if amount > 50 {
6962
msg!("The amount is too big {0}", amount);
70-
//return err!(TransferError::AmountTooBig);
7163
}
7264

7365
ctx.accounts.counter_account.counter += 1;
@@ -77,7 +69,6 @@ pub mod transfer_hook {
7769
ctx.accounts.counter_account.counter
7870
);
7971

80-
// All accounts are non writable so you can not burn any of them for example here
8172
msg!(
8273
"Is writable mint {0}",
8374
ctx.accounts.mint.to_account_info().is_writable
@@ -116,11 +107,10 @@ pub mod transfer_hook {
116107
fn check_is_transferring(ctx: &Context<TransferHook>) -> Result<()> {
117108
let source_token_info = ctx.accounts.source_token.to_account_info();
118109
let mut account_data_ref: RefMut<&mut [u8]> = source_token_info.try_borrow_mut_data()?;
119-
// .map_err() needed because spl-token-2022 uses solana-program-error 2.x
120-
// while anchor-lang 1.0 uses 3.x — structurally identical but different semver types
121110
let mut account = PodStateWithExtensionsMut::<PodAccount>::unpack(*account_data_ref)
122111
.map_err(|_| ProgramError::InvalidAccountData)?;
123-
let account_extension = account.get_extension_mut::<TransferHookAccount>()
112+
let account_extension = account
113+
.get_extension_mut::<TransferHookAccount>()
124114
.map_err(|_| ProgramError::InvalidAccountData)?;
125115

126116
if !bool::from(account_extension.transferring) {
@@ -162,15 +152,9 @@ impl<'info> InitializeExtraAccountMetaList<'info> {
162152
// index 0-3 are the accounts required for token transfer (source, mint, destination, owner)
163153
// index 4 is address of ExtraAccountMetaList account
164154

165-
// .map_err() needed because spl-tlv-account-resolution uses solana-program-error 2.x
166-
// while anchor-lang 1.0 uses 3.x — structurally identical but different semver types.
167-
// Pubkey types also differ (solana-pubkey 2.x vs 3.x), so we convert via bytes using
168-
// transmute_pubkey() — both are #[repr(transparent)] over [u8; 32].
169-
let wsol_mint = transmute_pubkey(
170-
&Pubkey::from_str("So11111111111111111111111111111111111111112").unwrap()
171-
);
172-
let token_program_id = transmute_pubkey(&Token::id());
173-
let ata_program_id = transmute_pubkey(&AssociatedToken::id());
155+
let wsol_mint = Pubkey::from_str("So11111111111111111111111111111111111111112").unwrap();
156+
let token_program_id = Token::id();
157+
let ata_program_id = AssociatedToken::id();
174158

175159
Ok(vec![
176160
// index 5, wrapped SOL mint
@@ -189,7 +173,8 @@ impl<'info> InitializeExtraAccountMetaList<'info> {
189173
}],
190174
false, // is_signer
191175
true, // is_writable
192-
).map_err(|_| ProgramError::InvalidArgument)?,
176+
)
177+
.map_err(|_| ProgramError::InvalidArgument)?,
193178
// index 9, delegate wrapped SOL token account
194179
ExtraAccountMeta::new_external_pda_with_seeds(
195180
7, // associated token program index
@@ -200,7 +185,8 @@ impl<'info> InitializeExtraAccountMetaList<'info> {
200185
],
201186
false, // is_signer
202187
true, // is_writable
203-
).map_err(|_| ProgramError::InvalidArgument)?,
188+
)
189+
.map_err(|_| ProgramError::InvalidArgument)?,
204190
// index 10, sender wrapped SOL token account
205191
ExtraAccountMeta::new_external_pda_with_seeds(
206192
7, // associated token program index
@@ -211,14 +197,16 @@ impl<'info> InitializeExtraAccountMetaList<'info> {
211197
],
212198
false, // is_signer
213199
true, // is_writable
214-
).map_err(|_| ProgramError::InvalidArgument)?,
200+
)
201+
.map_err(|_| ProgramError::InvalidArgument)?,
215202
ExtraAccountMeta::new_with_seeds(
216203
&[Seed::Literal {
217204
bytes: b"counter".to_vec(),
218205
}],
219206
false, // is_signer
220207
true, // is_writable
221-
).map_err(|_| ProgramError::InvalidArgument)?,
208+
)
209+
.map_err(|_| ProgramError::InvalidArgument)?,
222210
])
223211
}
224212

@@ -232,19 +220,24 @@ impl<'info> InitializeExtraAccountMetaList<'info> {
232220
// The first 4 accounts are the accounts required for token transfer (source, mint, destination, owner)
233221
// Remaining accounts are the extra accounts required from the ExtraAccountMetaList account
234222
// These accounts are provided via CPI to this program from the token2022 program
223+
//
224+
// Box<InterfaceAccount> used for source_token, destination_token, wsol_mint,
225+
// delegate_wsol_token_account, and sender_wsol_token_account to avoid exceeding
226+
// the 4096-byte BPF stack frame limit in try_accounts deserialization.
227+
// This struct has 12 accounts — without Box, the generated code uses ~4160 bytes of stack.
235228
#[derive(Accounts)]
236229
pub struct TransferHook<'info> {
237230
#[account(token::mint = mint, token::authority = owner)]
238-
pub source_token: InterfaceAccount<'info, TokenAccount>,
239-
pub mint: InterfaceAccount<'info, Mint>,
231+
pub source_token: Box<InterfaceAccount<'info, TokenAccount>>,
232+
pub mint: Box<InterfaceAccount<'info, Mint>>,
240233
#[account(token::mint = mint)]
241-
pub destination_token: InterfaceAccount<'info, TokenAccount>,
234+
pub destination_token: Box<InterfaceAccount<'info, TokenAccount>>,
242235
/// CHECK: source token account owner, can be SystemAccount or PDA owned by another program
243236
pub owner: UncheckedAccount<'info>,
244237
/// CHECK: ExtraAccountMetaList Account,
245238
#[account(seeds = [b"extra-account-metas", mint.key().as_ref()], bump)]
246239
pub extra_account_meta_list: UncheckedAccount<'info>,
247-
pub wsol_mint: InterfaceAccount<'info, Mint>,
240+
pub wsol_mint: Box<InterfaceAccount<'info, Mint>>,
248241
pub token_program: Program<'info, Token>,
249242
pub associated_token_program: Program<'info, AssociatedToken>,
250243
#[account(
@@ -258,13 +251,13 @@ pub struct TransferHook<'info> {
258251
token::mint = wsol_mint,
259252
token::authority = delegate,
260253
)]
261-
pub delegate_wsol_token_account: InterfaceAccount<'info, TokenAccount>,
254+
pub delegate_wsol_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
262255
#[account(
263256
mut,
264257
token::mint = wsol_mint,
265258
token::authority = owner,
266259
)]
267-
pub sender_wsol_token_account: InterfaceAccount<'info, TokenAccount>,
260+
pub sender_wsol_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
268261
#[account(seeds = [b"counter"], bump)]
269262
pub counter_account: Account<'info, CounterAccount>,
270263
}

0 commit comments

Comments
 (0)