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}