use std::io::ErrorKind;
use crate::{
deser::meta_deser_unchecked,
error::MetadataError,
utils::{assert_owned_by, is_correct_account_type, try_from_slice_checked},
ID,
};
use borsh::{maybestd::io::Error as BorshError, BorshDeserialize, BorshSerialize};
use shank::ShankAccount;
use solana_program::{
account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError,
pubkey::Pubkey,
};
#[cfg(feature = "serde-feature")]
use {
serde::{Deserialize, Serialize},
serde_with::{As, DisplayFromStr},
};
pub const PREFIX: &str = "metadata";
pub const EDITION: &str = "edition";
pub const RESERVATION: &str = "reservation";
pub const USER: &str = "user";
pub const BURN: &str = "burn";
pub const COLLECTION_AUTHORITY: &str = "collection_authority";
pub const MAX_NAME_LENGTH: usize = 32;
pub const MAX_SYMBOL_LENGTH: usize = 10;
pub const MAX_URI_LENGTH: usize = 200;
pub const MAX_METADATA_LEN: usize = 1 + 32 + 32 + MAX_DATA_SIZE
+ 1 + 1 + 9 + 2 + 34 + 18 + 118;
pub const MAX_DATA_SIZE: usize = 4
+ MAX_NAME_LENGTH
+ 4
+ MAX_SYMBOL_LENGTH
+ 4
+ MAX_URI_LENGTH
+ 2
+ 1
+ 4
+ MAX_CREATOR_LIMIT * MAX_CREATOR_LEN;
pub const MAX_EDITION_LEN: usize = 1 + 32 + 8 + 200;
pub const MAX_MASTER_EDITION_LEN: usize = 1 + 9 + 8 + 264;
pub const MAX_CREATOR_LIMIT: usize = 5;
pub const MAX_CREATOR_LEN: usize = 32 + 1 + 1;
pub const MAX_RESERVATIONS: usize = 200;
pub const MAX_RESERVATION_LIST_V1_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 34 + 100;
pub const MAX_RESERVATION_LIST_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 48 + 8 + 8 + 84;
pub const MAX_EDITION_MARKER_SIZE: usize = 32;
pub const EDITION_MARKER_BIT_SIZE: u64 = 248;
pub const USE_AUTHORITY_RECORD_SIZE: usize = 18;
pub const COLLECTION_AUTHORITY_RECORD_SIZE: usize = 11;
pub trait TokenMetadataAccount {
fn key() -> Key;
fn size() -> usize;
fn pad_length(buf: &mut Vec<u8>) -> Result<(), MetadataError> {
let padding_length = Self::size()
.checked_sub(buf.len())
.ok_or(MetadataError::NumericalOverflowError)?;
buf.extend(vec![0; padding_length]);
Ok(())
}
fn safe_deserialize<T: BorshDeserialize>(mut data: &[u8]) -> Result<T, BorshError> {
if !is_correct_account_type(data, Self::key(), Self::size()) {
return Err(BorshError::new(ErrorKind::Other, "DataTypeMismatch"));
}
let result: T = T::deserialize(&mut data)?;
Ok(result)
}
fn from_account_info<T: BorshDeserialize>(a: &AccountInfo) -> Result<T, ProgramError>
where {
let ua: T = Self::safe_deserialize(&a.data.borrow_mut())
.map_err(|_| MetadataError::DataTypeMismatch)?;
assert_owned_by(a, &ID)?;
Ok(ua)
}
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone, Copy)]
pub enum Key {
Uninitialized,
EditionV1,
MasterEditionV1,
ReservationListV1,
MetadataV1,
ReservationListV2,
MasterEditionV2,
EditionMarker,
UseAuthorityRecord,
CollectionAuthorityRecord,
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, Default, PartialEq, Debug, Clone)]
pub struct Data {
pub name: String,
pub symbol: String,
pub uri: String,
pub seller_fee_basis_points: u16,
pub creators: Option<Vec<Creator>>,
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
pub struct DataV2 {
pub name: String,
pub symbol: String,
pub uri: String,
pub seller_fee_basis_points: u16,
pub creators: Option<Vec<Creator>>,
pub collection: Option<Collection>,
pub uses: Option<Uses>,
}
impl DataV2 {
pub fn to_v1(&self) -> Data {
let ns = self.clone();
Data {
name: ns.name,
symbol: ns.symbol,
uri: ns.uri,
seller_fee_basis_points: ns.seller_fee_basis_points,
creators: ns.creators,
}
}
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
pub enum UseMethod {
Burn,
Multiple,
Single,
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
pub enum CollectionDetails {
V1 { size: u64 },
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
pub struct Uses {
pub use_method: UseMethod, pub remaining: u64, pub total: u64, }
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
pub enum TokenStandard {
NonFungible, FungibleAsset, Fungible, NonFungibleEdition, }
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone, ShankAccount)]
pub struct UseAuthorityRecord {
pub key: Key, pub allowed_uses: u64, pub bump: u8,
}
impl Default for UseAuthorityRecord {
fn default() -> Self {
UseAuthorityRecord {
key: Key::UseAuthorityRecord,
allowed_uses: 0,
bump: 255,
}
}
}
impl TokenMetadataAccount for UseAuthorityRecord {
fn key() -> Key {
Key::UseAuthorityRecord
}
fn size() -> usize {
USE_AUTHORITY_RECORD_SIZE
}
}
impl UseAuthorityRecord {
pub fn from_bytes(b: &[u8]) -> Result<UseAuthorityRecord, ProgramError> {
let ua: UseAuthorityRecord =
try_from_slice_checked(b, Key::UseAuthorityRecord, USE_AUTHORITY_RECORD_SIZE)?;
Ok(ua)
}
pub fn bump_empty(&self) -> bool {
self.bump == 0 && self.key == Key::UseAuthorityRecord
}
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone, ShankAccount)]
pub struct CollectionAuthorityRecord {
pub key: Key, pub bump: u8, }
impl Default for CollectionAuthorityRecord {
fn default() -> Self {
CollectionAuthorityRecord {
key: Key::CollectionAuthorityRecord,
bump: 255,
}
}
}
impl TokenMetadataAccount for CollectionAuthorityRecord {
fn key() -> Key {
Key::CollectionAuthorityRecord
}
fn size() -> usize {
COLLECTION_AUTHORITY_RECORD_SIZE
}
}
impl CollectionAuthorityRecord {
pub fn from_bytes(b: &[u8]) -> Result<CollectionAuthorityRecord, ProgramError> {
let ca: CollectionAuthorityRecord = try_from_slice_checked(
b,
Key::CollectionAuthorityRecord,
COLLECTION_AUTHORITY_RECORD_SIZE,
)?;
Ok(ca)
}
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
pub struct Collection {
pub verified: bool,
pub key: Pubkey,
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(Clone, BorshSerialize, Debug, PartialEq, ShankAccount)]
pub struct Metadata {
pub key: Key,
pub update_authority: Pubkey,
pub mint: Pubkey,
pub data: Data,
pub primary_sale_happened: bool,
pub is_mutable: bool,
pub edition_nonce: Option<u8>,
pub token_standard: Option<TokenStandard>,
pub collection: Option<Collection>,
pub uses: Option<Uses>,
pub collection_details: Option<CollectionDetails>,
}
impl Default for Metadata {
fn default() -> Self {
Metadata {
key: Key::MetadataV1,
update_authority: Pubkey::default(),
mint: Pubkey::default(),
data: Data::default(),
primary_sale_happened: false,
is_mutable: false,
edition_nonce: None,
token_standard: None,
collection: None,
uses: None,
collection_details: None,
}
}
}
impl TokenMetadataAccount for Metadata {
fn key() -> Key {
Key::MetadataV1
}
fn size() -> usize {
MAX_METADATA_LEN
}
}
impl borsh::de::BorshDeserialize for Metadata {
fn deserialize(buf: &mut &[u8]) -> ::core::result::Result<Self, BorshError> {
let md = meta_deser_unchecked(buf)?;
Ok(md)
}
}
pub trait MasterEdition {
fn key(&self) -> Key;
fn supply(&self) -> u64;
fn set_supply(&mut self, supply: u64);
fn max_supply(&self) -> Option<u64>;
fn save(&self, account: &AccountInfo) -> ProgramResult;
}
pub fn get_master_edition(account: &AccountInfo) -> Result<Box<dyn MasterEdition>, ProgramError> {
let version = account.data.borrow()[0];
let master_edition_result: Result<Box<dyn MasterEdition>, ProgramError> = match version {
2 => {
let me: MasterEditionV1 = MasterEditionV1::from_account_info(account)?;
Ok(Box::new(me))
}
6 => {
let me: MasterEditionV2 = MasterEditionV2::from_account_info(account)?;
Ok(Box::new(me))
}
_ => Err(MetadataError::DataTypeMismatch.into()),
};
master_edition_result
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, ShankAccount)]
pub struct MasterEditionV2 {
pub key: Key,
pub supply: u64,
pub max_supply: Option<u64>,
}
impl Default for MasterEditionV2 {
fn default() -> Self {
MasterEditionV2 {
key: Key::MasterEditionV2,
supply: 0,
max_supply: Some(0),
}
}
}
impl TokenMetadataAccount for MasterEditionV2 {
fn key() -> Key {
Key::MasterEditionV2
}
fn size() -> usize {
MAX_MASTER_EDITION_LEN
}
}
impl MasterEdition for MasterEditionV2 {
fn key(&self) -> Key {
self.key
}
fn supply(&self) -> u64 {
self.supply
}
fn set_supply(&mut self, supply: u64) {
self.supply = supply;
}
fn max_supply(&self) -> Option<u64> {
self.max_supply
}
fn save(&self, account: &AccountInfo) -> ProgramResult {
BorshSerialize::serialize(self, &mut *account.data.borrow_mut())?;
Ok(())
}
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, ShankAccount)]
pub struct MasterEditionV1 {
pub key: Key,
pub supply: u64,
pub max_supply: Option<u64>,
pub printing_mint: Pubkey,
pub one_time_printing_authorization_mint: Pubkey,
}
impl TokenMetadataAccount for MasterEditionV1 {
fn key() -> Key {
Key::MasterEditionV1
}
fn size() -> usize {
MAX_MASTER_EDITION_LEN
}
}
impl MasterEdition for MasterEditionV1 {
fn key(&self) -> Key {
self.key
}
fn supply(&self) -> u64 {
self.supply
}
fn max_supply(&self) -> Option<u64> {
self.max_supply
}
fn set_supply(&mut self, supply: u64) {
self.supply = supply;
}
fn save(&self, account: &AccountInfo) -> ProgramResult {
BorshSerialize::serialize(self, &mut *account.data.borrow_mut())?;
Ok(())
}
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, ShankAccount)]
pub struct Edition {
pub key: Key,
pub parent: Pubkey,
pub edition: u64,
}
impl Default for Edition {
fn default() -> Self {
Edition {
key: Key::EditionV1,
parent: Pubkey::default(),
edition: 0,
}
}
}
impl TokenMetadataAccount for Edition {
fn key() -> Key {
Key::EditionV1
}
fn size() -> usize {
MAX_EDITION_LEN
}
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
pub struct Creator {
#[cfg_attr(feature = "serde-feature", serde(with = "As::<DisplayFromStr>"))]
pub address: Pubkey,
pub verified: bool,
pub share: u8,
}
pub trait ReservationList {
fn master_edition(&self) -> Pubkey;
fn supply_snapshot(&self) -> Option<u64>;
fn reservations(&self) -> Vec<Reservation>;
fn total_reservation_spots(&self) -> u64;
fn current_reservation_spots(&self) -> u64;
fn set_master_edition(&mut self, key: Pubkey);
fn set_supply_snapshot(&mut self, supply: Option<u64>);
fn set_reservations(&mut self, reservations: Vec<Reservation>) -> ProgramResult;
fn add_reservation(
&mut self,
reservation: Reservation,
offset: u64,
total_spot_offset: u64,
) -> ProgramResult;
fn set_total_reservation_spots(&mut self, total_reservation_spots: u64);
fn set_current_reservation_spots(&mut self, current_reservation_spots: u64);
fn save(&self, account: &AccountInfo) -> ProgramResult;
}
pub fn get_reservation_list(
account: &AccountInfo,
) -> Result<Box<dyn ReservationList>, ProgramError> {
let version = account.data.borrow()[0];
let reservation_list_result: Result<Box<dyn ReservationList>, ProgramError> = match version {
3 => {
let reservation_list = Box::new(ReservationListV1::from_account_info::<
ReservationListV1,
>(account)?);
Ok(reservation_list)
}
5 => {
let reservation_list = Box::new(ReservationListV2::from_account_info::<
ReservationListV2,
>(account)?);
Ok(reservation_list)
}
_ => Err(MetadataError::DataTypeMismatch.into()),
};
reservation_list_result
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone, ShankAccount)]
pub struct ReservationListV2 {
pub key: Key,
pub master_edition: Pubkey,
pub supply_snapshot: Option<u64>,
pub reservations: Vec<Reservation>,
pub total_reservation_spots: u64,
pub current_reservation_spots: u64,
}
impl TokenMetadataAccount for ReservationListV2 {
fn key() -> Key {
Key::ReservationListV2
}
fn size() -> usize {
MAX_RESERVATION_LIST_SIZE
}
}
impl ReservationList for ReservationListV2 {
fn master_edition(&self) -> Pubkey {
self.master_edition
}
fn supply_snapshot(&self) -> Option<u64> {
self.supply_snapshot
}
fn reservations(&self) -> Vec<Reservation> {
self.reservations.clone()
}
fn set_master_edition(&mut self, key: Pubkey) {
self.master_edition = key
}
fn set_supply_snapshot(&mut self, supply: Option<u64>) {
self.supply_snapshot = supply;
}
fn add_reservation(
&mut self,
reservation: Reservation,
offset: u64,
total_spot_offset: u64,
) -> ProgramResult {
let usize_offset = offset as usize;
while self.reservations.len() < usize_offset {
self.reservations.push(Reservation {
address: solana_program::system_program::id(),
spots_remaining: 0,
total_spots: 0,
})
}
if self.reservations.len() > usize_offset {
let replaced_addr = self.reservations[usize_offset].address;
let replaced_spots = self.reservations[usize_offset].total_spots;
if replaced_addr == reservation.address {
self.set_current_reservation_spots(
self.current_reservation_spots()
.checked_sub(replaced_spots)
.ok_or(MetadataError::NumericalOverflowError)?,
);
} else if replaced_addr != solana_program::system_program::id() {
return Err(MetadataError::TriedToReplaceAnExistingReservation.into());
}
self.reservations[usize_offset] = reservation;
} else {
self.reservations.push(reservation)
}
if usize_offset != 0
&& self.reservations[usize_offset - 1].address == solana_program::system_program::id()
{
self.reservations[usize_offset - 1].spots_remaining = total_spot_offset;
self.reservations[usize_offset - 1].total_spots = total_spot_offset;
}
Ok(())
}
fn set_reservations(&mut self, reservations: Vec<Reservation>) -> ProgramResult {
self.reservations = reservations;
Ok(())
}
fn save(&self, account: &AccountInfo) -> ProgramResult {
BorshSerialize::serialize(self, &mut *account.data.borrow_mut())?;
Ok(())
}
fn total_reservation_spots(&self) -> u64 {
self.total_reservation_spots
}
fn set_total_reservation_spots(&mut self, total_reservation_spots: u64) {
self.total_reservation_spots = total_reservation_spots;
}
fn current_reservation_spots(&self) -> u64 {
self.current_reservation_spots
}
fn set_current_reservation_spots(&mut self, current_reservation_spots: u64) {
self.current_reservation_spots = current_reservation_spots;
}
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
pub struct Reservation {
pub address: Pubkey,
pub spots_remaining: u64,
pub total_spots: u64,
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone, ShankAccount)]
pub struct ReservationListV1 {
pub key: Key,
pub master_edition: Pubkey,
pub supply_snapshot: Option<u64>,
pub reservations: Vec<ReservationV1>,
}
impl TokenMetadataAccount for ReservationListV1 {
fn key() -> Key {
Key::ReservationListV1
}
fn size() -> usize {
MAX_RESERVATION_LIST_V1_SIZE
}
}
impl ReservationList for ReservationListV1 {
fn master_edition(&self) -> Pubkey {
self.master_edition
}
fn supply_snapshot(&self) -> Option<u64> {
self.supply_snapshot
}
fn reservations(&self) -> Vec<Reservation> {
self.reservations
.iter()
.map(|r| Reservation {
address: r.address,
spots_remaining: r.spots_remaining as u64,
total_spots: r.total_spots as u64,
})
.collect()
}
fn set_master_edition(&mut self, key: Pubkey) {
self.master_edition = key
}
fn set_supply_snapshot(&mut self, supply: Option<u64>) {
self.supply_snapshot = supply;
}
fn add_reservation(&mut self, reservation: Reservation, _: u64, _: u64) -> ProgramResult {
self.reservations = vec![ReservationV1 {
address: reservation.address,
spots_remaining: reservation.spots_remaining as u8,
total_spots: reservation.total_spots as u8,
}];
Ok(())
}
fn set_reservations(&mut self, reservations: Vec<Reservation>) -> ProgramResult {
self.reservations = reservations
.iter()
.map(|r| ReservationV1 {
address: r.address,
spots_remaining: r.spots_remaining as u8,
total_spots: r.total_spots as u8,
})
.collect();
Ok(())
}
fn save(&self, account: &AccountInfo) -> ProgramResult {
BorshSerialize::serialize(self, &mut *account.data.borrow_mut())?;
Ok(())
}
fn total_reservation_spots(&self) -> u64 {
self.reservations.iter().map(|r| r.total_spots as u64).sum()
}
fn set_total_reservation_spots(&mut self, _: u64) {}
fn current_reservation_spots(&self) -> u64 {
self.reservations.iter().map(|r| r.total_spots as u64).sum()
}
fn set_current_reservation_spots(&mut self, _: u64) {}
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
pub struct ReservationV1 {
pub address: Pubkey,
pub spots_remaining: u8,
pub total_spots: u8,
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone, ShankAccount)]
pub struct EditionMarker {
pub key: Key,
pub ledger: [u8; 31],
}
impl Default for EditionMarker {
fn default() -> Self {
Self {
key: Key::EditionMarker,
ledger: [0; 31],
}
}
}
impl TokenMetadataAccount for EditionMarker {
fn key() -> Key {
Key::EditionMarker
}
fn size() -> usize {
MAX_EDITION_MARKER_SIZE
}
}
impl EditionMarker {
fn get_edition_offset_from_starting_index(edition: u64) -> Result<usize, ProgramError> {
Ok(edition
.checked_rem(EDITION_MARKER_BIT_SIZE)
.ok_or(MetadataError::NumericalOverflowError)? as usize)
}
fn get_index(offset_from_start: usize) -> Result<usize, ProgramError> {
let index = offset_from_start
.checked_div(8)
.ok_or(MetadataError::NumericalOverflowError)?;
if index > 30 {
return Err(MetadataError::InvalidEditionIndex.into());
}
Ok(index)
}
fn get_offset_from_right(offset_from_start: usize) -> Result<u32, ProgramError> {
Ok(7 - offset_from_start
.checked_rem(8)
.ok_or(MetadataError::NumericalOverflowError)? as u32)
}
fn get_index_and_mask(edition: u64) -> Result<(usize, u8), ProgramError> {
let offset_from_start = EditionMarker::get_edition_offset_from_starting_index(edition)?;
let index = EditionMarker::get_index(offset_from_start)?;
let my_position_in_index_starting_from_right =
EditionMarker::get_offset_from_right(offset_from_start)?;
Ok((
index,
u8::pow(2, my_position_in_index_starting_from_right as u32),
))
}
pub fn edition_taken(&self, edition: u64) -> Result<bool, ProgramError> {
let (index, mask) = EditionMarker::get_index_and_mask(edition)?;
let applied_mask = self.ledger[index] & mask;
Ok(applied_mask != 0)
}
pub fn insert_edition(&mut self, edition: u64) -> ProgramResult {
let (index, mask) = EditionMarker::get_index_and_mask(edition)?;
self.ledger[index] |= mask;
Ok(())
}
}