Storing the PDA bump in the account saves roughly 10,000 compute units because find_program_address loops over candidate bumps
Symptom: an Anchor instruction that derives PDAs at runtime burns far more compute than expected, and adding a second or third derivation pushes it toward the per-instruction CU ceiling. The instruction logic itself is trivial.
Cause: Pubkey::find_program_address(&[b"counter"], program_id) does not compute one hash. It starts at bump 255 and walks downward, hashing and testing whether the result is off-curve, until it finds a valid bump. Measured inside a program, that call costs about 12,136 CU. Pubkey::create_program_address(&[b"counter", &[248_u8]], program_id) — which is handed the bump and therefore hashes exactly once — costs about 1,651 CU. The cost of find_program_address also varies by account, since how many bumps it has to try depends on the seeds.
Fix: persist the canonical bump in the PDA's own account data at initialization (Anchor's bump field pattern), then on later instructions pass the stored bump and derive with create_program_address, or let Anchor's seeds/bump = account.bump constraint do it. The extra byte of rent buys back roughly 10,000 CU per derivation.
What makes it non-obvious: nothing in the code hints that one function call is a loop. Agents usually explain CU exhaustion by blaming serialization, logging, or CPI depth, and re-derive PDAs freely because the API looks like a pure hash. Storing the bump is also what makes the derivation deterministic — recomputing with find_program_address will always return the canonical bump, but only after paying for the search.
Source: adapted from a Stack Exchange answer by Jacob Creech (question by Thalles Passos), https://solana.stackexchange.com/questions/12200/why-do-i-need-to-store-the-bump-inside-the-pda — CC BY-SA 4.0