Skip to content

Commit b84d662

Browse files
committed
token-2022 transfer-hook transfer-cost: create PDAs over a pre-funded address
1 parent 2e6cac8 commit b84d662

4 files changed

Lines changed: 58 additions & 18 deletions

File tree

‎tokens/token-2022/transfer-hook/transfer-cost/pinocchio/program/src/instructions/initialize_extra_account_meta_list.rs‎

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
11
use alloc::vec::Vec;
22

3-
use pinocchio::{
4-
cpi::{Seed, Signer},
5-
error::ProgramError,
6-
sysvars::{rent::Rent, Sysvar},
7-
AccountView, Address, ProgramResult,
8-
};
3+
use pinocchio::{cpi::Seed, error::ProgramError, AccountView, Address, ProgramResult};
94
use pinocchio_log::log;
10-
use pinocchio_system::instructions::CreateAccount;
115

126
use crate::{
137
error::TransferHookError,
148
instructions::{
159
ASSOCIATED_TOKEN_PROGRAM_ID, COUNTER_SEED, COUNTER_SIZE, EXTRA_ACCOUNT_METAS_SEED, NATIVE_MINT,
1610
SPL_TOKEN_PROGRAM_ID,
1711
},
12+
util::create_pda_account,
1813
};
1914

2015
/// `Execute`'s TLV discriminator, which keys the `ExtraAccountMetaList` entry.
@@ -142,11 +137,9 @@ pub fn initialize_extra_account_meta_list(program_id: &Address, accounts: &mut [
142137
let seeds = [Seed::from(EXTRA_ACCOUNT_METAS_SEED), Seed::from(mint.address().as_ref()), Seed::from(&bump_bytes)];
143138

144139
let metas = build_extra_account_metas();
145-
let lamports = Rent::get()?.try_minimum_balance(metas.len())?;
146140

147141
log!("Creating extra account meta list");
148-
CreateAccount { from: payer, to: extra_account_meta_list, lamports, space: metas.len() as u64, owner: program_id }
149-
.invoke_signed(&[Signer::from(&seeds)])?;
142+
create_pda_account(payer, extra_account_meta_list, metas.len(), program_id, &seeds)?;
150143

151144
let mut account_data = extra_account_meta_list.try_borrow_mut()?;
152145
account_data.copy_from_slice(&metas);
@@ -167,14 +160,7 @@ pub fn initialize_extra_account_meta_list(program_id: &Address, accounts: &mut [
167160
let counter_seeds = [Seed::from(COUNTER_SEED), Seed::from(&counter_bump_bytes)];
168161

169162
log!("Creating counter");
170-
CreateAccount {
171-
from: payer,
172-
to: counter,
173-
lamports: Rent::get()?.try_minimum_balance(COUNTER_SIZE)?,
174-
space: COUNTER_SIZE as u64,
175-
owner: program_id,
176-
}
177-
.invoke_signed(&[Signer::from(&counter_seeds)])?;
163+
create_pda_account(payer, counter, COUNTER_SIZE, program_id, &counter_seeds)?;
178164
} else if !counter.owned_by(program_id) || counter.data_len() != COUNTER_SIZE {
179165
return Err(TransferHookError::InvalidCounterAccount.into());
180166
}

‎tokens/token-2022/transfer-hook/transfer-cost/pinocchio/program/src/lib.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub mod error;
99
pub mod instructions;
1010
pub mod processor;
1111
pub mod token2022;
12+
pub mod util;
1213

1314
use pinocchio::{entrypoint, nostd_panic_handler};
1415

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//! Shared account-creation helper.
2+
3+
use pinocchio::{
4+
cpi::{Seed, Signer},
5+
sysvars::{rent::Rent, Sysvar},
6+
AccountView, Address, ProgramResult,
7+
};
8+
use pinocchio_system::instructions::{Allocate, Assign, Transfer};
9+
10+
/// Creates `account` at a PDA, tolerating an address that already holds
11+
/// lamports.
12+
///
13+
/// `CreateAccount` fails outright if the target has a balance, and every PDA
14+
/// here has a publicly derivable address — so anyone could send one lamport to
15+
/// one of these addresses and permanently block the instruction that was
16+
/// meant to create it. Topping the account up and then allocating and assigning
17+
/// it separately sidesteps that; it is the same fallback Anchor's `init`
18+
/// performs.
19+
pub fn create_pda_account(
20+
payer: &AccountView,
21+
account: &mut AccountView,
22+
space: usize,
23+
owner: &Address,
24+
seeds: &[Seed],
25+
) -> ProgramResult {
26+
let required = Rent::get()?.try_minimum_balance(space)?;
27+
let current = account.lamports();
28+
29+
if current < required {
30+
Transfer { from: payer, to: account, lamports: required - current }.invoke()?;
31+
}
32+
33+
let signer = [Signer::from(seeds)];
34+
Allocate { account, space: space as u64 }.invoke_signed(&signer)?;
35+
Assign { account, owner }.invoke_signed(&signer)?;
36+
37+
Ok(())
38+
}

‎tokens/token-2022/transfer-hook/transfer-cost/pinocchio/tests/test.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,21 @@ describe('Token-2022 Transfer Hook — Transfer Cost (Pinocchio)', () => {
297297
});
298298

299299
it('Creates the ExtraAccountMetaList account', async () => {
300+
// Both PDAs below have publicly derivable addresses, and `CreateAccount`
301+
// refuses to create over an account that already holds lamports — so a
302+
// stray lamport on either would otherwise block setup for this mint
303+
// permanently. Drop one on each first.
304+
for (const address of [extraAccountMetaList, counter]) {
305+
svm.setAccount({
306+
address,
307+
data: new Uint8Array(0),
308+
executable: false,
309+
lamports: lamports(1n),
310+
programAddress: SYSTEM_PROGRAM_ADDRESS,
311+
space: 0n,
312+
});
313+
}
314+
300315
const ix = {
301316
programAddress: programId,
302317
accounts: [

0 commit comments

Comments
 (0)