Skip to main content

shadow_nft_standard/common/
collection.rs

1use std::ops::{Deref, DerefMut};
2
3use anchor_lang::prelude::*;
4
5use super::creator_group::MAX_GROUP_SIZE;
6
7#[account]
8#[derive(PartialEq, Debug)]
9pub struct Collection {
10    /// Pubkey of the creator group that created this collection
11    pub creator_group_key: Pubkey,
12
13    /// Number of items in the collection
14    pub size: u32,
15
16    /// Signatures indicating whether the collection has been signed by all creators
17    pub sigs: u8,
18
19    /// Whether this collection will be minted via minter
20    pub for_minter: bool,
21
22    /// Royalty per creator (in half-percentages!). Half-percentages are used
23    /// to be able to use a single byte (0 to 255) effectively.
24    ///
25    /// # Examples
26    /// `royalty_50bps_multiplier` = 4
27    ///     -> 4 * 0.5% = 2% royalty
28    ///
29    /// `royalty_50bps_multiplier` = 7
30    ///     -> 7 * 0.5% = 3.5% royalty
31    ///
32    /// The number of nonzero entries must match the number of creators in the creator group.
33    /// This should be enforced upon the initialization of a collection.
34    pub royalty_50bps: [u8; MAX_GROUP_SIZE],
35
36    /// Symbol for the collection
37    pub symbol: Symbol,
38
39    /// Name for the collection
40    pub name: String,
41}
42
43impl Collection {
44    /// Finds the `Pubkey` of the `Collection` for a particular creator group's collection.
45    pub fn get_pda(creator_group: Pubkey, collection_name: &str) -> Pubkey {
46        Pubkey::find_program_address(
47            &[creator_group.as_ref(), collection_name.as_ref()],
48            &crate::ID,
49        )
50        .0
51    }
52
53    pub(crate) fn initialized(&self, creators: &[Pubkey]) -> bool {
54        self.sigs.count_ones() == creators.len() as u32
55    }
56}
57
58pub const MAX_SYMBOL_BYTE_LENGTH: usize = 8;
59
60/// Only valid ASCII is allowed
61///
62/// (TODO: should we extend to all utf-8?)
63// #[account]
64#[derive(PartialEq, Eq, AnchorSerialize, AnchorDeserialize, Clone, Copy)]
65pub struct Symbol {
66    inner: [u8; MAX_SYMBOL_BYTE_LENGTH],
67}
68
69impl Symbol {
70    /// Validates input bytes and returns a new `Symbol`.
71    pub fn new(input: impl AsRef<[u8]>) -> std::result::Result<Symbol, SymbolError> {
72        // Try to parse input
73        let input_data = input.as_ref();
74        if input_data.len() > MAX_SYMBOL_BYTE_LENGTH {
75            return Err(SymbolError::ExceedsMaxLength);
76        }
77
78        // ASCII is a subset of utf-8. If it is not valid utf-8 it is not valid ASCII
79        let input_str = core::str::from_utf8(input_data).map_err(|_| SymbolError::InvalidAscii)?;
80
81        // Check for valid uppercase ascii
82        let uppercase_ascii = input_str.chars().all(|c| c.is_ascii_uppercase());
83        if !uppercase_ascii {
84            return Err(SymbolError::IncorrectCaseOrInvalidCharacters);
85        }
86
87        // Copy into array
88        let mut inner = [0; MAX_SYMBOL_BYTE_LENGTH];
89        inner[..input_data.len()].copy_from_slice(input_data);
90
91        Ok(Symbol { inner })
92    }
93
94    /// Returns the inner symbol as a `&str`.
95    pub fn get(&self) -> &str {
96        core::str::from_utf8(&self.inner)
97            .expect("should have been checked for valid ASCII")
98            .trim_end()
99            .trim_end_matches('\0')
100    }
101}
102
103#[derive(Debug, PartialEq)]
104pub enum SymbolError {
105    InvalidAscii,
106    IncorrectCaseOrInvalidCharacters,
107    ExceedsMaxLength,
108}
109
110impl Deref for Symbol {
111    type Target = [u8];
112    fn deref(&self) -> &Self::Target {
113        &self.inner
114    }
115}
116
117impl DerefMut for Symbol {
118    fn deref_mut(&mut self) -> &mut Self::Target {
119        &mut self.inner
120    }
121}
122
123impl std::fmt::Debug for Symbol {
124    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125        let symbol = core::str::from_utf8(&self.inner)
126            .expect("this should have been checked")
127            .trim_end()
128            .trim_end_matches('\0');
129        write!(f, "symbol = {symbol}")
130    }
131}
132
133#[test]
134fn test_symbol_uppercase() {
135    let upper_case = "SHDW";
136    let symbol = Symbol::new(upper_case).unwrap();
137    assert_eq!(symbol.get(), upper_case);
138
139    let upper_case_bytes = b"SHDW";
140    let symbol = Symbol::new(upper_case_bytes).unwrap();
141    assert_eq!(upper_case_bytes.as_ref(), symbol.get().as_bytes());
142}
143
144#[test]
145fn test_symbol_lowercase() {
146    let lower_case = "shdw";
147    assert_eq!(
148        Symbol::new(lower_case).unwrap_err(),
149        SymbolError::IncorrectCaseOrInvalidCharacters
150    );
151}