Skip to main content

multisig/instructions/
add_asset_mint.rs

1use anchor_lang::{prelude::*, solana_program::program_option::COption};
2use anchor_spl::{
3    token::ID as TOKEN_PROGRAM_ID,
4    token_interface::{
5        spl_token_2022::{
6            self,
7            extension::{BaseStateWithExtensions, StateWithExtensions},
8            state::Mint as Token2022Mint,
9        },
10        Mint, TokenInterface,
11    },
12};
13
14use crate::{utils::fractional_threshold::FractionalThreshold, Permissions};
15
16use crate::state::{
17    asset::Asset,
18    error::MultisigError,
19    group::Group,
20    member::{AssetMember, GroupMember},
21};
22
23#[inline(always)]
24fn require_supported_mint_extensions(mint: &AccountInfo<'_>, token_program: Pubkey) -> Result<()> {
25    if token_program == TOKEN_PROGRAM_ID {
26        return Ok(());
27    }
28
29    require_keys_eq!(
30        token_program,
31        spl_token_2022::ID,
32        MultisigError::UnsupportedTokenProgram
33    );
34
35    let data = mint.data.borrow();
36    let mint_with_extensions = StateWithExtensions::<Token2022Mint>::unpack(&data)?;
37    let ext_types = mint_with_extensions
38        .get_extension_types()
39        .map_err(|_| MultisigError::UnsupportedTokenExtensions)?;
40    require!(
41        ext_types.is_empty(),
42        MultisigError::UnsupportedTokenExtensions
43    );
44
45    Ok(())
46}
47
48#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
49pub struct AddAssetMintInstructionArgs {
50    pub member_key_1: Pubkey,
51    pub member_key_2: Pubkey,
52    pub member_key_3: Pubkey,
53    pub initial_weights: [u32; 3],
54    pub initial_permissions: [Permissions; 3],
55    pub use_threshold: FractionalThreshold,
56    pub not_use_threshold: FractionalThreshold,
57    pub add_threshold: FractionalThreshold,
58    pub not_add_threshold: FractionalThreshold,
59    pub remove_threshold: FractionalThreshold,
60    pub not_remove_threshold: FractionalThreshold,
61    pub change_config_threshold: FractionalThreshold,
62    pub not_change_config_threshold: FractionalThreshold,
63    pub minimum_member_count: u32,
64    pub minimum_vote_count: u32,
65}
66
67#[derive(Accounts)]
68#[instruction(args: AddAssetMintInstructionArgs)]
69pub struct AddAssetMintInstructionAccounts<'info> {
70    #[account(mut)]
71    pub payer: Signer<'info>,
72
73    #[account(
74        mut,
75        seeds = [b"group", group.group_seed.as_ref()],
76        bump = group.account_bump
77    )]
78    pub group: Box<Account<'info, Group>>,
79
80    pub mint: InterfaceAccount<'info, Mint>,
81
82    #[account(
83        init,
84        payer = payer,
85        space = 8 + Asset::INIT_SPACE,
86        seeds = [b"asset", group.key().as_ref(), mint.key().as_ref()],
87        bump
88    )]
89    pub asset: Account<'info, Asset>,
90
91    #[account(
92        seeds = [b"authority", group.key().as_ref(), mint.key().as_ref()],
93        bump
94    )]
95    /// CHECK: New asset authority
96    pub asset_authority: UncheckedAccount<'info>,
97
98    #[account(
99        seeds = [b"member", group.key().as_ref(), payer.key.as_ref()],
100        bump = adder.account_bump
101    )]
102    pub adder: Account<'info, GroupMember>,
103
104    #[account(
105        seeds = [b"member", group.key().as_ref(), args.member_key_1.as_ref()],
106        bump = group_member_1.account_bump
107    )]
108    pub group_member_1: Box<Account<'info, GroupMember>>,
109
110    #[account(
111        seeds = [b"member", group.key().as_ref(), args.member_key_2.as_ref()],
112        bump = group_member_2.account_bump
113    )]
114    pub group_member_2: Box<Account<'info, GroupMember>>,
115
116    #[account(
117        seeds = [b"member", group.key().as_ref(), args.member_key_3.as_ref()],
118        bump = group_member_3.account_bump
119    )]
120    pub group_member_3: Box<Account<'info, GroupMember>>,
121
122    #[account(
123        init,
124        payer = payer,
125        space = 8 + AssetMember::INIT_SPACE,
126        seeds = [b"asset-member", group.key().as_ref(), mint.key().as_ref(), args.member_key_1.as_ref()],
127        bump
128    )]
129    pub asset_member_1: Box<Account<'info, AssetMember>>,
130
131    #[account(
132        init,
133        payer = payer,
134        space = 8 + AssetMember::INIT_SPACE,
135        seeds = [b"asset-member", group.key().as_ref(), mint.key().as_ref(), args.member_key_2.as_ref()],
136        bump
137    )]
138    pub asset_member_2: Box<Account<'info, AssetMember>>,
139
140    #[account(
141        init,
142        payer = payer,
143        space = 8 + AssetMember::INIT_SPACE,
144        seeds = [b"asset-member", group.key().as_ref(), mint.key().as_ref(), args.member_key_3.as_ref()],
145        bump
146    )]
147    pub asset_member_3: Box<Account<'info, AssetMember>>,
148
149    pub token_program: Interface<'info, TokenInterface>,
150
151    pub system_program: Program<'info, System>,
152}
153
154#[inline(always)]
155fn checks(ctx: &Context<AddAssetMintInstructionAccounts>) -> Result<()> {
156    require!(!ctx.accounts.group.paused, MultisigError::GroupPaused);
157
158    require_supported_mint_extensions(
159        &ctx.accounts.mint.to_account_info(),
160        ctx.accounts.token_program.key(),
161    )?;
162
163    require!(
164        ctx.accounts.adder.has_add_asset(),
165        MultisigError::InsufficientPermissions
166    );
167
168    require_keys_eq!(
169        ctx.accounts
170            .mint
171            .mint_authority
172            .ok_or(MultisigError::AuthorityNotProvided)?,
173        *ctx.accounts.asset_authority.key,
174        MultisigError::InvalidMintMintAuthority
175    );
176
177    // If the freeze authority is not set ignore
178    match ctx.accounts.mint.freeze_authority.as_ref() {
179        COption::Some(freeze_authority) => {
180            require_keys_eq!(
181                *freeze_authority,
182                *ctx.accounts.asset_authority.key,
183                MultisigError::InvalidMintFreezeAuthority
184            );
185        }
186        COption::None => {} // Ok
187    }
188
189    Ok(())
190}
191
192/// Registers a new token mint that is controlled by the multisig.
193pub fn add_asset_mint_handler(
194    ctx: Context<AddAssetMintInstructionAccounts>,
195    args: AddAssetMintInstructionArgs,
196) -> Result<()> {
197    checks(&ctx)?;
198
199    let AddAssetMintInstructionArgs {
200        member_key_1,
201        member_key_2,
202        member_key_3,
203        initial_weights,
204        initial_permissions,
205        use_threshold,
206        not_use_threshold,
207        add_threshold,
208        not_add_threshold,
209        remove_threshold,
210        not_remove_threshold,
211        change_config_threshold,
212        not_change_config_threshold,
213        minimum_member_count,
214        minimum_vote_count,
215    } = args;
216
217    let mint_key = ctx.accounts.mint.key();
218    let asset_acc = &mut ctx.accounts.asset;
219
220    asset_acc.set_inner(Asset::new(
221        mint_key,
222        use_threshold,
223        not_use_threshold,
224        add_threshold,
225        not_add_threshold,
226        remove_threshold,
227        not_remove_threshold,
228        change_config_threshold,
229        not_change_config_threshold,
230        minimum_member_count,
231        minimum_vote_count,
232        3,
233        ctx.bumps.asset,
234        ctx.bumps.asset_authority,
235    )?);
236
237    let member_keys = [member_key_1, member_key_2, member_key_3];
238    let member_bumps = [
239        ctx.bumps.asset_member_1,
240        ctx.bumps.asset_member_2,
241        ctx.bumps.asset_member_3,
242    ];
243    let mut asset_members: [&mut Account<AssetMember>; 3] = [
244        &mut ctx.accounts.asset_member_1,
245        &mut ctx.accounts.asset_member_2,
246        &mut ctx.accounts.asset_member_3,
247    ];
248
249    for ((((asset_member, key), bump), weight), permissions) in asset_members
250        .iter_mut()
251        .zip(member_keys.into_iter())
252        .zip(member_bumps.into_iter())
253        .zip(initial_weights.into_iter())
254        .zip(initial_permissions.into_iter())
255    {
256        (*asset_member).set_inner(AssetMember::new(
257            key,
258            ctx.accounts.group.key(),
259            asset_acc.asset_address,
260            permissions,
261            weight,
262            bump,
263            ctx.accounts.group.max_member_weight,
264        )?);
265    }
266
267    Ok(())
268}