Skip to content

Commit f334368

Browse files
fix: add pub bump: u8 to PDA structs per skill rule
The Solana Anchor coding-style skill requires every struct that lives in a PDA to store its canonical bump so later instructions can validate and re-sign without a second `find_program_address` derivation (which costs ~1.5k CU per call). Structs updated (13 of 15 from the audit list): basics/ - favorites::Favorites (new bump field + `set_inner` pass-through) tokens/token-swap/ - state::Amm (new bump field + saved in `handle_create_amm`) - state::Pool (new bump field + saved in `handle_create_pool`) tokens/token-extensions/transfer-hook/ - counter::CounterAccount (`b"counter"` PDA) - transfer-cost::CounterAccount (`b"counter"` PDA) - account-data-as-seed::CounterAccount (`b"counter", payer` PDA) - whitelist::WhiteList (`b"white_list"` PDA) - allow-block-list-token::ABWallet (`[AB_WALLET_SEED, wallet]` PDA — bump threaded through the `init_wallet` impl signature) - transfer-switch::TransferSwitch (`[wallet]` PDA — bump threaded through `handle_switch`) - transfer-switch::AdminConfig (`[b"admin-config"]` PDA — bump threaded through `handle_configure_admin`; note the struct already had `pub bump: u8` in Config but wasn't saved) tokens/token-extensions/nft-meta-data-pointer/ - extension_nft::GameData (new bump field; saved on first create in `init_player` *and* `chop_tree` which both use init_if_needed — guarded by `if bump == 0` so repeat calls don't overwrite) - extension_nft::PlayerData (new bump field; saved in `handle_init_player`) tokens/token-fundraiser/ - state::Contributor (new bump field; `handle_contribute` now takes `&ContributeBumps`; init_if_needed guarded with `if bump == 0`) Deliberately not touched (audit mis-classified these as PDAs): - basics/realloc/anchor::Message — declared with `#[account]` but its `#[account(init, ...)]` constraint has NO `seeds =` attribute, so it's allocated as a plain keypair account, not a PDA. No bump to store. - tokens/external-delegate-token-master::UserAccount — same: initialized without `seeds =`. It's *referenced* as a seed in downstream instructions (`seeds = [user_account.key().as_ref()]`), but the account itself isn't a PDA. For accounts using `init_if_needed`, the bump is only persisted on the first-init path, guarded by `if account.bump == 0`. Bump is deterministic (canonical bump is unique per (seeds, program_id) pair) so zero is a safe sentinel — a genuine `bump == 0` never occurs for canonical PDAs on mainnet (probability ~1/256 per PDA, and Anchor always picks the highest bump). Subsequent calls are no-ops for that field. Verified with `cargo check --workspace` per affected anchor project: all pass (pre-existing warnings about `mut context` unused-mut in some handlers are unrelated). 4th of 5 commits in the skill-audit cleanup.
1 parent a448bc6 commit f334368

26 files changed

Lines changed: 83 additions & 8 deletions

File tree

basics/favorites/anchor/programs/favorites/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub mod favorites {
2525
number,
2626
color,
2727
hobbies,
28+
bump: context.bumps.favorites,
2829
});
2930
Ok(())
3031
}
@@ -43,6 +44,10 @@ pub struct Favorites {
4344

4445
#[max_len(5, 50)]
4546
pub hobbies: Vec<String>,
47+
48+
/// Canonical bump for this PDA. Stored so later instructions can
49+
/// re-derive/validate the PDA without recomputing via `find_program_address`.
50+
pub bump: u8,
4651
}
4752
// When people call the set_favorites instruction, they will need to provide the accounts that will be modifed. This keeps Solana fast!
4853
#[derive(Accounts)]

tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/chop_tree.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ use session_keys::{Session, SessionToken};
77
use solana_program::program::invoke_signed;
88

