Skip to main content

percli_program/instructions/
accept_authority.rs

1use anchor_lang::prelude::*;
2
3use crate::error::PercolatorError;
4use crate::instructions::events;
5use crate::state::{header_from_account_data, write_header, MARKET_ACCOUNT_SIZE};
6
7/// Complete a two-step authority transfer. The caller must match
8/// `header.pending_authority`, which must not be `Pubkey::default()`.
9///
10/// On success:
11///   - `header.authority` becomes the new authority.
12///   - `header.pending_authority` is reset to `Pubkey::default()`.
13///   - `AuthorityAccepted` is emitted.
14///
15/// The two-step design prevents the authority from being accidentally handed
16/// to an invalid / unreachable pubkey, which would permanently brick the
17/// market.
18#[derive(Accounts)]
19pub struct AcceptAuthority<'info> {
20    pub new_authority: Signer<'info>,
21
22    /// CHECK: Validated via owner, discriminator, and size.
23    #[account(
24        mut,
25        owner = crate::ID @ PercolatorError::AccountNotFound,
26        constraint = market.data_len() >= MARKET_ACCOUNT_SIZE @ PercolatorError::AccountNotFound,
27    )]
28    pub market: UncheckedAccount<'info>,
29}
30
31pub fn handler(ctx: Context<AcceptAuthority>) -> Result<()> {
32    let market = &ctx.accounts.market;
33    let mut data = market.try_borrow_mut_data()?;
34
35    require!(
36        crate::state::is_v1_market(&data),
37        PercolatorError::AccountNotFound
38    );
39
40    // Defense in depth: the Solana runtime already rejects `Pubkey::default()`
41    // as a signer (it has no corresponding private key), so this check is
42    // theoretically unreachable. We keep it because it makes the security
43    // intent legible inside the handler and protects against any future
44    // runtime change.
45    require!(
46        ctx.accounts.new_authority.key() != Pubkey::default(),
47        PercolatorError::Unauthorized
48    );
49
50    let mut header = header_from_account_data(&data)?;
51    require!(
52        header.pending_authority != Pubkey::default(),
53        PercolatorError::NoPendingAuthority
54    );
55    require!(
56        header.pending_authority == ctx.accounts.new_authority.key(),
57        PercolatorError::Unauthorized
58    );
59
60    let old_authority = header.authority;
61    let new_authority = header.pending_authority;
62    header.authority = new_authority;
63    header.pending_authority = Pubkey::default();
64    write_header(&mut data, &header);
65
66    emit!(events::AuthorityAccepted {
67        market: market.key(),
68        old_authority,
69        new_authority,
70    });
71
72    Ok(())
73}