Skip to content

Commit 5bdbf23

Browse files
committed
token-2022 transfer-hook counter: create PDAs over a pre-funded address
1 parent dcb7b16 commit 5bdbf23

4 files changed

Lines changed: 58 additions & 25 deletions

File tree

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

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
1-
use pinocchio::{
2-
cpi::{Seed, Signer},
3-
error::ProgramError,
4-
sysvars::{rent::Rent, Sysvar},
5-
AccountView, Address, ProgramResult,
6-
};
1+
use pinocchio::{cpi::Seed, error::ProgramError, AccountView, Address, ProgramResult};
72
use pinocchio_log::log;
8-
use pinocchio_system::instructions::CreateAccount;
93

104
use crate::{
115
error::TransferHookError,
126
instructions::{COUNTER_SEED, COUNTER_SIZE, EXTRA_ACCOUNT_METAS_SEED},
7+
util::create_pda_account,
138
};
149

1510
/// A serialized `ExtraAccountMetaList` holding this example's one extra
@@ -89,17 +84,8 @@ pub fn initialize_extra_account_meta_list(program_id: &Address, accounts: &mut [
8984
let bump_bytes = [bump];
9085
let seeds = [Seed::from(EXTRA_ACCOUNT_METAS_SEED), Seed::from(mint.address().as_ref()), Seed::from(&bump_bytes)];
9186

92-
let lamports = Rent::get()?.try_minimum_balance(EXTRA_ACCOUNT_METAS_DATA.len())?;
93-
9487
log!("Creating extra account meta list");
95-
CreateAccount {
96-
from: payer,
97-
to: extra_account_meta_list,
98-
lamports,
99-
space: EXTRA_ACCOUNT_METAS_DATA.len() as u64,
100-
owner: program_id,
101-
}
102-
.invoke_signed(&[Signer::from(&seeds)])?;
88+
create_pda_account(payer, extra_account_meta_list, EXTRA_ACCOUNT_METAS_DATA.len(), program_id, &seeds)?;
10389

10490
let mut account_data = extra_account_meta_list.try_borrow_mut()?;
10591
account_data.copy_from_slice(&EXTRA_ACCOUNT_METAS_DATA);
@@ -120,14 +106,7 @@ pub fn initialize_extra_account_meta_list(program_id: &Address, accounts: &mut [
120106
let counter_seeds = [Seed::from(COUNTER_SEED), Seed::from(&counter_bump_bytes)];
121107

122108
log!("Creating counter");
123-
CreateAccount {
124-
from: payer,
125-
to: counter,
126-
lamports: Rent::get()?.try_minimum_balance(COUNTER_SIZE)?,
127-
space: COUNTER_SIZE as u64,
128-
owner: program_id,
129-
}
130-
.invoke_signed(&[Signer::from(&counter_seeds)])?;
109+
create_pda_account(payer, counter, COUNTER_SIZE, program_id, &counter_seeds)?;
131110
} else if !counter.owned_by(program_id) || counter.data_len() != COUNTER_SIZE {
132111
return Err(TransferHookError::InvalidCounterAccount.into());
133112
}

tokens/token-2022/transfer-hook/counter/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/counter/pinocchio/tests/test.ts

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

226226
it('Creates the ExtraAccountMetaList account', async () => {
227+
// Both PDAs below have publicly derivable addresses, and `CreateAccount`
228+
// refuses to create over an account that already holds lamports — so a
229+
// stray lamport on either would otherwise block setup for this mint
230+
// permanently. Drop one on each first.
231+
for (const address of [extraAccountMetaList, counter]) {
232+
svm.setAccount({
233+
address,
234+
data: new Uint8Array(0),
235+
executable: false,
236+
lamports: lamports(1n),
237+
programAddress: SYSTEM_PROGRAM_ADDRESS,
238+
space: 0n,
239+
});
240+
}
241+
227242
const ix = {
228243
programAddress: programId,
229244
accounts: [

0 commit comments

Comments
 (0)