gmsol_timelock/instructions/
config.rs

1use anchor_lang::prelude::*;
2use gmsol_store::{
3    program::GmsolStore,
4    states::{Seed, Store, MAX_ROLE_NAME_LEN},
5    utils::{fixed_str::fixed_str_to_bytes, CpiAuthenticate, CpiAuthentication, WithStore},
6    CoreError,
7};
8use gmsol_utils::InitSpace;
9
10use crate::{
11    roles,
12    states::{config::TimelockConfig, Executor, ExecutorWalletSigner},
13};
14
15/// The accounts definition for [`initialize_config`](crate::gmsol_timelock::initialize_config).
16#[derive(Accounts)]
17pub struct InitializeConfig<'info> {
18    /// Authority.
19    #[account(mut)]
20    pub authority: Signer<'info>,
21    /// Store.
22    /// CHECK: check by CPI.
23    #[account(mut)]
24    pub store: AccountLoader<'info, Store>,
25    /// Config.
26    #[account(
27        init,
28        payer = authority,
29        space = 8 + TimelockConfig::INIT_SPACE,
30        seeds = [TimelockConfig::SEED, store.key().as_ref()],
31        bump,
32    )]
33    pub timelock_config: AccountLoader<'info, TimelockConfig>,
34    /// Admin executor.
35    #[account(
36        has_one = store,
37        constraint = executor.load()?.role_name()? == roles::ADMIN @ CoreError::InvalidArgument,
38        seeds = [
39            Executor::SEED,
40            store.key().as_ref(),
41            &fixed_str_to_bytes::<MAX_ROLE_NAME_LEN>(roles::ADMIN)?,
42        ],
43        bump = executor.load()?.bump,
44    )]
45    pub executor: AccountLoader<'info, Executor>,
46    /// Admin executor wallet.
47    #[account(
48        seeds = [Executor::WALLET_SEED, executor.key().as_ref()],
49        bump,
50    )]
51    pub wallet: SystemAccount<'info>,
52    /// Store program.
53    pub store_program: Program<'info, GmsolStore>,
54    /// System program.
55    pub system_program: Program<'info, System>,
56}
57
58/// Initialize timelock config.
59/// # CHECK
60/// Only [`TIMELOCK_ADMIN`](crate::roles::TIMELOCK_ADMIN) can use.
61pub(crate) fn unchecked_initialize_config(
62    ctx: Context<InitializeConfig>,
63    delay: u32,
64) -> Result<()> {
65    // Ensure at least one address can create and execute timelocked instructions.
66    CpiAuthenticate::only(&ctx, roles::TIMELOCK_KEEPER)?;
67    // Ensure at least one address can approve timelocked instructions requiring ADMIN permission,
68    // such as granting roles to other addresses.
69    CpiAuthenticate::only(&ctx, roles::TIMELOCKED_ADMIN)?;
70
71    let admin_executor_wallet_bump = ctx.bumps.wallet;
72
73    ctx.accounts
74        .accept_store_authority(admin_executor_wallet_bump)?;
75
76    ctx.accounts.timelock_config.load_init()?.init(
77        ctx.bumps.timelock_config,
78        delay,
79        ctx.accounts.store.key(),
80    );
81    msg!(
82        "[Timelock] Initialized timelock config with delay = {}",
83        delay
84    );
85    Ok(())
86}
87
88impl<'info> WithStore<'info> for InitializeConfig<'info> {
89    fn store_program(&self) -> AccountInfo<'info> {
90        self.store_program.to_account_info()
91    }
92
93    fn store(&self) -> AccountInfo<'info> {
94        self.store.to_account_info()
95    }
96}
97
98impl<'info> CpiAuthentication<'info> for InitializeConfig<'info> {
99    fn authority(&self) -> AccountInfo<'info> {
100        self.authority.to_account_info()
101    }
102
103    fn on_error(&self) -> Result<()> {
104        err!(CoreError::PermissionDenied)
105    }
106}
107
108impl InitializeConfig<'_> {
109    fn accept_store_authority(&self, admin_executor_wallet_bump: u8) -> Result<()> {
110        use gmsol_store::cpi::{accept_store_authority, accounts::AcceptStoreAuthority};
111
112        if !self.store.load()?.is_authority(self.wallet.key) {
113            let signer = ExecutorWalletSigner::new(self.executor.key(), admin_executor_wallet_bump);
114            accept_store_authority(
115                CpiContext::new(
116                    self.store_program.to_account_info(),
117                    AcceptStoreAuthority {
118                        next_authority: self.wallet.to_account_info(),
119                        store: self.store.to_account_info(),
120                    },
121                )
122                .with_signer(&[&signer.as_seeds()]),
123            )?;
124        }
125
126        Ok(())
127    }
128}
129
130/// The accounts definition for [`increase_delay`](crate::gmsol_timelock::increase_delay).
131#[derive(Accounts)]
132pub struct IncreaseDelay<'info> {
133    /// Authority.
134    #[account(mut)]
135    pub authority: Signer<'info>,
136    /// Store.
137    /// CHECK: check by CPI.
138    pub store: UncheckedAccount<'info>,
139    #[account(mut, has_one = store)]
140    pub timelock_config: AccountLoader<'info, TimelockConfig>,
141    /// Store program.
142    pub store_program: Program<'info, GmsolStore>,
143}
144
145/// Increase delay.
146/// # CHECK
147/// Only [`TIMELOCK_ADMIN`](crate::roles::TIMELOCK_ADMIN) can use.
148pub(crate) fn unchecked_increase_delay(ctx: Context<IncreaseDelay>, delta: u32) -> Result<()> {
149    require_neq!(delta, 0, CoreError::InvalidArgument);
150    let new_delay = ctx
151        .accounts
152        .timelock_config
153        .load_mut()?
154        .increase_delay(delta)?;
155    msg!(
156        "[Timelock] Timelock delay increased, new delay = {}",
157        new_delay
158    );
159    Ok(())
160}
161
162impl<'info> WithStore<'info> for IncreaseDelay<'info> {
163    fn store_program(&self) -> AccountInfo<'info> {
164        self.store_program.to_account_info()
165    }
166
167    fn store(&self) -> AccountInfo<'info> {
168        self.store.to_account_info()
169    }
170}
171
172impl<'info> CpiAuthentication<'info> for IncreaseDelay<'info> {
173    fn authority(&self) -> AccountInfo<'info> {
174        self.authority.to_account_info()
175    }
176
177    fn on_error(&self) -> Result<()> {
178        err!(CoreError::PermissionDenied)
179    }
180}