odra_modules/cep78/
modalities.rs

1use super::error::CEP78Error;
2use odra::prelude::*;
3
4/// The WhitelistMode dictates if the ACL whitelist restricting access to
5/// the mint entry point can be updated.
6#[repr(u8)]
7#[odra::odra_type]
8#[derive(Default)]
9pub enum WhitelistMode {
10    /// The ACL whitelist is unlocked and can be updated via the `set_variables` endpoint.
11    #[default]
12    Unlocked = 0,
13    /// The ACL whitelist is locked and cannot be updated further.
14    Locked = 1
15}
16
17impl TryFrom<u8> for WhitelistMode {
18    type Error = CEP78Error;
19
20    fn try_from(value: u8) -> Result<Self, Self::Error> {
21        match value {
22            0 => Ok(WhitelistMode::Unlocked),
23            1 => Ok(WhitelistMode::Locked),
24            _ => Err(CEP78Error::InvalidWhitelistMode)
25        }
26    }
27}
28
29/// The modality dictates which entities on a Casper network can own and mint NFTs.
30///
31/// If the NFTHolderMode is set to Contracts a ContractHash whitelist must be provided.
32/// This whitelist dictates which Contracts are allowed to mint NFTs in the restricted
33/// Installer minting mode.
34///
35/// This modality is an optional installation parameter and will default to the Mixed mode
36/// if not provided. However, this mode cannot be changed once the contract has been installed.
37#[repr(u8)]
38#[odra::odra_type]
39#[derive(Copy, Default)]
40pub enum NFTHolderMode {
41    /// Only Accounts can own and mint NFTs.
42    Accounts = 0,
43    /// Only Contracts can own and mint NFTs.
44    Contracts = 1,
45    /// Both Accounts and Contracts can own and mint NFTs.
46    #[default]
47    Mixed = 2
48}
49
50impl TryFrom<u8> for NFTHolderMode {
51    type Error = CEP78Error;
52
53    fn try_from(value: u8) -> Result<Self, Self::Error> {
54        match value {
55            0 => Ok(NFTHolderMode::Accounts),
56            1 => Ok(NFTHolderMode::Contracts),
57            2 => Ok(NFTHolderMode::Mixed),
58            _ => Err(CEP78Error::InvalidHolderMode)
59        }
60    }
61}
62
63/// The minting mode governs the behavior of contract when minting new tokens.
64///
65/// This modality is an optional installation parameter and will default
66/// to the `Installer` mode if not provided. However, this mode cannot be changed
67/// once the contract has been installed.
68#[odra::odra_type]
69#[repr(u8)]
70#[derive(Default)]
71pub enum MintingMode {
72    /// The ability to mint NFTs is restricted to the installing account only.
73    #[default]
74    Installer = 0,
75    /// The ability to mint NFTs is not restricted.
76    Public = 1,
77    /// The ability to mint NFTs is restricted by an ACL.
78    Acl = 2
79}
80
81impl TryFrom<u8> for MintingMode {
82    type Error = CEP78Error;
83
84    fn try_from(value: u8) -> Result<Self, Self::Error> {
85        match value {
86            0 => Ok(MintingMode::Installer),
87            1 => Ok(MintingMode::Public),
88            2 => Ok(MintingMode::Acl),
89            _ => Err(CEP78Error::InvalidMintingMode)
90        }
91    }
92}
93
94#[repr(u8)]
95#[odra::odra_type]
96#[derive(Default)]
97pub enum NFTKind {
98    /// The NFT represents a real-world physical
99    /// like a house.
100    #[default]
101    Physical = 0,
102    /// The NFT represents a digital asset like a unique
103    /// JPEG or digital art.
104    Digital = 1,
105    /// The NFT is the virtual representation
106    /// of a physical notion, e.g a patent
107    /// or copyright.
108    Virtual = 2
109}
110
111impl TryFrom<u8> for NFTKind {
112    type Error = CEP78Error;
113
114    fn try_from(value: u8) -> Result<Self, Self::Error> {
115        match value {
116            0 => Ok(NFTKind::Physical),
117            1 => Ok(NFTKind::Digital),
118            2 => Ok(NFTKind::Virtual),
119            _ => Err(CEP78Error::InvalidNftKind)
120        }
121    }
122}
123
124pub type MetadataRequirement = BTreeMap<NFTMetadataKind, Requirement>;
125
126#[odra::odra_type]
127#[repr(u8)]
128pub enum Requirement {
129    Required = 0,
130    Optional = 1,
131    Unneeded = 2
132}
133
134impl TryFrom<u8> for Requirement {
135    type Error = CEP78Error;
136
137    fn try_from(value: u8) -> Result<Self, Self::Error> {
138        match value {
139            0 => Ok(Requirement::Required),
140            1 => Ok(Requirement::Optional),
141            2 => Ok(Requirement::Unneeded),
142            _ => Err(CEP78Error::InvalidRequirement)
143        }
144    }
145}
146
147/// This modality dictates the schema for the metadata for NFTs minted
148/// by a given instance of an NFT contract.
149#[repr(u8)]
150#[derive(Default, PartialOrd, Ord)]
151#[odra::odra_type]
152pub enum NFTMetadataKind {
153    /// NFTs must have valid metadata conforming to the CEP-78 schema.
154    #[default]
155    CEP78 = 0,
156    /// NFTs  must have valid metadata conforming to the NFT-721 metadata schema.
157    NFT721 = 1,
158    /// Metadata validation will not occur and raw strings can be passed to
159    /// `token_metadata` runtime argument as part of the call to mint entrypoint.
160    Raw = 2,
161    /// Custom schema provided at the time of install will be used when validating
162    /// the metadata as part of the call to mint entrypoint.
163    CustomValidated = 3
164}
165
166impl TryFrom<u8> for NFTMetadataKind {
167    type Error = CEP78Error;
168
169    fn try_from(value: u8) -> Result<Self, Self::Error> {
170        match value {
171            0 => Ok(NFTMetadataKind::CEP78),
172            1 => Ok(NFTMetadataKind::NFT721),
173            2 => Ok(NFTMetadataKind::Raw),
174            3 => Ok(NFTMetadataKind::CustomValidated),
175            _ => Err(CEP78Error::InvalidNFTMetadataKind)
176        }
177    }
178}
179
180/// This modality specifies the behavior regarding ownership of NFTs and whether
181/// the owner of the NFT can change over the contract's lifetime.
182///
183/// Ownership mode is a required installation parameter and cannot be changed
184/// once the contract has been installed.
185#[repr(u8)]
186#[odra::odra_type]
187#[derive(Default, PartialOrd, Ord, Copy)]
188pub enum OwnershipMode {
189    /// The minter owns it and can never transfer it.
190    #[default]
191    Minter = 0,
192    /// The minter assigns it to an address and can never be transferred.
193    Assigned = 1,
194    /// The NFT can be transferred even to an recipient that does not exist.
195    Transferable = 2
196}
197
198impl TryFrom<u8> for OwnershipMode {
199    type Error = CEP78Error;
200
201    fn try_from(value: u8) -> Result<Self, Self::Error> {
202        match value {
203            0 => Ok(OwnershipMode::Minter),
204            1 => Ok(OwnershipMode::Assigned),
205            2 => Ok(OwnershipMode::Transferable),
206            _ => Err(CEP78Error::InvalidOwnershipMode)
207        }
208    }
209}
210
211/// The identifier mode governs the primary identifier for NFTs minted
212/// for a given instance on an installed contract.
213///
214/// Since the default primary identifier in the `Hash` mode is custom or derived by
215/// hashing over the metadata, making it a content-addressed identifier,
216/// the metadata for the minted NFT cannot be updated after the mint.
217///
218/// Attempting to install the contract with the [MetadataMutability] modality set to
219/// `Mutable` in the `Hash` identifier mode will raise an error.
220///
221/// This modality is a required installation parameter and cannot be changed
222/// once the contract has been installed.
223#[repr(u8)]
224#[odra::odra_type]
225#[derive(Default, PartialOrd, Ord, Copy)]
226pub enum NFTIdentifierMode {
227    /// NFTs minted in this modality are identified by a u64 value.
228    /// This value is determined by the number of NFTs minted by
229    /// the contract at the time the NFT is minted.
230    #[default]
231    Ordinal = 0,
232    /// NFTs minted in this modality are identified by an optional custom
233    /// string identifier or by default a base16 encoded representation of
234    /// the blake2b hash of the metadata provided at the time of mint.
235    Hash = 1
236}
237
238impl TryFrom<u8> for NFTIdentifierMode {
239    type Error = CEP78Error;
240
241    fn try_from(value: u8) -> Result<Self, Self::Error> {
242        match value {
243            0 => Ok(NFTIdentifierMode::Ordinal),
244            1 => Ok(NFTIdentifierMode::Hash),
245            _ => Err(CEP78Error::InvalidIdentifierMode)
246        }
247    }
248}
249
250/// The metadata mutability mode governs the behavior around updates to a given NFTs metadata.
251///
252/// The Mutable option cannot be used in conjunction with the Hash modality for the NFT identifier;
253/// attempting to install the contract with this configuration raises
254/// [super::error::CEP78Error::InvalidMetadataMutability] error.
255///
256/// This modality is a required installation parameter and cannot be changed
257/// once the contract has been installed.
258#[repr(u8)]
259#[derive(Default, PartialOrd, Ord, Copy)]
260#[odra::odra_type]
261pub enum MetadataMutability {
262    /// Metadata for NFTs minted in this mode cannot be updated once the NFT has been minted.
263    #[default]
264    Immutable = 0,
265    /// Metadata for NFTs minted in this mode can update the metadata via the `set_token_metadata` entrypoint.
266    Mutable = 1
267}
268
269impl TryFrom<u8> for MetadataMutability {
270    type Error = CEP78Error;
271
272    fn try_from(value: u8) -> Result<Self, Self::Error> {
273        match value {
274            0 => Ok(MetadataMutability::Immutable),
275            1 => Ok(MetadataMutability::Mutable),
276            _ => Err(CEP78Error::InvalidMetadataMutability)
277        }
278    }
279}
280
281#[odra::odra_type]
282pub enum TokenIdentifier {
283    Index(u64),
284    Hash(String)
285}
286
287impl TokenIdentifier {
288    pub fn new_index(index: u64) -> Self {
289        TokenIdentifier::Index(index)
290    }
291
292    pub fn new_hash(hash: String) -> Self {
293        TokenIdentifier::Hash(hash)
294    }
295
296    pub fn get_index(&self) -> Option<u64> {
297        if let Self::Index(index) = self {
298            return Some(*index);
299        }
300        None
301    }
302
303    pub fn get_hash(&self) -> Option<String> {
304        if let Self::Hash(hash) = self {
305            return Some(hash.to_owned());
306        }
307        None
308    }
309
310    pub fn get_dictionary_item_key(&self) -> String {
311        match self {
312            TokenIdentifier::Index(token_index) => token_index.to_string(),
313            TokenIdentifier::Hash(hash) => hash.clone()
314        }
315    }
316}
317
318#[allow(clippy::to_string_trait_impl)]
319impl ToString for TokenIdentifier {
320    fn to_string(&self) -> String {
321        match self {
322            TokenIdentifier::Index(index) => index.to_string(),
323            TokenIdentifier::Hash(hash) => hash.to_string()
324        }
325    }
326}
327
328/// The modality dictates whether tokens minted by a given instance of
329/// an NFT contract can be burnt.
330#[repr(u8)]
331#[odra::odra_type]
332#[derive(Default)]
333pub enum BurnMode {
334    /// Minted tokens can be burnt.
335    #[default]
336    Burnable = 0,
337    /// Minted tokens cannot be burnt.
338    NonBurnable = 1
339}
340
341impl TryFrom<u8> for BurnMode {
342    type Error = CEP78Error;
343
344    fn try_from(value: u8) -> Result<Self, Self::Error> {
345        match value {
346            0 => Ok(BurnMode::Burnable),
347            1 => Ok(BurnMode::NonBurnable),
348            _ => Err(CEP78Error::InvalidBurnMode)
349        }
350    }
351}
352
353/// This modality is set at install and determines if a given contract instance
354/// writes necessary data to allow reverse lookup by owner in addition to by ID.
355///
356/// This modality provides the following options:
357///
358/// `NoLookup`: The reporting and receipt functionality is not supported.
359/// In this option, the contract instance does not maintain a reverse lookup
360/// database of ownership and therefore has more predictable gas costs and greater
361/// scaling.
362/// `Complete`: The reporting and receipt functionality is supported. Token
363/// ownership will be tracked by the contract instance using the system described
364/// [here](https://github.com/casper-ecosystem/cep-78-enhanced-nft/blob/dev/docs/reverse-lookup.md#owner-reverse-lookup-functionality).
365/// `TransfersOnly`: The reporting and receipt functionality is supported like
366/// `Complete`. However, it does not begin tracking until the first transfer.
367/// This modality is for use cases where the majority of NFTs are owned by
368/// a private minter and only NFT's that have been transferred benefit from
369/// reverse lookup tracking. Token ownership will also be tracked by the contract
370/// instance using the system described [here](https://github.com/casper-ecosystem/cep-78-enhanced-nft/blob/dev/docs/reverse-lookup.md#owner-reverse-lookup-functionality).
371///
372/// Additionally, when set to Complete, causes a receipt to be returned by the mint
373/// or transfer entrypoints, which the caller can store in their account or contract
374/// context for later reference.
375///
376/// Further, two special entrypoints are enabled in Complete mode. First,
377/// `register_owner` which when called will allocate the necessary tracking
378/// record for the imputed entity. This allows isolation of the one time gas cost
379/// to do this per owner, which is convenient for accounting purposes. Second,
380/// updated_receipts, which allows an owner of one or more NFTs held by the contract
381/// instance to attain up to date receipt information for the NFTs they currently own.
382#[repr(u8)]
383#[derive(Default, PartialOrd, Ord, Copy)]
384#[odra::odra_type]
385pub enum OwnerReverseLookupMode {
386    /// The reporting and receipt functionality is not supported.
387    #[default]
388    NoLookUp = 0,
389    /// The reporting and receipt functionality is supported.
390    Complete = 1,
391    /// The reporting and receipt functionality is supported, but the tracking
392    /// does not start until the first transfer.
393    TransfersOnly = 2
394}
395
396impl TryFrom<u8> for OwnerReverseLookupMode {
397    type Error = CEP78Error;
398
399    fn try_from(value: u8) -> Result<Self, Self::Error> {
400        match value {
401            0 => Ok(OwnerReverseLookupMode::NoLookUp),
402            1 => Ok(OwnerReverseLookupMode::Complete),
403            2 => Ok(OwnerReverseLookupMode::TransfersOnly),
404            _ => Err(CEP78Error::InvalidReportingMode)
405        }
406    }
407}
408
409/// The `EventsMode` modality determines how the installed instance of CEP-78
410/// will handle the recording of events that occur from interacting with
411/// the contract.
412///
413/// Odra does not allow to set the `CEP47` event schema.
414#[repr(u8)]
415#[odra::odra_type]
416#[derive(Copy, Default)]
417#[allow(clippy::upper_case_acronyms)]
418pub enum EventsMode {
419    /// Signals the contract to not record events at all. This is the default mode.
420    #[default]
421    NoEvents = 0,
422    /// Signals the contract to record events using the Casper Event Standard.
423    CES = 2
424}
425
426impl TryFrom<u8> for EventsMode {
427    type Error = CEP78Error;
428
429    fn try_from(value: u8) -> Result<Self, Self::Error> {
430        match value {
431            0 => Ok(EventsMode::NoEvents),
432            2 => Ok(EventsMode::CES),
433            _ => Err(CEP78Error::InvalidEventsMode)
434        }
435    }
436}
437
438/// The transfer filter modality.
439///
440/// If enabled, specifies a contract package hash
441/// pointing to a contract that will be called when the transfer method is
442/// invoked on the contract. CEP-78 will call the `can_transfer` method on the
443/// specified callback contract, which is expected to return a value of
444/// `TransferFilterContractResult`, represented as a u8.
445#[repr(u8)]
446#[non_exhaustive]
447#[odra::odra_type]
448pub enum TransferFilterContractResult {
449    /// Blocks the transfer regardless of the outcome of other checks
450    DenyTransfer = 0,
451    /// Allows the transfer to proceed if other checks also pass
452    ProceedTransfer
453}
454
455impl From<u8> for TransferFilterContractResult {
456    fn from(value: u8) -> Self {
457        match value {
458            0 => TransferFilterContractResult::DenyTransfer,
459            _ => TransferFilterContractResult::ProceedTransfer
460        }
461    }
462}