Skip to main content

percli_program/instructions/
transfer_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/// Initiate a two-step authority transfer. Writes `new_authority` into
8/// `header.pending_authority` but does **not** rotate `header.authority` —
9/// that only happens when the pending party calls `accept_authority`.
10///
11/// Passing `Pubkey::default()` as `new_authority` cancels any in-flight
12/// transfer (since `accept_authority` rejects the default pubkey as a signer
13/// and explicitly rejects `pending_authority == default`).
14///
15/// Self-transfer (`new_authority == header.authority`) is rejected with
16/// `Unauthorized` because it serves no purpose and would emit a confusing
17/// event for indexers.
18///
19/// Calling this with a different `new_authority` while a transfer is already
20/// in flight overwrites the previous `pending_authority` — this is intentional
21/// (it lets the authority change their mind) and emits
22/// `AuthorityTransferInitiated` for the new pending key.
23#[derive(Accounts)]
24pub struct TransferAuthority<'info> {
25    pub authority: Signer<'info>,
26
27    /// CHECK: Validated via owner, discriminator, and size.
28    #[account(
29        mut,
30        owner = crate::ID @ PercolatorError::AccountNotFound,
31        constraint = market.data_len() >= MARKET_ACCOUNT_SIZE @ PercolatorError::AccountNotFound,
32    )]
33    pub market: UncheckedAccount<'info>,
34}
35
36pub fn handler(ctx: Context<TransferAuthority>, new_authority: Pubkey) -> Result<()> {
37    let market = &ctx.accounts.market;
38    let mut data = market.try_borrow_mut_data()?;
39
40    require!(
41        crate::state::is_v1_market(&data),
42        PercolatorError::AccountNotFound
43    );
44
45    let mut header = header_from_account_data(&data)?;
46    require!(
47        header.authority == ctx.accounts.authority.key(),
48        PercolatorError::Unauthorized
49    );
50    // Reject self-transfer: it's a no-op and only adds event noise.
51    require!(
52        new_authority != header.authority,
53        PercolatorError::Unauthorized
54    );
55
56    let previous_pending = header.pending_authority;
57    header.pending_authority = new_authority;
58    write_header(&mut data, &header);
59
60    if new_authority == Pubkey::default() {
61        emit!(events::AuthorityTransferCancelled {
62            market: market.key(),
63            authority: header.authority,
64            previous_pending,
65        });
66    } else {
67        emit!(events::AuthorityTransferInitiated {
68            market: market.key(),
69            old_authority: header.authority,
70            pending_authority: new_authority,
71        });
72    }
73
74    Ok(())
75}