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
23const 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; pub const TOKEN_RECORD_SIZE: usize = 1 + 1 + 1 + 9 + 33 + 2 + 33; #[repr(C)]
55#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
56#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone, ShankAccount)]
57pub struct TokenRecord {
58 pub key: Key,
60 pub bump: u8,
62 pub state: TokenState,
64 pub rule_set_revision: Option<u64>,
67 #[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 pub delegate_role: Option<TokenDelegateRole>,
79 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 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 let length = TokenRecord::size() as i64 - account_data.len() as i64;
137
138 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 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#[repr(C)]
179#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
180#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
181pub enum TokenState {
182 Unlocked,
184 Locked,
186 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 pub precedence: &'a [AuthorityType],
206 pub authority: &'a Pubkey,
208 pub update_authority: &'b Pubkey,
210 pub mint: &'b Pubkey,
212 pub collection_mint: Option<&'b Pubkey>,
214 pub token: Option<&'a Pubkey>,
216 pub token_account: Option<&'b Account>,
218 pub metadata_delegate_record_info: Option<&'a AccountInfo<'a>>,
220 pub metadata_delegate_roles: Vec<MetadataDelegateRole>,
222 pub collection_metadata_delegate_roles: Vec<MetadataDelegateRole>,
224 pub token_record_info: Option<&'a AccountInfo<'a>>,
226 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#[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 pub fn get_authority_type(
283 request: AuthorityRequest,
284 ) -> Result<AuthorityResponse, ProgramError> {
285 for authority_type in request.precedence {
288 match authority_type {
289 AuthorityType::TokenDelegate => {
290 if let Some(token_record_info) = request.token_record_info {
293 assert_owned_by(token_record_info, &crate::ID)?;
295
296 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 if let Some(metadata_delegate_record_info) =
326 request.metadata_delegate_record_info
327 {
328 assert_owned_by(metadata_delegate_record_info, &crate::ID)?;
330
331 for role in &request.metadata_delegate_roles {
332 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 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 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 if cmp_pubkeys(request.update_authority, request.authority) {
400 return Ok(AuthorityResponse {
401 authority_type: AuthorityType::Metadata,
402 ..Default::default()
403 });
404 }
405 }
406 _ => { }
407 }
408 }
409
410 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}