sol_cerberus/instructions/
add_rule.rs

1use anchor_spl::{metadata::MetadataAccount, token::TokenAccount};
2use crate::instructions::allowed::{allowed, AllowedRule};
3use crate::utils::{valid_rules, utc_now, validate_ns_permission, roles::address_or_wildcard};
4use crate::state::role::Role;
5use anchor_lang::prelude::*;
6use crate::state::app::{App, Seed};
7use crate::state::rule::*;
8use crate::Errors;
9use crate::metadata_program;
10
11// SPACE SIZE:
12// + 8 discriminator
13// + 32 app_id (Pubkey)
14// + 1 namespace (u8)
15// + 4 + 16 role (string)
16// + 4 + 16 resource (string)
17// + 4 + 16  permission (string)
18// + 1 + 8 expires_at Option<i64>
19// + 1 bump
20// total = 8 + 32 + 1 + 4 + 16 + 4 + 16 + 4 + 16  + 1 + 8 + 1 = 111
21#[derive(Accounts)]
22#[instruction(rule_data:RuleData)]
23pub struct AddRule<'info> {
24    #[account(mut)]
25    pub signer: Signer<'info>,
26    #[account(
27        init,
28        payer = signer,
29        space = 111,
30        seeds = [rule_data.namespace.to_le_bytes().as_ref(), rule_data.role.as_ref(), rule_data.resource.as_ref(), rule_data.permission.as_ref(), sol_cerberus_app.id.key().as_ref()], 
31        constraint = valid_rules(&rule_data.role, &rule_data.resource, &rule_data.permission)  @ Errors::InvalidRule,
32        bump
33    )]
34    pub rule: Account<'info, Rule>,
35    #[account(
36        seeds = [b"app".as_ref(), sol_cerberus_app.id.key().as_ref()],
37        bump = sol_cerberus_app.bump,
38    )]
39    pub sol_cerberus_app: Box<Account<'info, App>>,
40    #[account(
41        seeds = [sol_cerberus_role.role.as_ref(),  address_or_wildcard(&sol_cerberus_role.address), sol_cerberus_role.app_id.key().as_ref()],
42        bump = sol_cerberus_role.bump
43    )]
44    pub sol_cerberus_role: Option<Box<Account<'info, Role>>>,
45    #[account(
46        seeds = [sol_cerberus_rule.namespace.to_le_bytes().as_ref(), sol_cerberus_rule.role.as_ref(), sol_cerberus_rule.resource.as_ref(), sol_cerberus_rule.permission.as_ref(), sol_cerberus_rule.app_id.key().as_ref()],
47        bump = sol_cerberus_rule.bump,
48    )]
49    pub sol_cerberus_rule: Option<Box<Account<'info, Rule>>>,
50    #[account(
51        seeds = [sol_cerberus_rule2.namespace.to_le_bytes().as_ref(), sol_cerberus_rule2.role.as_ref(), sol_cerberus_rule2.resource.as_ref(), sol_cerberus_rule2.permission.as_ref(), sol_cerberus_rule2.app_id.key().as_ref()],
52        bump = sol_cerberus_rule2.bump,
53    )]
54    pub sol_cerberus_rule2: Option<Box<Account<'info, Rule>>>,
55    #[account()]
56    pub sol_cerberus_token: Option<Box<Account<'info, TokenAccount>>>,
57    #[account(
58        seeds = [b"metadata", metadata_program::ID.as_ref(), sol_cerberus_metadata.mint.key().as_ref()],
59        seeds::program = metadata_program::ID,
60        bump,
61    )]
62    pub sol_cerberus_metadata: Option<Box<Account<'info, MetadataAccount>>>,
63    #[account(
64        init_if_needed,
65        payer = signer,
66        space = 9, // Account discriminator + initialized
67        seeds = [b"seed".as_ref(), signer.key.as_ref()],
68        bump
69    )]
70    pub sol_cerberus_seed: Option<Account<'info, Seed>>,
71    pub system_program: Program<'info, System>,
72}
73
74pub fn add_rule(
75    ctx: Context<AddRule>,
76    data:RuleData
77) -> Result<()> {
78    // Checks if is allowed to add a rule for this specific Namespace and Role.
79    allowed(
80        &ctx.accounts.signer,
81        &ctx.accounts.sol_cerberus_app,
82        &ctx.accounts.sol_cerberus_role,
83        &ctx.accounts.sol_cerberus_rule,
84        &ctx.accounts.sol_cerberus_token,
85        &ctx.accounts.sol_cerberus_metadata,
86        &mut ctx.accounts.sol_cerberus_seed,
87        &ctx.accounts.system_program,
88        AllowedRule {
89            app_id: ctx.accounts.sol_cerberus_app.id.key(),
90            namespace: Namespaces::AddRuleNSRole as u8,
91            resource: data.namespace.to_string(),
92            permission: data.role.to_string(),
93        },
94    )?;
95    // // Checks if is allowed to add a rule for this specific Resource and Permission.
96    allowed(
97        &ctx.accounts.signer,
98        &ctx.accounts.sol_cerberus_app,
99        &ctx.accounts.sol_cerberus_role,
100        &ctx.accounts.sol_cerberus_rule2,
101        &ctx.accounts.sol_cerberus_token,
102        &ctx.accounts.sol_cerberus_metadata,
103        &mut ctx.accounts.sol_cerberus_seed,
104        &ctx.accounts.system_program,
105        AllowedRule {
106            app_id: ctx.accounts.sol_cerberus_app.id.key(),
107            namespace: Namespaces::AddRuleResourcePerm as u8,
108            resource: data.resource.to_string(),
109            permission: data.permission.to_string(),
110        },
111    )?;
112
113    // Validate AddressType when creating "AssignRole" or "DeleteAssignRole" rules (Resource can only be Wallet, Nft, Collection or wildcard "*")
114    if data.namespace >= Namespaces::AssignRole as u8 && data.namespace <= Namespaces::DeleteAssignRole as u8 {
115        if !matches!(data.resource.as_str(), "Wallet" | "Nft" | "Collection" | "*") {
116                return Err(error!(Errors::InvalidAddressType))
117        }
118    }
119    
120    // Validate Namespace when creating "AddRuleNSRole", "DeleteRuleNSRole" rules.
121    // The allowed namespace must be either an u8 number (0-255) or a wildcard "*"
122    if data.namespace == Namespaces::AddRuleNSRole as u8 && data.namespace == Namespaces::DeleteRuleNSRole as u8 {
123        validate_ns_permission(&data.resource)?;
124    }
125
126    // Add permission
127    let rule = &mut ctx.accounts.rule;
128    rule.bump = ctx.bumps.rule;
129    rule.app_id = ctx.accounts.sol_cerberus_app.id;
130    rule.namespace = data.namespace;
131    rule.role = data.role;
132    rule.resource = data.resource;
133    rule.permission = data.permission;
134    rule.expires_at = data.expires_at;
135    emit!(RulesChanged {
136        time: utc_now(),
137        app_id: ctx.accounts.sol_cerberus_app.id,
138    });
139    Ok(())
140}