|
| 1 | +#![deny(warnings)] |
| 2 | +#![allow(clippy::result_large_err)] |
| 3 | + |
| 4 | +use anchor_lang::{ |
| 5 | + prelude::*, |
| 6 | + solana_program::{ |
| 7 | + bpf_loader_upgradeable, |
| 8 | + program::{ |
| 9 | + invoke, |
| 10 | + invoke_signed, |
| 11 | + }, |
| 12 | + }, |
| 13 | +}; |
| 14 | + |
| 15 | +#[cfg(test)] |
| 16 | +mod tests; |
| 17 | + |
| 18 | +declare_id!("escMHe7kSqPcDHx4HU44rAHhgdTLBZkUrU39aN8kMcL"); |
| 19 | +const ONE_YEAR: i64 = 365 * 24 * 60 * 60; |
| 20 | + |
| 21 | +#[program] |
| 22 | +pub mod program_authority_timelock { |
| 23 | + use super::*; |
| 24 | + |
| 25 | + pub fn commit(ctx: Context<Commit>, timestamp: i64) -> Result<()> { |
| 26 | + let current_authority = &ctx.accounts.current_authority; |
| 27 | + let escrow_authority = &ctx.accounts.escrow_authority; |
| 28 | + let program_account = &ctx.accounts.program_account; |
| 29 | + |
| 30 | + invoke( |
| 31 | + &bpf_loader_upgradeable::set_upgrade_authority( |
| 32 | + &program_account.key(), |
| 33 | + ¤t_authority.key(), |
| 34 | + Some(&escrow_authority.key()), |
| 35 | + ), |
| 36 | + &ctx.accounts.to_account_infos(), |
| 37 | + )?; |
| 38 | + |
| 39 | + // Check that the timelock is no longer than 1 year |
| 40 | + if Clock::get()?.unix_timestamp.saturating_add(ONE_YEAR) < timestamp { |
| 41 | + return Err(ErrorCode::TimestampTooLate.into()); |
| 42 | + } |
| 43 | + |
| 44 | + Ok(()) |
| 45 | + } |
| 46 | + |
| 47 | + pub fn transfer(ctx: Context<Transfer>, timestamp: i64) -> Result<()> { |
| 48 | + let new_authority = &ctx.accounts.new_authority; |
| 49 | + let escrow_authority = &ctx.accounts.escrow_authority; |
| 50 | + let program_account = &ctx.accounts.program_account; |
| 51 | + |
| 52 | + invoke_signed( |
| 53 | + &bpf_loader_upgradeable::set_upgrade_authority( |
| 54 | + &program_account.key(), |
| 55 | + &escrow_authority.key(), |
| 56 | + Some(&new_authority.key()), |
| 57 | + ), |
| 58 | + &ctx.accounts.to_account_infos(), |
| 59 | + &[&[ |
| 60 | + new_authority.key().as_ref(), |
| 61 | + timestamp.to_be_bytes().as_ref(), |
| 62 | + &[*ctx.bumps.get("escrow_authority").unwrap()], |
| 63 | + ]], |
| 64 | + )?; |
| 65 | + |
| 66 | + if Clock::get()?.unix_timestamp < timestamp { |
| 67 | + return Err(ErrorCode::TimestampTooEarly.into()); |
| 68 | + } |
| 69 | + |
| 70 | + Ok(()) |
| 71 | + } |
| 72 | +} |
| 73 | + |
| 74 | +#[derive(Accounts)] |
| 75 | +#[instruction(timestamp : i64)] |
| 76 | +pub struct Commit<'info> { |
| 77 | + pub current_authority: Signer<'info>, |
| 78 | + /// CHECK: Unchecked new authority, can be a native wallet or a PDA of another program |
| 79 | + pub new_authority: AccountInfo<'info>, |
| 80 | + #[account(seeds = [new_authority.key().as_ref(), timestamp.to_be_bytes().as_ref()], bump)] |
| 81 | + pub escrow_authority: SystemAccount<'info>, |
| 82 | + #[account(executable, constraint = matches!(program_account.as_ref(), UpgradeableLoaderState::Program{..}))] |
| 83 | + pub program_account: Account<'info, UpgradeableLoaderState>, |
| 84 | + #[account(mut, seeds = [program_account.key().as_ref()], bump, seeds::program = bpf_upgradable_loader.key())] |
| 85 | + pub program_data: Account<'info, ProgramData>, |
| 86 | + pub bpf_upgradable_loader: Program<'info, BpfUpgradableLoader>, |
| 87 | +} |
| 88 | + |
| 89 | +#[derive(Accounts)] |
| 90 | +#[instruction(timestamp : i64)] |
| 91 | +pub struct Transfer<'info> { |
| 92 | + /// CHECK: Unchecked new authority, can be a native wallet or a PDA of another program |
| 93 | + pub new_authority: AccountInfo<'info>, |
| 94 | + #[account(seeds = [new_authority.key().as_ref(), timestamp.to_be_bytes().as_ref()], bump)] |
| 95 | + pub escrow_authority: SystemAccount<'info>, |
| 96 | + #[account(executable, constraint = matches!(program_account.as_ref(), UpgradeableLoaderState::Program{..}))] |
| 97 | + pub program_account: Account<'info, UpgradeableLoaderState>, |
| 98 | + #[account(mut, seeds = [program_account.key().as_ref()], bump, seeds::program = bpf_upgradable_loader.key())] |
| 99 | + pub program_data: Account<'info, ProgramData>, |
| 100 | + pub bpf_upgradable_loader: Program<'info, BpfUpgradableLoader>, |
| 101 | +} |
| 102 | + |
| 103 | +#[derive(Clone)] |
| 104 | +pub struct BpfUpgradableLoader {} |
| 105 | + |
| 106 | +impl Id for BpfUpgradableLoader { |
| 107 | + fn id() -> Pubkey { |
| 108 | + bpf_loader_upgradeable::id() |
| 109 | + } |
| 110 | +} |
| 111 | + |
| 112 | +#[error_code] |
| 113 | +#[derive(PartialEq, Eq)] |
| 114 | +pub enum ErrorCode { |
| 115 | + #[msg("Timestamp too early")] |
| 116 | + TimestampTooEarly, |
| 117 | + #[msg("Timestamp too late")] |
| 118 | + TimestampTooLate, |
| 119 | +} |
0 commit comments