mpl_token_metadata/processor/delegate/
delegate.rs

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
63/// Delegates an action over an asset to a specific account.
64pub 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    // checks if it is a TokenDelegate creation
72    let delegate_args = match &args {
73        // Sale
74        DelegateArgs::SaleV1 {
75            amount,
76            authorization_data,
77        } => Some((TokenDelegateRole::Sale, amount, authorization_data)),
78        // Transfer
79        DelegateArgs::TransferV1 {
80            amount,
81            authorization_data,
82        } => Some((TokenDelegateRole::Transfer, amount, authorization_data)),
83        // Utility
84        DelegateArgs::UtilityV1 {
85            amount,
86            authorization_data,
87        } => Some((TokenDelegateRole::Utility, amount, authorization_data)),
88        // Staking
89        DelegateArgs::StakingV1 {
90            amount,
91            authorization_data,
92        } => Some((TokenDelegateRole::Staking, amount, authorization_data)),
93        // Standard
94        DelegateArgs::StandardV1 { amount } => Some((TokenDelegateRole::Standard, amount, &None)),
95        // LockedTransfer
96        DelegateArgs::LockedTransferV1 {
97            amount,
98            authorization_data,
99            ..
100        } => Some((
101            TokenDelegateRole::LockedTransfer,
102            amount,
103            authorization_data,
104        )),
105
106        // we don't need to fail if did not find a match at this point
107        _ => None,
108    };
109
110    if let Some((role, amount, authorization_data)) = delegate_args {
111        // proceed with the delegate creation if we have a match
112        return create_persistent_delegate_v1(
113            program_id,
114            context,
115            &args,
116            role,
117            *amount,
118            authorization_data,
119        );
120    }
121
122    // checks if it is a MetadataDelegate creation
123    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        // we don't need to fail if did not find a match at this point
148        _ => 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    // this only happens if we did not find a match
156    Err(MetadataError::InvalidDelegateArgs.into())
157}
158
159/// Creates a `DelegateRole::Collection` delegate.
160///
161/// There can be multiple collections delegates set at any time.
162fn create_delegate_v1(
163    program_id: &Pubkey,
164    ctx: Context<Delegate>,
165    _args: DelegateArgs,
166    role: MetadataDelegateRole,
167) -> ProgramResult {
168    // signers
169
170    assert_signer(ctx.accounts.payer_info)?;
171    assert_signer(ctx.accounts.authority_info)?;
172
173    // ownership
174
175    assert_owned_by(ctx.accounts.metadata_info, program_id)?;
176    assert_owned_by(ctx.accounts.mint_info, &spl_token::ID)?;
177
178    // key match
179
180    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    // account relationships
187
188    let metadata = Metadata::from_account_info(ctx.accounts.metadata_info)?;
189    // authority must match update authority
190    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    // process the delegation creation (the derivation is checked
204    // by the create helper)
205
206    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
220/// Creates a presistent delegate. For non-programmable assets, this is just a wrapper over
221/// spl-token 'approve' delegate.
222///
223/// Note that `DelegateRole::Sale` is only available for programmable assets.
224fn 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    // retrieving required optional accounts
233
234    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    // signers
249
250    assert_signer(ctx.accounts.payer_info)?;
251    assert_signer(ctx.accounts.authority_info)?;
252
253    // ownership
254
255    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    // key match
260
261    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    // account relationships
269
270    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    // authority must be the owner of the token account: spl-token required the
276    // token owner to set a delegate
277    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    // process the delegation
283
284    // programmables assets can have delegates from any role apart from `Standard`
285    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                    // token record is required for programmable assets
306                    return Err(MetadataError::MissingTokenRecord.into());
307                }
308            };
309
310            // we cannot replace an existing delegate, it must be revoked first
311            if token_record.delegate.is_some() {
312                return Err(MetadataError::DelegateAlreadyExists.into());
313            }
314
315            // if we have a rule set, we need to store its revision; at this point,
316            // we will validate that we have the correct auth rules PDA
317            if let Some(ProgrammableConfig::V1 {
318                rule_set: Some(rule_set),
319            }) = metadata.programmable_config
320            {
321                // validates that we got the correct rule set
322                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                // validates auth rules program
330                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                // stores the latest rule set revision
361                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                // when a 'Sale' delegate is set, the token state is 'Listed'
367                // to restrict holder transfers
368                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                // derivation is checked on the thaw function
394                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    // creates the spl-token delegate
412    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    // For Utility Delegates we request Close Authority as well so that the
429    // token can be closed by the delegate on Burn. We assign CloseAuthority to
430    // the master edition PDA so we can close it on Transfer and revoke it in Revoke.
431    if matches!(role, TokenDelegateRole::Utility) {
432        // If there's an existing close authority that is not the metadata account,
433        // it will need to be revoked by the original UtilityDelegate.
434        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            // sanity check: this should not happen at this point since the master
475            // edition account is validated before the delegation
476            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    // validates the delegate derivation
494
495    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    // allocate the delegate account
515
516    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}