mpl_token_metadata/state/
programmable.rs

1use std::io::Error;
2
3use borsh::{BorshDeserialize, BorshSerialize};
4use mpl_utils::cmp_pubkeys;
5#[cfg(feature = "serde-feature")]
6use serde::{Deserialize, Serialize};
7use shank::ShankAccount;
8use solana_program::{
9    account_info::AccountInfo, instruction::AccountMeta, program_error::ProgramError,
10    program_option::COption, pubkey::Pubkey,
11};
12use spl_token::state::Account;
13
14use super::*;
15use crate::{
16    error::MetadataError,
17    instruction::MetadataDelegateRole,
18    pda::{find_metadata_delegate_record_account, find_token_record_account},
19    processor::{DelegateScenario, TransferScenario, UpdateScenario},
20    utils::assert_owned_by,
21};
22
23/// Empty pubkey constant.
24const DEFAULT_PUBKEY: Pubkey = Pubkey::new_from_array([0u8; 32]);
25
26pub const TOKEN_RECORD_SEED: &str = "token_record";
27
28pub const TOKEN_STATE_INDEX: usize = 2;
29
30pub const LOCKED_TRANSFER_SIZE: usize = 33; // Optional Pubkey
31
32pub const TOKEN_RECORD_SIZE: usize = 1 // Key
33+ 1   // bump
34+ 1   // state
35+ 9   // rule set revision
36+ 33  // delegate
37+ 2   // delegate role
38+ 33; // locked transfer
39
40/// The `TokenRecord` struct represents the state of the token account holding a `pNFT`. Given
41/// that the token account is always frozen, it includes a `state` that provides an abstraction
42/// of frozen (locked) and thaw (unlocked).
43///
44/// It also stores state regarding token delegates that are set on the token account: the pubkey
45/// of the delegate set (this would match the spl-token account delegate) and the role.
46///
47/// Every token account holding a `pNFT` has a token record associated. The seeds for the token
48/// record PDA are:
49/// 1. `"metadata"`
50/// 2. program id
51/// 3. mint id
52/// 4. `"token_record"`
53/// 5. token account id
54#[repr(C)]
55#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
56#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone, ShankAccount)]
57pub struct TokenRecord {
58    /// Account key.
59    pub key: Key,
60    /// Derivation bump.
61    pub bump: u8,
62    /// Represented the token state.
63    pub state: TokenState,
64    /// Stores the rule set revision (if any). The revision is updated every time
65    /// a new token delegate is approved.
66    pub rule_set_revision: Option<u64>,
67    /// Pubkey of the current token delegate. This delegate key will match the spl-token
68    /// delegate pubkey.
69    #[cfg_attr(
70        feature = "serde-feature",
71        serde(
72            deserialize_with = "deser_option_pubkey",
73            serialize_with = "ser_option_pubkey"
74        )
75    )]
76    pub delegate: Option<Pubkey>,
77    /// The role of the current token delegate.
78    pub delegate_role: Option<TokenDelegateRole>,
79    /// Stores the destination pubkey when a transfer is lock to an allowed address. This
80    /// pubkey gets set when a 'LockTransfer' delegate is approved.
81    pub locked_transfer: Option<Pubkey>,
82}
83
84impl Default for TokenRecord {
85    fn default() -> Self {
86        Self {
87            key: Key::TokenRecord,
88            bump: 255,
89            state: TokenState::Unlocked,
90            rule_set_revision: None,
91            delegate: None,
92            delegate_role: None,
93            locked_transfer: None,
94        }
95    }
96}
97
98impl TokenMetadataAccount for TokenRecord {
99    fn key() -> Key {
100        Key::TokenRecord
101    }
102
103    fn size() -> usize {
104        TOKEN_RECORD_SIZE
105    }
106
107    fn safe_deserialize(data: &[u8]) -> Result<Self, BorshError> {
108        Self::from_bytes(data).map_err(|e| Error::new(ErrorKind::Other, e.to_string()))
109    }
110
111    fn from_account_info(account_info: &AccountInfo) -> Result<Self, ProgramError> {
112        let data = &account_info.try_borrow_data()?;
113        Self::from_bytes(data)
114    }
115}
116
117impl TokenRecord {
118    pub fn is_locked(&self) -> bool {
119        matches!(self.state, TokenState::Locked)
120    }
121
122    /// Resets the token state by clearing any state stored.
123    pub fn reset(&mut self) {
124        self.state = TokenState::Unlocked;
125        self.rule_set_revision = None;
126        self.delegate = None;
127        self.delegate_role = None;
128        self.locked_transfer = None;
129    }
130}
131
132impl Resizable for TokenRecord {
133    fn from_bytes<'a>(account_data: &[u8]) -> Result<TokenRecord, ProgramError> {
134        // we perform a manual deserialization since we are potentially dealing
135        // with accounts of different sizes
136        let length = TokenRecord::size() as i64 - account_data.len() as i64;
137
138        // we use the account length in the 'is_correct_account_type' since we are
139        // manually checking that the account length is valid
140        if !(length == 0 || length == LOCKED_TRANSFER_SIZE as i64)
141            || !TokenRecord::is_correct_account_type(
142                account_data,
143                Key::TokenRecord,
144                account_data.len(),
145            )
146        {
147            return Err(MetadataError::DataTypeMismatch.into());
148        }
149        // mutable "pointer" to the account data
150        let mut data = account_data;
151
152        let key: Key = BorshDeserialize::deserialize(&mut data)?;
153        let bump: u8 = BorshDeserialize::deserialize(&mut data)?;
154        let state: TokenState = BorshDeserialize::deserialize(&mut data)?;
155        let rule_set_revision: Option<u64> = BorshDeserialize::deserialize(&mut data)?;
156        let delegate: Option<Pubkey> = BorshDeserialize::deserialize(&mut data)?;
157        let delegate_role: Option<TokenDelegateRole> = BorshDeserialize::deserialize(&mut data)?;
158
159        let locked_transfer: Option<Pubkey> = if length == 0 {
160            BorshDeserialize::deserialize(&mut data)?
161        } else {
162            None
163        };
164
165        Ok(TokenRecord {
166            key,
167            bump,
168            state,
169            rule_set_revision,
170            delegate,
171            delegate_role,
172            locked_transfer,
173        })
174    }
175}
176
177/// Programmable account state.
178#[repr(C)]
179#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
180#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
181pub enum TokenState {
182    /// Token account is unlocked; operations are allowed on this account.
183    Unlocked,
184    /// Token account has been locked; no operations are allowed on this account.
185    Locked,
186    /// Token account has a `Sale` delegate set; operations are restricted.
187    Listed,
188}
189
190#[repr(C)]
191#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
192#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone, Copy)]
193pub enum TokenDelegateRole {
194    Sale,
195    Transfer,
196    Utility,
197    Staking,
198    Standard,
199    LockedTransfer,
200    Migration = 255,
201}
202
203pub struct AuthorityRequest<'a, 'b> {
204    /// Determines the precedence of authority types.
205    pub precedence: &'a [AuthorityType],
206    /// Pubkey of the authority.
207    pub authority: &'a Pubkey,
208    /// Metadata's update authority pubkey of the asset.
209    pub update_authority: &'b Pubkey,
210    /// Mint address.
211    pub mint: &'b Pubkey,
212    /// Collection mint address.
213    pub collection_mint: Option<&'b Pubkey>,
214    /// Holder's token account info.
215    pub token: Option<&'a Pubkey>,
216    /// Holder's token account.
217    pub token_account: Option<&'b Account>,
218    /// `MetadataDelegateRecord` account of the authority (when the authority is a delegate).
219    pub metadata_delegate_record_info: Option<&'a AccountInfo<'a>>,
220    /// Expected `MetadataDelegateRole` for the request.
221    pub metadata_delegate_roles: Vec<MetadataDelegateRole>,
222    /// Expected collection-level `MetadataDelegateRole` for the request.
223    pub collection_metadata_delegate_roles: Vec<MetadataDelegateRole>,
224    /// `TokenRecord` account.
225    pub token_record_info: Option<&'a AccountInfo<'a>>,
226    /// Expected `TokenDelegateRole` for the request.
227    pub token_delegate_roles: Vec<TokenDelegateRole>,
228}
229
230impl<'a, 'b> Default for AuthorityRequest<'a, 'b> {
231    fn default() -> Self {
232        Self {
233            precedence: &[
234                AuthorityType::TokenDelegate,
235                AuthorityType::Holder,
236                AuthorityType::MetadataDelegate,
237                AuthorityType::Metadata,
238            ],
239            authority: &DEFAULT_PUBKEY,
240            update_authority: &DEFAULT_PUBKEY,
241            mint: &DEFAULT_PUBKEY,
242            collection_mint: None,
243            token: None,
244            token_account: None,
245            metadata_delegate_record_info: None,
246            metadata_delegate_roles: Vec::with_capacity(0),
247            collection_metadata_delegate_roles: Vec::with_capacity(0),
248            token_record_info: None,
249            token_delegate_roles: Vec::with_capacity(0),
250        }
251    }
252}
253
254/// Struct to represent the authority type identified from
255/// an authority request.
256#[derive(Default)]
257pub struct AuthorityResponse {
258    pub authority_type: AuthorityType,
259    pub token_delegate_role: Option<TokenDelegateRole>,
260    pub metadata_delegate_role: Option<MetadataDelegateRole>,
261}
262
263#[repr(C)]
264#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
265#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone, Default)]
266pub enum AuthorityType {
267    #[default]
268    None,
269    Metadata,
270    Holder,
271    MetadataDelegate,
272    TokenDelegate,
273}
274
275impl AuthorityType {
276    /// Determines the `AuthorityType`.
277    ///
278    /// The `AuthorityType` is used to determine the authority of a request. An authority can
279    /// be "valid" for multiples types (e.g., the same authority can be the holder and the update
280    /// authority). This ambiguity is resolved by using the `precedence`, which determines the
281    /// priority of types.
282    pub fn get_authority_type(
283        request: AuthorityRequest,
284    ) -> Result<AuthorityResponse, ProgramError> {
285        // the evaluation follows the `request.precedence` order; as soon as a match is
286        // found, the type is returned
287        for authority_type in request.precedence {
288            match authority_type {
289                AuthorityType::TokenDelegate => {
290                    // checks if the authority is a token delegate
291
292                    if let Some(token_record_info) = request.token_record_info {
293                        // must be owned by token medatata
294                        assert_owned_by(token_record_info, &crate::ID)?;
295
296                        // we can only validate if it is a token delegate when we have the token account
297                        if let Some(token_account) = request.token_account {
298                            let token = request.token.ok_or(MetadataError::MissingTokenAccount)?;
299
300                            let (pda_key, _) = find_token_record_account(request.mint, token);
301                            let token_record = TokenRecord::from_account_info(token_record_info)?;
302
303                            let role_matches = match token_record.delegate_role {
304                                Some(role) => request.token_delegate_roles.contains(&role),
305                                None => request.token_delegate_roles.is_empty(),
306                            };
307
308                            if cmp_pubkeys(&pda_key, token_record_info.key)
309                                && Some(*request.authority) == token_record.delegate
310                                && role_matches
311                                && (COption::from(token_record.delegate) == token_account.delegate)
312                            {
313                                return Ok(AuthorityResponse {
314                                    authority_type: AuthorityType::TokenDelegate,
315                                    token_delegate_role: token_record.delegate_role,
316                                    ..Default::default()
317                                });
318                            }
319                        }
320                    }
321                }
322                AuthorityType::MetadataDelegate => {
323                    // checks if the authority is a metadata delegate
324
325                    if let Some(metadata_delegate_record_info) =
326                        request.metadata_delegate_record_info
327                    {
328                        // must be owned by token medatata
329                        assert_owned_by(metadata_delegate_record_info, &crate::ID)?;
330
331                        for role in &request.metadata_delegate_roles {
332                            // looking up the delegate on the metadata mint
333                            let (pda_key, _) = find_metadata_delegate_record_account(
334                                request.mint,
335                                *role,
336                                request.update_authority,
337                                request.authority,
338                            );
339
340                            if cmp_pubkeys(&pda_key, metadata_delegate_record_info.key) {
341                                let delegate_record = MetadataDelegateRecord::from_account_info(
342                                    metadata_delegate_record_info,
343                                )?;
344
345                                if delegate_record.delegate == *request.authority {
346                                    return Ok(AuthorityResponse {
347                                        authority_type: AuthorityType::MetadataDelegate,
348                                        metadata_delegate_role: Some(*role),
349                                        ..Default::default()
350                                    });
351                                }
352                            }
353                        }
354
355                        // looking up the delegate on the collection mint (this is for
356                        // collection-level delegates)
357                        if let Some(collection_mint) = request.collection_mint {
358                            for role in &request.collection_metadata_delegate_roles {
359                                let (pda_key, _) = find_metadata_delegate_record_account(
360                                    collection_mint,
361                                    *role,
362                                    request.update_authority,
363                                    request.authority,
364                                );
365
366                                if cmp_pubkeys(&pda_key, metadata_delegate_record_info.key) {
367                                    let delegate_record =
368                                        MetadataDelegateRecord::from_account_info(
369                                            metadata_delegate_record_info,
370                                        )?;
371
372                                    if delegate_record.delegate == *request.authority {
373                                        return Ok(AuthorityResponse {
374                                            authority_type: AuthorityType::MetadataDelegate,
375                                            metadata_delegate_role: Some(*role),
376                                            ..Default::default()
377                                        });
378                                    }
379                                }
380                            }
381                        }
382                    }
383                }
384                AuthorityType::Holder => {
385                    // checks if the authority is the token owner
386
387                    if let Some(token_account) = request.token_account {
388                        if cmp_pubkeys(&token_account.owner, request.authority) {
389                            return Ok(AuthorityResponse {
390                                authority_type: AuthorityType::Holder,
391                                ..Default::default()
392                            });
393                        }
394                    }
395                }
396                AuthorityType::Metadata => {
397                    // checks if the authority is the update authority
398
399                    if cmp_pubkeys(request.update_authority, request.authority) {
400                        return Ok(AuthorityResponse {
401                            authority_type: AuthorityType::Metadata,
402                            ..Default::default()
403                        });
404                    }
405                }
406                _ => { /* the default return type is 'None' */ }
407            }
408        }
409
410        // if we reach this point, no 'valid' authority type has been found
411        Ok(AuthorityResponse::default())
412    }
413}
414
415#[derive(Clone, Debug, PartialEq, Eq)]
416pub enum Operation {
417    Transfer { scenario: TransferScenario },
418    Update { scenario: UpdateScenario },
419    Delegate { scenario: DelegateScenario },
420}
421
422impl ToString for Operation {
423    fn to_string(&self) -> String {
424        match self {
425            Self::Transfer { scenario } => format!("Transfer:{}", scenario),
426            Self::Update { scenario } => format!("Update:{}", scenario),
427            Self::Delegate { scenario } => format!("Delegate:{}", scenario),
428        }
429    }
430}
431
432#[repr(C)]
433#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
434#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
435pub enum PayloadKey {
436    Amount,
437    Authority,
438    AuthoritySeeds,
439    Delegate,
440    DelegateSeeds,
441    Destination,
442    DestinationSeeds,
443    Holder,
444    Source,
445    SourceSeeds,
446}
447
448impl ToString for PayloadKey {
449    fn to_string(&self) -> String {
450        match self {
451            PayloadKey::Amount => "Amount",
452            PayloadKey::Authority => "Authority",
453            PayloadKey::AuthoritySeeds => "AuthoritySeeds",
454            PayloadKey::Delegate => "Delegate",
455            PayloadKey::DelegateSeeds => "DelegateSeeds",
456            PayloadKey::Destination => "Destination",
457            PayloadKey::DestinationSeeds => "DestinationSeeds",
458            PayloadKey::Holder => "Holder",
459            PayloadKey::Source => "Source",
460            PayloadKey::SourceSeeds => "SourceSeeds",
461        }
462        .to_string()
463    }
464}
465
466pub trait ToAccountMeta {
467    fn to_account_meta(&self) -> AccountMeta;
468}
469
470impl<'info> ToAccountMeta for AccountInfo<'info> {
471    fn to_account_meta(&self) -> AccountMeta {
472        AccountMeta {
473            pubkey: *self.key,
474            is_signer: self.is_signer,
475            is_writable: self.is_writable,
476        }
477    }
478}