1use std::fmt::Display;
2
3use borsh::BorshSerialize;
4use mpl_token_auth_rules::utils::get_latest_revision;
5use mpl_utils::{assert_signer, create_or_allocate_account_raw};
6use solana_program::{
7 account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, program_option::COption,
8 program_pack::Pack, pubkey::Pubkey, system_program, sysvar,
9};
10use spl_token::{instruction::AuthorityType as SplAuthorityType, state::Account};
11
12use crate::{
13 assertions::{
14 assert_derivation, assert_keys_equal, assert_owned_by,
15 metadata::assert_update_authority_is_correct,
16 },
17 error::MetadataError,
18 instruction::{Context, Delegate, DelegateArgs, MetadataDelegateRole},
19 pda::{find_token_record_account, PREFIX},
20 processor::AuthorizationData,
21 state::{
22 Metadata, MetadataDelegateRecord, Operation, ProgrammableConfig, Resizable,
23 TokenDelegateRole, TokenMetadataAccount, TokenRecord, TokenStandard, TokenState,
24 },
25 utils::{auth_rules_validate, freeze, thaw, AuthRulesValidateParams},
26};
27
28#[derive(Clone, Debug, PartialEq, Eq)]
29pub enum DelegateScenario {
30 Metadata(MetadataDelegateRole),
31 Token(TokenDelegateRole),
32}
33
34impl Display for DelegateScenario {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 let message = match self {
37 Self::Metadata(role) => match role {
38 MetadataDelegateRole::AuthorityItem => "AuthorityItem".to_string(),
39 MetadataDelegateRole::Collection => "Collection".to_string(),
40 MetadataDelegateRole::Use => "Use".to_string(),
41 MetadataDelegateRole::Data => "Data".to_string(),
42 MetadataDelegateRole::ProgrammableConfig => "ProgrammableConfig".to_string(),
43 MetadataDelegateRole::DataItem => "DataItem".to_string(),
44 MetadataDelegateRole::CollectionItem => "CollectionItem".to_string(),
45 MetadataDelegateRole::ProgrammableConfigItem => {
46 "ProgrammableConfigItem".to_string()
47 }
48 },
49 Self::Token(role) => match role {
50 TokenDelegateRole::Sale => "Sale".to_string(),
51 TokenDelegateRole::Transfer => "Transfer".to_string(),
52 TokenDelegateRole::Utility => "Utility".to_string(),
53 TokenDelegateRole::Staking => "Staking".to_string(),
54 TokenDelegateRole::LockedTransfer => "LockedTransfer".to_string(),
55 _ => panic!("Invalid delegate role"),
56 },
57 };
58
59 write!(f, "{message}")
60 }
61}
62
63pub fn delegate<'a>(
65 program_id: &Pubkey,
66 accounts: &'a [AccountInfo<'a>],
67 args: DelegateArgs,
68) -> ProgramResult {
69 let context = Delegate::to_context(accounts)?;
70
71 let delegate_args = match &args {
73 DelegateArgs::SaleV1 {
75 amount,
76 authorization_data,
77 } => Some((TokenDelegateRole::Sale, amount, authorization_data)),
78 DelegateArgs::TransferV1 {
80 amount,
81 authorization_data,
82 } => Some((TokenDelegateRole::Transfer, amount, authorization_data)),
83 DelegateArgs::UtilityV1 {
85 amount,
86 authorization_data,
87 } => Some((TokenDelegateRole::Utility, amount, authorization_data)),
88 DelegateArgs::StakingV1 {
90 amount,
91 authorization_data,
92 } => Some((TokenDelegateRole::Staking, amount, authorization_data)),
93 DelegateArgs::StandardV1 { amount } => Some((TokenDelegateRole::Standard, amount, &None)),
95 DelegateArgs::LockedTransferV1 {
97 amount,
98 authorization_data,
99 ..
100 } => Some((
101 TokenDelegateRole::LockedTransfer,
102 amount,
103 authorization_data,
104 )),
105
106 _ => None,
108 };
109
110 if let Some((role, amount, authorization_data)) = delegate_args {
111 return create_persistent_delegate_v1(
113 program_id,
114 context,
115 &args,
116 role,
117 *amount,
118 authorization_data,
119 );
120 }
121
122 let delegate_args = match &args {
124 DelegateArgs::CollectionV1 { authorization_data } => {
125 Some((MetadataDelegateRole::Collection, authorization_data))
126 }
127 DelegateArgs::DataV1 { authorization_data } => {
128 Some((MetadataDelegateRole::Data, authorization_data))
129 }
130 DelegateArgs::ProgrammableConfigV1 { authorization_data } => {
131 Some((MetadataDelegateRole::ProgrammableConfig, authorization_data))
132 }
133 DelegateArgs::AuthorityItemV1 { authorization_data } => {
134 Some((MetadataDelegateRole::AuthorityItem, authorization_data))
135 }
136 DelegateArgs::DataItemV1 { authorization_data } => {
137 Some((MetadataDelegateRole::DataItem, authorization_data))
138 }
139 DelegateArgs::CollectionItemV1 { authorization_data } => {
140 Some((MetadataDelegateRole::CollectionItem, authorization_data))
141 }
142 DelegateArgs::ProgrammableConfigItemV1 { authorization_data } => Some((
143 MetadataDelegateRole::ProgrammableConfigItem,
144 authorization_data,
145 )),
146
147 _ => None,
149 };
150
151 if let Some((role, _authorization_data)) = delegate_args {
152 return create_delegate_v1(program_id, context, args, role);
153 }
154
155 Err(MetadataError::InvalidDelegateArgs.into())
157}
158
159fn create_delegate_v1(
163 program_id: &Pubkey,
164 ctx: Context<Delegate>,
165 _args: DelegateArgs,
166 role: MetadataDelegateRole,
167) -> ProgramResult {
168 assert_signer(ctx.accounts.payer_info)?;
171 assert_signer(ctx.accounts.authority_info)?;
172
173 assert_owned_by(ctx.accounts.metadata_info, program_id)?;
176 assert_owned_by(ctx.accounts.mint_info, &spl_token::ID)?;
177
178 assert_keys_equal(ctx.accounts.system_program_info.key, &system_program::ID)?;
181 assert_keys_equal(
182 ctx.accounts.sysvar_instructions_info.key,
183 &sysvar::instructions::ID,
184 )?;
185
186 let metadata = Metadata::from_account_info(ctx.accounts.metadata_info)?;
189 assert_update_authority_is_correct(&metadata, ctx.accounts.authority_info)?;
191
192 if metadata.mint != *ctx.accounts.mint_info.key {
193 return Err(MetadataError::MintMismatch.into());
194 }
195
196 let delegate_record_info = match ctx.accounts.delegate_record_info {
197 Some(delegate_record_info) => delegate_record_info,
198 None => {
199 return Err(MetadataError::MissingDelegateRecord.into());
200 }
201 };
202
203 let delegate_role = role.to_string();
207
208 create_pda_account(
209 program_id,
210 delegate_record_info,
211 ctx.accounts.delegate_info,
212 ctx.accounts.mint_info,
213 ctx.accounts.authority_info,
214 ctx.accounts.payer_info,
215 ctx.accounts.system_program_info,
216 &delegate_role,
217 )
218}
219
220fn create_persistent_delegate_v1(
225 program_id: &Pubkey,
226 ctx: Context<Delegate>,
227 args: &DelegateArgs,
228 role: TokenDelegateRole,
229 amount: u64,
230 authorization_data: &Option<AuthorizationData>,
231) -> ProgramResult {
232 let token_info = match ctx.accounts.token_info {
235 Some(token_info) => token_info,
236 None => {
237 return Err(MetadataError::MissingTokenAccount.into());
238 }
239 };
240
241 let spl_token_program_info = match ctx.accounts.spl_token_program_info {
242 Some(spl_token_program_info) => spl_token_program_info,
243 None => {
244 return Err(MetadataError::MissingSplTokenProgram.into());
245 }
246 };
247
248 assert_signer(ctx.accounts.payer_info)?;
251 assert_signer(ctx.accounts.authority_info)?;
252
253 assert_owned_by(ctx.accounts.metadata_info, program_id)?;
256 assert_owned_by(ctx.accounts.mint_info, &spl_token::ID)?;
257 assert_owned_by(token_info, &spl_token::ID)?;
258
259 assert_keys_equal(ctx.accounts.system_program_info.key, &system_program::ID)?;
262 assert_keys_equal(
263 ctx.accounts.sysvar_instructions_info.key,
264 &sysvar::instructions::ID,
265 )?;
266 assert_keys_equal(spl_token_program_info.key, &spl_token::ID)?;
267
268 let metadata = Metadata::from_account_info(ctx.accounts.metadata_info)?;
271 if metadata.mint != *ctx.accounts.mint_info.key {
272 return Err(MetadataError::MintMismatch.into());
273 }
274
275 let token = Account::unpack(&token_info.try_borrow_data()?).unwrap();
278 if token.owner != *ctx.accounts.authority_info.key {
279 return Err(MetadataError::IncorrectOwner.into());
280 }
281
282 match metadata.token_standard {
286 Some(TokenStandard::ProgrammableNonFungible) => {
287 if matches!(role, TokenDelegateRole::Standard) {
288 return Err(MetadataError::InvalidDelegateRole.into());
289 }
290
291 let (mut token_record, token_record_info) = match ctx.accounts.token_record_info {
292 Some(token_record_info) => {
293 let (pda_key, _) =
294 find_token_record_account(ctx.accounts.mint_info.key, token_info.key);
295
296 assert_keys_equal(&pda_key, token_record_info.key)?;
297 assert_owned_by(token_record_info, &crate::ID)?;
298
299 (
300 TokenRecord::from_account_info(token_record_info)?,
301 token_record_info,
302 )
303 }
304 None => {
305 return Err(MetadataError::MissingTokenRecord.into());
307 }
308 };
309
310 if token_record.delegate.is_some() {
312 return Err(MetadataError::DelegateAlreadyExists.into());
313 }
314
315 if let Some(ProgrammableConfig::V1 {
318 rule_set: Some(rule_set),
319 }) = metadata.programmable_config
320 {
321 let authorization_rules_info = ctx
323 .accounts
324 .authorization_rules_info
325 .ok_or(MetadataError::MissingAuthorizationRules)?;
326 assert_keys_equal(authorization_rules_info.key, &rule_set)?;
327 assert_owned_by(authorization_rules_info, &mpl_token_auth_rules::ID)?;
328
329 let authorization_rules_program_info = ctx
331 .accounts
332 .authorization_rules_program_info
333 .ok_or(MetadataError::MissingAuthorizationRulesProgram)?;
334 assert_keys_equal(
335 authorization_rules_program_info.key,
336 &mpl_token_auth_rules::ID,
337 )?;
338
339 let auth_rules_validate_params = AuthRulesValidateParams {
340 mint_info: ctx.accounts.mint_info,
341 owner_info: None,
342 authority_info: None,
343 source_info: None,
344 destination_info: Some(ctx.accounts.delegate_info),
345 programmable_config: metadata.programmable_config,
346 amount,
347 auth_data: authorization_data.clone(),
348 auth_rules_info: ctx.accounts.authorization_rules_info,
349 operation: Operation::Delegate {
350 scenario: DelegateScenario::Token(role),
351 },
352 is_wallet_to_wallet: false,
353 rule_set_revision: token_record
354 .rule_set_revision
355 .map(|revision| revision as usize),
356 };
357
358 auth_rules_validate(auth_rules_validate_params)?;
359
360 token_record.rule_set_revision =
362 get_latest_revision(authorization_rules_info)?.map(|revision| revision as u64);
363 }
364
365 token_record.state = if matches!(role, TokenDelegateRole::Sale) {
366 TokenState::Listed
369 } else {
370 TokenState::Unlocked
371 };
372
373 token_record.locked_transfer = if matches!(role, TokenDelegateRole::LockedTransfer) {
374 if let DelegateArgs::LockedTransferV1 { locked_address, .. } = args {
375 Some(*locked_address)
376 } else {
377 return Err(MetadataError::InvalidDelegateArgs.into());
378 }
379 } else {
380 None
381 };
382
383 token_record.delegate = Some(*ctx.accounts.delegate_info.key);
384 token_record.delegate_role = Some(role);
385 token_record.save(
386 token_record_info,
387 ctx.accounts.payer_info,
388 ctx.accounts.system_program_info,
389 )?;
390
391 if let Some(master_edition_info) = ctx.accounts.master_edition_info {
392 assert_owned_by(master_edition_info, &crate::ID)?;
393 thaw(
395 ctx.accounts.mint_info.clone(),
396 token_info.clone(),
397 master_edition_info.clone(),
398 spl_token_program_info.clone(),
399 )?;
400 } else {
401 return Err(MetadataError::MissingEditionAccount.into());
402 }
403 }
404 _ => {
405 if !matches!(role, TokenDelegateRole::Standard) {
406 return Err(MetadataError::InvalidDelegateRole.into());
407 }
408 }
409 }
410
411 invoke(
413 &spl_token::instruction::approve(
414 spl_token_program_info.key,
415 token_info.key,
416 ctx.accounts.delegate_info.key,
417 ctx.accounts.authority_info.key,
418 &[],
419 amount,
420 )?,
421 &[
422 token_info.clone(),
423 ctx.accounts.delegate_info.clone(),
424 ctx.accounts.authority_info.clone(),
425 ],
426 )?;
427
428 if matches!(role, TokenDelegateRole::Utility) {
432 let master_edition_info = ctx
435 .accounts
436 .master_edition_info
437 .ok_or(MetadataError::MissingEditionAccount)?;
438
439 if let COption::Some(close_authority) = token.close_authority {
440 if &close_authority != master_edition_info.key {
441 return Err(MetadataError::InvalidCloseAuthority.into());
442 }
443 } else {
444 invoke(
445 &spl_token::instruction::set_authority(
446 spl_token_program_info.key,
447 token_info.key,
448 Some(master_edition_info.key),
449 SplAuthorityType::CloseAccount,
450 ctx.accounts.authority_info.key,
451 &[],
452 )?,
453 &[
454 token_info.clone(),
455 ctx.accounts.delegate_info.clone(),
456 ctx.accounts.authority_info.clone(),
457 ],
458 )?;
459 }
460 }
461
462 if matches!(
463 metadata.token_standard,
464 Some(TokenStandard::ProgrammableNonFungible)
465 ) {
466 if let Some(master_edition_info) = ctx.accounts.master_edition_info {
467 freeze(
468 ctx.accounts.mint_info.clone(),
469 token_info.clone(),
470 master_edition_info.clone(),
471 spl_token_program_info.clone(),
472 )?;
473 } else {
474 return Err(MetadataError::MissingEditionAccount.into());
477 }
478 }
479
480 Ok(())
481}
482
483fn create_pda_account<'a>(
484 program_id: &Pubkey,
485 delegate_record_info: &'a AccountInfo<'a>,
486 delegate_info: &'a AccountInfo<'a>,
487 mint_info: &'a AccountInfo<'a>,
488 authority_info: &'a AccountInfo<'a>,
489 payer_info: &'a AccountInfo<'a>,
490 system_program_info: &'a AccountInfo<'a>,
491 delegate_role: &str,
492) -> ProgramResult {
493 let mut signer_seeds = vec![
496 PREFIX.as_bytes(),
497 program_id.as_ref(),
498 mint_info.key.as_ref(),
499 delegate_role.as_bytes(),
500 authority_info.key.as_ref(),
501 delegate_info.key.as_ref(),
502 ];
503 let bump = &[assert_derivation(
504 program_id,
505 delegate_record_info,
506 &signer_seeds,
507 )?];
508 signer_seeds.push(bump);
509
510 if !delegate_record_info.data_is_empty() {
511 return Err(MetadataError::DelegateAlreadyExists.into());
512 }
513
514 create_or_allocate_account_raw(
517 *program_id,
518 delegate_record_info,
519 system_program_info,
520 payer_info,
521 MetadataDelegateRecord::size(),
522 &signer_seeds,
523 )?;
524
525 let pda = MetadataDelegateRecord {
526 bump: bump[0],
527 mint: *mint_info.key,
528 delegate: *delegate_info.key,
529 update_authority: *authority_info.key,
530 ..Default::default()
531 };
532 pda.serialize(&mut *delegate_record_info.try_borrow_mut_data()?)?;
533
534 Ok(())
535}