gmsol_timelock/instructions/bypass/
revoke_role.rs

1use anchor_lang::prelude::*;
2use gmsol_store::{
3    cpi::{accounts::RevokeRole as StoreRevokeRole, revoke_role},
4    program::GmsolStore,
5    states::{Seed, MAX_ROLE_NAME_LEN},
6    utils::{fixed_str::fixed_str_to_bytes, CpiAuthentication, WithStore},
7    CoreError,
8};
9
10use crate::{
11    roles,
12    states::{Executor, ExecutorWalletSigner},
13};
14
15const NOT_BYPASSABLE_ROLES: [&str; 3] = [
16    roles::TIMELOCKED_ADMIN,
17    roles::TIMELOCK_ADMIN,
18    roles::TIMELOCK_KEEPER,
19];
20
21/// The accounts definition for [`revoke_role`](crate::gmsol_timelock::revoke_role).
22#[derive(Accounts)]
23pub struct RevokeRole<'info> {
24    /// Authority.
25    pub authority: Signer<'info>,
26    /// Store.
27    /// CHECK: check by CPI.
28    #[account(mut)]
29    pub store: UncheckedAccount<'info>,
30    /// Executor.
31    #[account(
32        has_one = store,
33        constraint = executor.load()?.role_name()? == roles::ADMIN @ CoreError::InvalidArgument,
34        seeds = [
35            Executor::SEED,
36            store.key.as_ref(),
37            &fixed_str_to_bytes::<MAX_ROLE_NAME_LEN>(roles::ADMIN)?,
38        ],
39        bump = executor.load()?.bump,
40    )]
41    pub executor: AccountLoader<'info, Executor>,
42    /// Executor Wallet.
43    #[account(
44        mut,
45        seeds = [Executor::WALLET_SEED, executor.key().as_ref()],
46        bump,
47    )]
48    pub wallet: SystemAccount<'info>,
49    /// User.
50    /// CHECK: only its address is used.
51    pub user: UncheckedAccount<'info>,
52    /// Store program.
53    pub store_program: Program<'info, GmsolStore>,
54}
55
56/// Revoke a role. This instruction will bypass the timelock check.
57/// # CHECK
58/// Only [`TIMELOCKED_ADMIN`](roles::TIMELOCKED_ADMIN) can use.
59pub(crate) fn unchecked_revoke_role(ctx: Context<RevokeRole>, role: String) -> Result<()> {
60    require!(
61        !NOT_BYPASSABLE_ROLES.contains(&role.as_str()),
62        CoreError::InvalidArgument
63    );
64    let signer = ExecutorWalletSigner::new(ctx.accounts.executor.key(), ctx.bumps.wallet);
65    let cpi_ctx = ctx.accounts.revoke_role_ctx();
66    revoke_role(
67        cpi_ctx.with_signer(&[&signer.as_seeds()]),
68        ctx.accounts.user.key(),
69        role,
70    )?;
71    Ok(())
72}
73
74impl<'info> WithStore<'info> for RevokeRole<'info> {
75    fn store_program(&self) -> AccountInfo<'info> {
76        self.store_program.to_account_info()
77    }
78
79    fn store(&self) -> AccountInfo<'info> {
80        self.store.to_account_info()
81    }
82}
83
84impl<'info> CpiAuthentication<'info> for RevokeRole<'info> {
85    fn authority(&self) -> AccountInfo<'info> {
86        self.authority.to_account_info()
87    }
88
89    fn on_error(&self) -> Result<()> {
90        err!(CoreError::PermissionDenied)
91    }
92}
93
94impl<'info> RevokeRole<'info> {
95    fn revoke_role_ctx(&self) -> CpiContext<'_, '_, '_, 'info, StoreRevokeRole<'info>> {
96        CpiContext::new(
97            self.store_program.to_account_info(),
98            StoreRevokeRole {
99                authority: self.wallet.to_account_info(),
100                store: self.store.to_account_info(),
101            },
102        )
103    }
104}