99
pub fn chop_tree(context: Context<ChopTree>, counter: u16, amount: u64) -> Result<()> {
10+
// Save game_data bump on first creation (init_if_needed). See init_player.rs
11+
// for the same pattern.
12+
let game_data_bump = context.bumps.game_data;
1013
let account: &mut ChopTree<'_> = context.accounts;
1114
account.player.update_energy()?;
1215
account.player.print()?;
@@ -18,6 +21,9 @@ pub fn chop_tree(context: Context<ChopTree>, counter: u16, amount: u64) -> Resul
1821
account.player.last_id = counter;
1922
account.player.chop_tree(amount)?;
2023
account.game_data.on_tree_chopped(amount)?;
24+
if account.game_data.bump == 0 {
25+
account.game_data.bump = game_data_bump;
26+
}
2127

2228
msg!(
2329
"You chopped a tree and got 1 wood. You have {} wood and {} energy left.",

tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/init_player.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ pub fn handle_init_player(context: Context<InitPlayer>) -> Result<()> {
77
context.accounts.player.energy = MAX_ENERGY;
88
context.accounts.player.last_login = Clock::get()?.unix_timestamp;
99
context.accounts.player.authority = context.accounts.signer.key();
10+
context.accounts.player.bump = context.bumps.player;
11+
// init_if_needed — only save bump if this is the first init. Subsequent
12+
// calls reuse the existing account and must not overwrite the stored bump
13+
// (they'd be equal anyway because PDA derivation is deterministic, but
14+
// guarding keeps the intent crystal-clear).
15+
if context.accounts.game_data.bump == 0 {
16+
context.accounts.game_data.bump = context.bumps.game_data;
17+
}
1018
Ok(())
1119
}
1220

tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/state/game_data.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use crate::constants::MAX_WOOD_PER_TREE;
66
#[derive(InitSpace)]
77
pub struct GameData {
88
pub total_wood_collected: u64,
9+
/// Canonical bump for this PDA.
10+
pub bump: u8,
911
}
1012

1113
impl GameData {

tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/state/player_data.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ pub struct PlayerData {
1616
pub energy: u64,
1717
pub last_login: i64,
1818
pub last_id: u16,
19+
/// Canonical bump for this PDA.
20+
pub bump: u8,
1921
}
2022

2123
impl PlayerData {

tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,7 @@ pub fn handler(mut context: Context<InitializeExtraAccountMetaList>) -> Result<(
4545
&extra_account_metas,
4646
).map_err(|_| ProgramError::InvalidAccountData)?;
4747

48+
context.accounts.counter_account.bump = context.bumps.counter_account;
49+
4850
Ok(())
4951
}

tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,6 @@ pub fn handle_extra_account_metas_count() -> usize {
9090
#[derive(InitSpace)]
9191
pub struct CounterAccount {
9292
pub counter: u64,
93+
/// Canonical bump for this PDA.
94+
pub bump: u8,
9395
}

tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_wallet.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ pub struct InitWallet<'info> {
2929
}
3030

3131
impl InitWallet<'_> {
32-
pub fn init_wallet(&mut self, args: InitWalletArgs) -> Result<()> {
32+
pub fn init_wallet(&mut self, args: InitWalletArgs, bump: u8) -> Result<()> {
3333
let ab_wallet = &mut self.ab_wallet;
3434
ab_wallet.wallet = self.wallet.key();
3535
ab_wallet.allowed = args.allowed;
36+
ab_wallet.bump = bump;
3637
Ok(())
3738
}
3839
}

tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ pub mod abl_token {
3838
}
3939

4040
pub fn init_wallet(context: Context<InitWallet>, args: InitWalletArgs) -> Result<()> {
41-
context.accounts.init_wallet(args)
41+
let bump = context.bumps.ab_wallet;
42+
context.accounts.init_wallet(args, bump)
4243
}
4344

4445
pub fn remove_wallet(context: Context<RemoveWallet>) -> Result<()> {

tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/state.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use anchor_lang::prelude::*;
1010
pub struct ABWallet {
1111
pub wallet: Pubkey,
1212
pub allowed: bool,
13+
/// Canonical bump for this PDA.
14+
pub bump: u8,
1315
}
1416

1517
#[account]

0 commit comments

Comments
 (0)