Skip to content

Commit 9693677

Browse files
authored
Authority timelock (#3)
* Authority timelock * Cleanup * Clippy * Comment * Renames
1 parent 05713de commit 9693677

7 files changed

Lines changed: 564 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[package]
2+
name = "program-authority-timelock"
3+
version = "1.0.0"
4+
description = "Created with Anchor"
5+
edition = "2021"
6+
7+
[lib]
8+
crate-type = ["cdylib", "lib"]
9+
name = "program_authority_timelock"
10+
11+
[features]
12+
no-entrypoint = []
13+
no-idl = []
14+
no-log-ix-name = []
15+
cpi = ["no-entrypoint"]
16+
default = []
17+
18+
[dependencies]
19+
anchor-lang = "0.26.0"
20+
21+
[dev-dependencies]
22+
solana-program-test = "=1.14.7"
23+
solana-sdk = "=1.14.7"
24+
tokio = "1.14.1"
25+
bincode = "1.3.3"
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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+
&current_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+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
mod simulator;
2+
mod test;

0 commit comments

Comments
 (0)