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 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 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 => {} }
188
189 Ok(())
190}
191
192pub 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}