◂ labs
TIER 2 · CPI & PDAs · 8-pda-sharing
PDA SHARING
Reusing one PDA as the authority for many things (vault + mint + pool) means any instruction that can sign with it controls all of them. Seeds should encode domain + instance: one PDA, one job.
seen in the wild
Over-powered PDAs turn one buggy instruction into total protocol control.
rust · anchor
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Token, TokenAccount};
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod pda_sharing_insecure {
use super::*;
pub fn withdraw_tokens(ctx: Context<WithdrawTokens>) -> ProgramResult {
let amount = ctx.accounts.vault.amount;
let seeds = &[ctx.accounts.pool.mint.as_ref(), &[ctx.accounts.pool.bump]];
token::transfer(ctx.accounts.transfer_ctx().with_signer(&[seeds]), amount)
}
}
#[derive(Accounts)]
pub struct WithdrawTokens<'info> {
#[account(has_one = vault, has_one = withdraw_destination)]
pool: Account<'info, TokenPool>,
vault: Account<'info, TokenAccount>,
withdraw_destination: Account<'info, TokenAccount>,
authority: Signer<'info>,
token_program: Program<'info, Token>,
}
impl<'info> WithdrawTokens<'info> {
pub fn transfer_ctx(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> {
let program = self.token_program.to_account_info();
let accounts = token::Transfer {
from: self.vault.to_account_info(),
to: self.withdraw_destination.to_account_info(),
authority: self.authority.to_account_info(),
};
CpiContext::new(program, accounts)
}
}
#[account]
pub struct TokenPool {
vault: Pubkey,
mint: Pubkey,
withdraw_destination: Pubkey,
bump: u8,
}
next labCLOSING ACCOUNTS & REVIVAL ▸