Skip to main content

odra_modules/
cep95.rs

1#![allow(unused_variables, missing_docs)]
2
3use alloc::collections::BTreeMap;
4use odra::{
5    casper_types::{bytesrepr::Bytes, U256},
6    named_keys::{
7        base64_encoded_key_value_storage, compound_key_value_storage, single_value_storage
8    },
9    prelude::*,
10    ContractRef
11};
12
13/// Casper-compatible NFT interface
14pub trait CEP95Interface {
15    /// Returns a name of the NFT token/collection.
16    fn name(&self) -> String;
17
18    /// Returns a short symbol or abbreviation for the NFT token/collection.
19    fn symbol(&self) -> String;
20
21    /// Returns the number of NFTs owned by a given account or contract
22    ///
23    /// # Arguments
24    /// owner - The account to query.
25    ///
26    /// # Returns
27    /// The number of NFTs owned by the account.
28    fn balance_of(&self, owner: Address) -> U256;
29
30    /// Returns the owner of a specific NFT.
31    ///
32    /// # Arguments
33    /// token_id - The ID of the NFT.
34    ///
35    /// # Returns
36    /// The owner if it exists, else None.
37    fn owner_of(&self, token_id: U256) -> Option<Address>;
38
39    /// Performs a recipient check and transfers the ownership of an NFT.
40    ///
41    /// Reverts unless the contract caller is the current owner, an authorized
42    /// operator, or the approved spender for this NFT. Reverts if `from` is not
43    /// the current owner, or if `token_id` does not reference a valid NFT.
44    /// Once ownership is updated and a `Transfer` event is emitted, the function
45    /// checks whether `to` is a contract hash. If it is, the contract MUST call
46    /// `on_cep95_received` on `to` and revert the entire transfer if that call
47    /// is absent or returns any value other than `true`.
48    ///
49    /// # Arguments
50    /// from - The current owner of the NFT.
51    /// to - The new owner.
52    /// token_id - The NFT ID.
53    /// data - Optional payload to pass to a receiving contract.
54    fn safe_transfer_from(
55        &mut self,
56        from: Address,
57        to: Address,
58        token_id: U256,
59        data: Option<Bytes>
60    );
61
62    /// Transfers the ownership of an NFT without checking the recipient contract.
63    ///
64    /// # Arguments
65    /// from - The current owner of the NFT.
66    /// to - The new owner.
67    /// token_id - The NFT ID.
68    fn transfer_from(&mut self, from: Address, to: Address, token_id: U256);
69
70    /// Approves another account or contract to transfer a specific NFT.
71    ///
72    /// # Arguments
73    /// spender - The account or contract that will be granted approval.
74    /// token_id - The NFT ID.
75    fn approve(&mut self, spender: Address, token_id: U256);
76
77    /// Revokes approval for a specific NFT.
78    ///
79    /// # Arguments
80    /// token_id - The NFT ID to revoke approval for.
81    fn revoke_approval(&mut self, token_id: U256);
82
83    /// Gets the approved account or contract for a specific NFT.
84    ///
85    /// # Arguments
86    /// token_id - The ID of the NFT to check.
87    ///
88    /// # Returns
89    /// Option<Address> - The approved spender account if one exists, else None.
90    fn approved_for(&self, token_id: U256) -> Option<Address>;
91
92    /// Enables operator approval for all of the caller's NFTs.
93    ///
94    /// # Arguments
95    /// operator - The operator address to be approved.
96    fn approve_for_all(&mut self, operator: Address);
97
98    /// Revokes operator approval for all of the caller's NFTs.
99    ///
100    /// # Arguments
101    /// operator - The operator address to be revoked.
102    fn revoke_approval_for_all(&mut self, operator: Address);
103
104    /// Checks if an operator is approved to manage all NFTs of the owner.
105    ///
106    /// # Arguments
107    /// owner - The NFT owner's address.
108    /// operator - The operator to check.
109    ///
110    /// # Returns
111    /// True if the operator is approved for all NFTs, false otherwise.
112    fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool;
113
114    /// Returns metadata for a given token ID.
115    ///
116    /// # Arguments
117    /// token_id - The ID of the NFT.
118    ///
119    /// # Returns
120    /// A vector of key-value pairs representing the metadata.
121    fn token_metadata(&self, token_id: U256) -> Vec<(String, String)>;
122}
123
124#[odra::module]
125/// Receiver interface
126struct CEP95Receiver;
127
128#[odra::module]
129impl CEP95Receiver {
130    /// Called after a `safe_transfer_from` completes its internal state update.
131    /// MUST return `true` to signal acceptance; returning `false` or reverting
132    /// causes the entire transfer to roll back.
133    ///
134    /// # Arguments
135    /// operator - The account (EOA or contract) that invoked `safe_transfer_from`.
136    /// from - The previous owner of `token_id`.
137    /// token_id - The NFT being transferred.
138    /// data - Opaque auxiliary data forwarded from the original call; may be `None` if no extra data was
139    /// supplied.
140    ///
141    /// # Returns
142    /// `true` to accept the NFT, anything else to reject.
143    #[allow(dead_code)]
144    pub fn on_cep95_received(
145        &mut self,
146        operator: &Address,
147        from: &Address,
148        token_id: &U256,
149        data: &Option<Bytes>
150    ) -> bool {
151        // This is a placeholder implementation. In a real contract, you would
152        // implement the logic to handle the received NFT here.
153        // For example, you might want to store the token ID and data in your contract's state.
154        // For now, we just return true to indicate acceptance.
155        true
156    }
157}
158
159const KEY_BALANCES: &str = "balances";
160const KEY_NAME: &str = "name";
161const KEY_SYMBOL: &str = "symbol";
162const KEY_APPROVED: &str = "approvals";
163const KEY_OPERATORS: &str = "operators";
164const KEY_METADATA: &str = "token_metadata";
165const KEY_OWNERS: &str = "owners";
166
167single_value_storage!(Cep95Name, String, KEY_NAME, Error::ValueNotSet);
168single_value_storage!(Cep95Symbol, String, KEY_SYMBOL, Error::ValueNotSet);
169base64_encoded_key_value_storage!(Cep95Balances, KEY_BALANCES, Address, U256);
170base64_encoded_key_value_storage!(Cep95Approvals, KEY_APPROVED, U256, Option<Address>);
171compound_key_value_storage!(Cep95Operators, KEY_OPERATORS, Address, bool);
172base64_encoded_key_value_storage!(Cep95Owners, KEY_OWNERS, U256, Option<Address>);
173base64_encoded_key_value_storage!(Cep95Metadata, KEY_METADATA, U256, BTreeMap<String, String>);
174
175/// Error enum for the CEP-95 contract.
176#[odra::odra_error]
177pub enum Error {
178    /// The value is not set.
179    ValueNotSet = 40_000,
180    /// The transfer failed.
181    TransferFailed = 40_001,
182    /// The token ID is invalid.
183    NotAnOwnerOrApproved = 40_002,
184    /// The approval is set to the current owner.
185    ApprovalToCurrentOwner = 40_003,
186    /// The caller is the same as the operator.
187    ApproveToCaller = 40_004,
188    /// The token ID is invalid.
189    InvalidTokenId = 40_005,
190    /// The token with the given ID already exists.
191    TokenAlreadyExists = 40_006
192}
193
194#[odra::event]
195/// Emitted when an NFT is minted
196pub struct Mint {
197    /// The address of the recipient.
198    pub to: Address,
199    /// The ID of the minted token.
200    pub token_id: U256
201}
202
203#[odra::event]
204/// Emitted when an NFT is burned
205pub struct Burn {
206    /// The address of the owner.
207    pub from: Address,
208    /// The ID of the burned token.
209    pub token_id: U256
210}
211
212#[odra::event]
213/// Emitted when an NFT is transferred
214pub struct Transfer {
215    /// The address of the previous owner.
216    pub from: Address,
217    /// The address of the new owner.
218    pub to: Address,
219    /// The ID of the transferred token.
220    pub token_id: U256
221}
222
223#[odra::event]
224/// Emitted when a specific NFT is approved to an account/contract
225pub struct Approval {
226    /// The address of the owner.
227    pub owner: Address,
228    /// The address of the approved spender.
229    pub spender: Address,
230    /// The ID of the approved token.
231    pub token_id: U256
232}
233
234#[odra::event]
235/// Emitted when a specific NFT approval is revoked from an account/contract
236pub struct RevokeApproval {
237    /// The address of the owner.
238    pub owner: Address,
239    /// The address of the revoked spender.
240    pub spender: Address,
241    /// The ID of the revoked token.
242    pub token_id: U256
243}
244
245#[odra::event]
246/// Emitted when an operator is approved for all NFTs of an owner
247pub struct ApprovalForAll {
248    /// The address of the owner.
249    pub owner: Address,
250    /// The address of the operator.
251    pub operator: Address
252}
253
254#[odra::event]
255/// Emitted when an operator approval is revoked for all NFTs of an owner
256pub struct RevokeApprovalForAll {
257    /// The address of the owner.
258    pub owner: Address,
259    /// The address of the operator.
260    pub operator: Address
261}
262
263#[odra::event]
264/// Emitted whenever on-chain metadata for a token is created or updated
265pub struct MetadataUpdate {
266    /// The ID of the token.
267    pub token_id: U256
268}
269
270#[odra::module(
271    events = [
272        Transfer,
273        Approval,
274        RevokeApproval,
275        ApprovalForAll,
276        RevokeApprovalForAll,
277        Mint,
278        Burn,
279        MetadataUpdate
280    ],
281    errors = Error
282)]
283/// A module representing a CEP-95 standard.
284pub struct Cep95 {
285    /// A submodule for the token name.
286    pub name: SubModule<Cep95Name>,
287    /// A submodule for the token symbol.
288    pub symbol: SubModule<Cep95Symbol>,
289    /// A submodule for the token balances mapping.
290    pub balances: SubModule<Cep95Balances>,
291    /// A submodule for the token owners mapping.
292    pub owners: SubModule<Cep95Owners>,
293    /// A submodule for the token approvals mapping.
294    pub approvals: SubModule<Cep95Approvals>,
295    /// A submodule for the token operators mapping.
296    pub operators: SubModule<Cep95Operators>,
297    /// A submodule for the token metadata mapping.
298    pub metadata: SubModule<Cep95Metadata>
299}
300
301#[odra::module]
302impl CEP95Interface for Cep95 {
303    fn name(&self) -> String {
304        self.name.get()
305    }
306
307    fn symbol(&self) -> String {
308        self.symbol.get()
309    }
310
311    fn balance_of(&self, owner: Address) -> U256 {
312        self.balances.get(&owner).unwrap_or_default()
313    }
314
315    fn owner_of(&self, token_id: U256) -> Option<Address> {
316        self.owners.get(&token_id).flatten()
317    }
318
319    fn safe_transfer_from(
320        &mut self,
321        from: Address,
322        to: Address,
323        token_id: U256,
324        data: Option<Bytes>
325    ) {
326        self.transfer_from(from, to, token_id);
327        if to.is_contract() {
328            let mut receiver = CEP95ReceiverContractRef::new(self.env(), to);
329            let caller = self.env().caller();
330            let result = receiver.on_cep95_received(&caller, &from, &token_id, &data);
331            if !result {
332                self.env().revert(Error::TransferFailed);
333            }
334        }
335    }
336
337    fn transfer_from(&mut self, from: Address, to: Address, token_id: U256) {
338        self.assert_exists(&token_id);
339
340        let caller = self.env().caller();
341        let owner = self.owner_of(token_id).unwrap_or_revert_with(self, Error::InvalidTokenId);
342
343        // `from` must be the current owner.
344        if owner != from {
345            self.env().revert(Error::NotAnOwnerOrApproved);
346        }
347
348        // Authorize the caller BEFORE any state mutation.
349        let is_authorized = owner == caller
350            || self.is_approved_for_all(from, caller)
351            || self.is_spender(token_id, caller);
352        if !is_authorized {
353            self.env().revert(Error::NotAnOwnerOrApproved);
354        }
355
356        self.raw_transfer(to, token_id); // clears approval + moves token
357    }
358
359    fn approve(&mut self, spender: Address, token_id: U256) {
360        let caller = self.env().caller();
361        let owner = self
362            .owner_of(token_id)
363            .unwrap_or_revert_with(self, Error::InvalidTokenId);
364        self.set_approve(token_id, owner, Some(spender));
365        self.env().emit_event(Approval {
366            owner,
367            spender,
368            token_id
369        });
370    }
371
372    fn revoke_approval(&mut self, token_id: U256) {
373        let spender = self
374            .approved_for(token_id)
375            .unwrap_or_revert_with(self, Error::ValueNotSet);
376        let owner = self
377            .owner_of(token_id)
378            .unwrap_or_revert_with(self, Error::InvalidTokenId);
379        self.set_approve(token_id, owner, None);
380        self.env().emit_event(RevokeApproval {
381            owner,
382            spender,
383            token_id
384        });
385    }
386
387    fn approved_for(&self, token_id: U256) -> Option<Address> {
388        self.assert_exists(&token_id);
389        self.approvals.get(&token_id).flatten()
390    }
391
392    fn approve_for_all(&mut self, operator: Address) {
393        let caller = self.env().caller();
394        self.set_approval_for_all(caller, operator, true);
395        self.env().emit_event(ApprovalForAll {
396            owner: caller,
397            operator
398        });
399    }
400
401    fn revoke_approval_for_all(&mut self, operator: Address) {
402        let caller = self.env().caller();
403        self.set_approval_for_all(caller, operator, false);
404        self.env().emit_event(RevokeApprovalForAll {
405            owner: caller,
406            operator
407        });
408    }
409
410    fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool {
411        self.operators.get_or_default(&owner, &operator)
412    }
413
414    fn token_metadata(&self, token_id: U256) -> Vec<(String, String)> {
415        self.assert_exists(&token_id);
416        self.metadata
417            .get(&token_id)
418            .unwrap_or_default()
419            .into_iter()
420            .collect()
421    }
422}
423
424impl Cep95 {
425    /// Initializes the module with a name and symbol.
426    pub fn init(&mut self, name: String, symbol: String) {
427        // Initialize the name and symbol.
428        self.name.set(name);
429        self.symbol.set(symbol);
430
431        // Initialize dictionaries.
432        self.balances.init();
433        self.owners.init();
434        self.approvals.init();
435        self.operators.init();
436        self.metadata.init();
437    }
438
439    #[inline]
440    /// Asserts that the token ID exists.
441    /// Reverts with `Error::InvalidTokenId` if it does not.
442    pub fn assert_exists(&self, token_id: &U256) {
443        if !self.exists(token_id) {
444            self.env().revert(Error::InvalidTokenId);
445        }
446    }
447
448    /// Checks if the token ID exists.
449    #[inline]
450    pub fn exists(&self, token_id: &U256) -> bool {
451        self.owners.get(token_id).flatten().is_some()
452    }
453
454    /// Clears the approval for a specific token ID.
455    /// SECURITY: Do not expose this function publicly without proper access control.
456    #[inline]
457    pub fn clear_approval(&mut self, token_id: &U256) {
458        if self.approvals.get(token_id).is_some() {
459            self.approvals.set(token_id, None);
460        }
461    }
462
463    /// Mints a new NFT and assigns it to the specified address.
464    /// SECURITY: Do not expose this function publicly without proper access control.
465    pub fn raw_mint(&mut self, to: Address, token_id: U256, metadata: Vec<(String, String)>) {
466        if self.exists(&token_id) {
467            self.env().revert(Error::TokenAlreadyExists);
468        }
469
470        self.balances.set(&to, self.balance_of(to) + 1);
471        self.owners.set(&token_id, Some(to));
472        self.metadata.set(&token_id, BTreeMap::from_iter(metadata));
473
474        self.env().emit_event(Mint { to, token_id });
475    }
476
477    /// Burns an NFT, removing it from the owner's balance and the contract.
478    /// SECURITY: Do not expose this function publicly without proper access control.
479    pub fn raw_burn(&mut self, token_id: U256) {
480        self.assert_exists(&token_id);
481        let owner = self
482            .owner_of(token_id)
483            .unwrap_or_revert_with(self, Error::ValueNotSet);
484
485        self.clear_approval(&token_id);
486        self.balances.set(&owner, self.balance_of(owner) - 1);
487        self.owners.set(&token_id, None);
488        self.metadata.set(&token_id, Default::default());
489
490        self.env().emit_event(Burn {
491            from: owner,
492            token_id
493        });
494    }
495
496    /// Sets metadata for a specific token ID.
497    /// Replaces any existing metadata.
498    /// SECURITY: Do not expose this function publicly without proper access control.
499    pub fn set_metadata(&mut self, token_id: U256, metadata: Vec<(String, String)>) {
500        self.raw_update_metadata(token_id, metadata, BTreeMap::new());
501    }
502
503    /// Updates metadata for a specific token ID.
504    /// If a key already exists, its value will be updated.
505    /// If a key does not exist, it will be added.
506    /// The remaining keys will be preserved.
507    /// SECURITY: Do not expose this function publicly without proper access control.
508    pub fn update_metadata(&mut self, token_id: U256, metadata: Vec<(String, String)>) {
509        let current_metadata = self.metadata.get(&token_id).unwrap_or_default();
510        self.raw_update_metadata(token_id, metadata, current_metadata);
511    }
512
513    /// Transfers an NFT from one address to another without checking the recipient contract.
514    /// SECURITY: Do not expose this function publicly without proper access control.
515    pub fn raw_transfer(&mut self, to: Address, token_id: U256) -> Address {
516        self.clear_approval(&token_id);
517        let from = self
518            .owner_of(token_id)
519            .unwrap_or_revert_with(self, Error::InvalidTokenId);
520        self.balances.set(&from, self.balance_of(from) - 1);
521        self.balances.set(&to, self.balance_of(to) + 1);
522        self.owners.set(&token_id, Some(to));
523
524        self.env().emit_event(Transfer { from, to, token_id });
525        from
526    }
527
528    fn raw_update_metadata(
529        &mut self,
530        token_id: U256,
531        new_metadata: Vec<(String, String)>,
532        mut current_metadata: BTreeMap<String, String>
533    ) {
534        self.assert_exists(&token_id);
535        for (k, v) in new_metadata {
536            current_metadata.insert(k, v);
537        }
538        self.metadata.set(&token_id, current_metadata);
539        self.env().emit_event(MetadataUpdate { token_id });
540    }
541
542    #[inline]
543    fn set_approve(&mut self, token_id: U256, owner: Address, spender: Option<Address>) {
544        if Some(owner) == spender {
545            self.env().revert(Error::ApprovalToCurrentOwner);
546        }
547
548        let caller = self.env().caller();
549        if caller != owner && !self.is_approved_for_all(owner, caller) {
550            self.env().revert(Error::NotAnOwnerOrApproved);
551        }
552
553        self.approvals.set(&token_id, spender);
554    }
555
556    #[inline]
557    fn set_approval_for_all(&mut self, caller: Address, operator: Address, approved: bool) {
558        if caller == operator {
559            self.env().revert(Error::ApproveToCaller)
560        }
561
562        self.operators.set(&caller, &operator, approved);
563    }
564
565    #[inline]
566    fn is_spender(&self, token_id: U256, spender: Address) -> bool {
567        self.approved_for(token_id) == Some(spender)
568    }
569}
570
571mod utils {
572    #![allow(dead_code)]
573    use crate::access::Ownable;
574
575    use super::*;
576
577    #[odra::module]
578    pub(crate) struct BasicCep95 {
579        token: SubModule<Cep95>,
580        ownable: SubModule<Ownable>
581    }
582
583    #[odra::module]
584    impl BasicCep95 {
585        /// Initializes the contract with the given parameters.
586        pub fn init(&mut self, name: String, symbol: String) {
587            let owner = self.env().caller();
588            self.ownable.init(owner);
589            self.token.init(name, symbol);
590        }
591
592        delegate! {
593            to self.token {
594                fn name(&self) -> String;
595                fn symbol(&self) -> String;
596                fn balance_of(&self, owner: Address) -> U256;
597                fn owner_of(&self, token_id: U256) -> Option<Address>;
598                fn safe_transfer_from(&mut self, from: Address, to: Address, token_id: U256, data: Option<Bytes>);
599                fn transfer_from(&mut self, from: Address, to: Address, token_id: U256);
600                fn approve(&mut self, spender: Address, token_id: U256);
601                fn revoke_approval(&mut self, token_id: U256);
602                fn approved_for(&self, token_id: U256) -> Option<Address>;
603                fn approve_for_all(&mut self, operator: Address);
604                fn revoke_approval_for_all(&mut self, operator: Address);
605                fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool;
606                fn token_metadata(&self, token_id: U256) -> Vec<(String, String)>;
607            }
608        }
609
610        pub fn mint(&mut self, to: Address, token_id: U256, metadata: Vec<(String, String)>) {
611            self.assert_owner();
612            self.token.raw_mint(to, token_id, metadata);
613        }
614
615        pub fn burn(&mut self, token_id: U256) {
616            self.assert_owner();
617            self.token.raw_burn(token_id);
618        }
619
620        pub fn set_metadata(&mut self, token_id: U256, metadata: Vec<(String, String)>) {
621            self.assert_owner();
622            self.token.set_metadata(token_id, metadata);
623        }
624
625        pub fn update_metadata(&mut self, token_id: U256, metadata: Vec<(String, String)>) {
626            self.assert_owner();
627            self.token.update_metadata(token_id, metadata);
628        }
629
630        pub fn assert_owner(&self) {
631            self.ownable.assert_owner(&self.env().caller());
632        }
633    }
634
635    #[odra::module]
636    pub(crate) struct NFTReceiver {
637        #[allow(clippy::type_complexity)]
638        last_call_data: Var<((Address, Address), (U256, Option<Bytes>))>
639    }
640
641    #[odra::module]
642    impl NFTReceiver {
643        #[allow(dead_code)]
644        pub fn on_cep95_received(
645            &mut self,
646            operator: Address,
647            from: Address,
648            token_id: U256,
649            data: Option<Bytes>
650        ) -> bool {
651            self.last_call_data
652                .set(((operator, from), (token_id, data.clone())));
653            true
654        }
655
656        #[allow(dead_code)]
657        pub fn last_call_result(&self) -> ((Address, Address), (U256, Option<Bytes>)) {
658            self.last_call_data.get().unwrap_or_revert(self)
659        }
660    }
661
662    #[odra::module]
663    pub(crate) struct RejectingNFTReceiver;
664
665    #[odra::module]
666    impl RejectingNFTReceiver {
667        #[allow(dead_code)]
668        pub fn on_cep95_received(
669            &mut self,
670            operator: Address,
671            from: Address,
672            token_id: U256,
673            data: Option<Bytes>
674        ) -> bool {
675            false
676        }
677    }
678
679    #[odra::module]
680    pub(crate) struct BasicContract;
681
682    #[odra::module]
683    impl BasicContract {}
684}
685
686#[cfg(test)]
687mod tests {
688    use super::*;
689    use crate::cep95::utils::*;
690    use odra::{
691        host::{Deployer, HostEnv, NoArgs},
692        prelude::Addressable,
693        VmError
694    };
695    use odra_test;
696
697    fn setup() -> (HostEnv, BasicCep95HostRef) {
698        let env = odra_test::env();
699        let cep95 = BasicCep95::try_deploy(
700            &env,
701            BasicCep95InitArgs {
702                name: "TestToken".to_string(),
703                symbol: "TT".to_string()
704            }
705        )
706        .unwrap();
707        (env, cep95)
708    }
709
710    #[test]
711    fn test_deploy() {
712        let env = odra_test::env();
713        let cep95 = BasicCep95::try_deploy(
714            &env,
715            BasicCep95InitArgs {
716                name: "TestToken".to_string(),
717                symbol: "TT".to_string()
718            }
719        );
720        assert!(cep95.is_ok());
721    }
722
723    #[test]
724    fn test_name() {
725        let (env, cep95) = setup();
726        let name = cep95.name();
727        assert_eq!(name, "TestToken");
728    }
729
730    #[test]
731    fn test_symbol() {
732        let (env, cep95) = setup();
733        let symbol = cep95.symbol();
734        assert_eq!(symbol, "TT");
735    }
736
737    #[test]
738    fn test_mint() {
739        let (env, mut cep95) = setup();
740        let owner = env.caller();
741
742        let token_id = U256::from(1);
743        let metadata = vec![("key".to_string(), "value".to_string())];
744        cep95.mint(owner, token_id, metadata);
745
746        assert_eq!(cep95.balance_of(owner), U256::from(1));
747        assert_eq!(cep95.owner_of(token_id), Some(owner));
748        assert_eq!(
749            cep95.token_metadata(token_id),
750            vec![("key".to_string(), "value".to_string())]
751        );
752        assert!(env.emitted(&cep95, "Mint"));
753    }
754
755    #[test]
756    fn test_minting_existing_token() {
757        let (env, mut cep95) = setup();
758        let owner = env.caller();
759
760        let token_id = U256::from(1);
761        let metadata = vec![("key".to_string(), "value".to_string())];
762        cep95.mint(owner, token_id, metadata.clone());
763
764        let result = cep95.try_mint(owner, token_id, metadata);
765        assert_eq!(result, Err(Error::TokenAlreadyExists.into()));
766    }
767
768    #[test]
769    fn test_mint_many_tokens() {
770        let (env, mut cep95) = setup();
771        let owner = env.caller();
772
773        let token_id1 = U256::from(1);
774        let metadata1 = vec![("key1".to_string(), "value1".to_string())];
775        cep95.mint(owner, token_id1, metadata1);
776
777        let token_id2 = U256::from(2);
778        let metadata2 = vec![("key2".to_string(), "value2".to_string())];
779        cep95.mint(owner, token_id2, metadata2);
780
781        assert_eq!(cep95.balance_of(owner), U256::from(2));
782        assert_eq!(cep95.owner_of(token_id1), Some(owner));
783        assert_eq!(cep95.owner_of(token_id2), Some(owner));
784    }
785
786    #[test]
787    fn test_burn() {
788        let (env, mut cep95) = setup();
789        let owner = env.caller();
790
791        let token_id = U256::from(1);
792        let metadata = vec![("key".to_string(), "value".to_string())];
793        cep95.mint(owner, token_id, metadata);
794        cep95.burn(token_id);
795
796        assert_eq!(cep95.balance_of(owner), U256::from(0));
797        assert_eq!(cep95.owner_of(token_id), None);
798        assert_eq!(
799            cep95.try_token_metadata(token_id),
800            Err(Error::InvalidTokenId.into())
801        );
802        assert!(env.emitted(&cep95, "Burn"));
803    }
804
805    #[test]
806    fn test_burn_non_existing_token() {
807        let (env, mut cep95) = setup();
808        let owner = env.caller();
809
810        let token_id = U256::from(1);
811        let result = cep95.try_burn(token_id);
812        assert_eq!(result, Err(Error::InvalidTokenId.into()));
813    }
814
815    #[test]
816    fn test_safe_transfer_to_receiver() {
817        let (env, mut cep95) = setup();
818        let nft_receiver = NFTReceiver::deploy(&env, NoArgs);
819        let recipient = nft_receiver.address();
820        let owner = env.caller();
821
822        let token_id = U256::from(1);
823        let metadata = vec![("key".to_string(), "value".to_string())];
824        cep95.mint(owner, token_id, metadata);
825
826        cep95.safe_transfer_from(owner, recipient, token_id, None);
827
828        assert_eq!(cep95.balance_of(owner), U256::from(0));
829        assert_eq!(cep95.balance_of(recipient), U256::from(1));
830        assert_eq!(
831            nft_receiver.last_call_result(),
832            ((owner, owner), (token_id, None))
833        );
834    }
835
836    #[test]
837    fn test_safe_transfer_to_non_receiver() {
838        let (env, mut cep95) = setup();
839        let contract = BasicContract::deploy(&env, NoArgs);
840        let recipient = contract.address();
841        let owner = env.caller();
842
843        let token_id = U256::from(1);
844        let metadata = vec![("key".to_string(), "value".to_string())];
845        cep95.mint(owner, token_id, metadata);
846
847        let result = cep95.try_safe_transfer_from(owner, recipient, token_id, None);
848        assert_eq!(
849            result,
850            Err(OdraError::VmError(VmError::NoSuchMethod(
851                "on_cep95_received".to_string()
852            )))
853        );
854
855        assert_eq!(cep95.balance_of(owner), U256::from(1));
856        assert_eq!(cep95.balance_of(recipient), U256::from(0));
857    }
858
859    #[test]
860    fn test_safe_transfer_to_rejecting_receiver() {
861        let (env, mut cep95) = setup();
862
863        let owner = env.get_account(0);
864        let contract = RejectingNFTReceiver::deploy(&env, NoArgs);
865        let recipient = contract.address();
866
867        let token_id = U256::from(1);
868        let metadata = vec![("key".to_string(), "value".to_string())];
869        cep95.mint(owner, token_id, metadata);
870
871        let result = cep95.try_safe_transfer_from(owner, recipient, token_id, None);
872        assert_eq!(result, Err(Error::TransferFailed.into()));
873
874        assert_eq!(cep95.balance_of(owner), U256::from(1));
875        assert_eq!(cep95.balance_of(recipient), U256::from(0));
876    }
877
878    #[test]
879    fn test_transfer() {
880        let (env, mut cep95) = setup();
881
882        let owner = env.get_account(0);
883        let recipient = env.get_account(10);
884
885        let token_id = U256::from(1);
886        let metadata = vec![("key".to_string(), "value".to_string())];
887        cep95.mint(owner, token_id, metadata);
888        cep95.transfer_from(owner, recipient, token_id);
889
890        assert_eq!(cep95.balance_of(owner), U256::from(0));
891        assert_eq!(cep95.balance_of(recipient), U256::from(1));
892        assert!(env.emitted(&cep95, "Transfer"));
893    }
894
895    #[test]
896    fn test_transfer_non_existing_token() {
897        let (env, mut cep95) = setup();
898
899        let owner = env.get_account(0);
900        let recipient = env.get_account(10);
901
902        let token_id = U256::from(1);
903        let metadata = vec![("key".to_string(), "value".to_string())];
904        cep95.mint(owner, token_id, metadata);
905
906        let result = cep95.try_transfer_from(owner, recipient, U256::from(2));
907        assert_eq!(result, Err(Error::InvalidTokenId.into()));
908    }
909
910    #[test]
911    fn test_transfer_from_non_owner() {
912        let (env, mut cep95) = setup();
913
914        let recipient = env.get_account(10);
915        let non_owner = env.get_account(11);
916        let owner = env.get_account(0);
917
918        let token_id = U256::from(1);
919        let metadata = vec![("key".to_string(), "value".to_string())];
920        cep95.mint(owner, token_id, metadata);
921
922        let result = cep95.try_transfer_from(non_owner, recipient, token_id);
923        assert_eq!(result, Err(Error::NotAnOwnerOrApproved.into()));
924        assert_eq!(cep95.balance_of(owner), U256::from(1));
925        assert_eq!(cep95.balance_of(recipient), U256::from(0));
926    }
927
928    #[test]
929    fn test_approve() {
930        let (env, mut cep95) = setup();
931        let owner = env.caller();
932
933        let token_id = U256::from(1);
934        let metadata = vec![("key".to_string(), "value".to_string())];
935        cep95.mint(owner, token_id, metadata);
936
937        let spender = env.get_account(10);
938        cep95.approve(spender, token_id);
939
940        assert_eq!(cep95.approved_for(token_id), Some(spender));
941        assert!(env.emitted_event(
942            &cep95,
943            Approval {
944                owner,
945                spender,
946                token_id
947            }
948        ));
949    }
950
951    #[test]
952    fn test_approve_by_non_owner() {
953        let (env, mut cep95) = setup();
954        let owner = env.get_account(0);
955        let non_owner = env.get_account(11);
956
957        let token_id = U256::from(1);
958        let metadata = vec![("key".to_string(), "value".to_string())];
959        cep95.mint(owner, token_id, metadata);
960
961        let spender = env.get_account(10);
962        env.set_caller(non_owner);
963        let result = cep95.try_approve(spender, token_id);
964        assert_eq!(result, Err(Error::NotAnOwnerOrApproved.into()));
965    }
966
967    #[test]
968    fn test_approve_by_spender() {
969        let (env, mut cep95) = setup();
970        let owner = env.get_account(0);
971        let spender = env.get_account(10);
972
973        let token_id = U256::from(1);
974        let metadata = vec![("key".to_string(), "value".to_string())];
975        cep95.approve_for_all(spender);
976        cep95.mint(owner, token_id, metadata);
977
978        env.set_caller(spender);
979        let result = cep95.try_approve(spender, token_id);
980        assert!(result.is_ok());
981        assert!(env.emitted_event(
982            &cep95,
983            Approval {
984                owner,
985                spender,
986                token_id
987            }
988        ));
989    }
990
991    #[test]
992    fn test_transfer_by_approved() {
993        let (env, mut cep95) = setup();
994        let owner = env.get_account(0);
995        let recipient = env.get_account(10);
996
997        let token_id = U256::from(1);
998        let metadata = vec![("key".to_string(), "value".to_string())];
999        cep95.mint(owner, token_id, metadata);
1000
1001        let spender = env.get_account(11);
1002        cep95.approve(spender, token_id);
1003        env.set_caller(spender);
1004        cep95.transfer_from(owner, recipient, token_id);
1005
1006        assert_eq!(cep95.balance_of(owner), U256::from(0));
1007        assert_eq!(cep95.balance_of(recipient), U256::from(1));
1008        assert!(env.emitted_event(
1009            &cep95,
1010            Transfer {
1011                from: owner,
1012                to: recipient,
1013                token_id
1014            }
1015        ));
1016    }
1017
1018    #[test]
1019    fn test_approve_for_all() {
1020        let (env, mut cep95) = setup();
1021        let owner = env.caller();
1022        let operator = env.get_account(10);
1023
1024        cep95.approve_for_all(operator);
1025
1026        assert!(cep95.is_approved_for_all(owner, operator));
1027        assert!(env.emitted(&cep95, "ApprovalForAll"));
1028    }
1029
1030    #[test]
1031    fn test_revoke_approval_for_all() {
1032        let (env, mut cep95) = setup();
1033        let owner = env.caller();
1034        let operator = env.get_account(10);
1035
1036        cep95.approve_for_all(operator);
1037        cep95.revoke_approval_for_all(operator);
1038
1039        assert!(!cep95.is_approved_for_all(owner, operator));
1040        assert!(env.emitted(&cep95, "RevokeApprovalForAll"));
1041    }
1042
1043    #[test]
1044    fn test_approve_for_all_self() {
1045        let (env, mut cep95) = setup();
1046        let owner = env.caller();
1047
1048        let result = cep95.try_approve_for_all(owner);
1049        assert_eq!(result, Err(Error::ApproveToCaller.into()));
1050    }
1051
1052    #[test]
1053    fn test_revoke_approval() {
1054        let (env, mut cep95) = setup();
1055        let owner = env.caller();
1056
1057        let token_id = U256::from(1);
1058        let metadata = vec![("key".to_string(), "value".to_string())];
1059        cep95.mint(owner, token_id, metadata);
1060
1061        let spender = env.get_account(10);
1062        cep95.approve(spender, token_id);
1063        cep95.revoke_approval(token_id);
1064
1065        assert_eq!(cep95.approved_for(token_id), None);
1066        assert!(env.emitted(&cep95, "RevokeApproval"));
1067    }
1068
1069    #[test]
1070    fn test_revoke_approval_by_spender() {
1071        let (env, mut cep95) = setup();
1072        let owner = env.caller();
1073        let spender = env.get_account(10);
1074
1075        let token_id = U256::from(1);
1076        let metadata = vec![("key".to_string(), "value".to_string())];
1077        cep95.approve_for_all(spender);
1078        cep95.mint(owner, token_id, metadata);
1079        cep95.approve(spender, token_id);
1080
1081        env.set_caller(spender);
1082        cep95.revoke_approval(token_id);
1083
1084        assert_eq!(cep95.approved_for(token_id), None);
1085        assert!(env.emitted_event(
1086            &cep95,
1087            RevokeApproval {
1088                owner,
1089                spender,
1090                token_id
1091            }
1092        ));
1093    }
1094
1095    #[test]
1096    fn revoke_non_existing_approval() {
1097        let (env, mut cep95) = setup();
1098        let owner = env.caller();
1099
1100        let token_id = U256::from(1);
1101        let metadata = vec![("key".to_string(), "value".to_string())];
1102        cep95.mint(owner, token_id, metadata);
1103
1104        let result = cep95.try_revoke_approval(token_id);
1105        assert_eq!(result, Err(Error::ValueNotSet.into()));
1106    }
1107
1108    #[test]
1109    fn test_revoke_approval_by_non_owner() {
1110        let (env, mut cep95) = setup();
1111        let owner = env.get_account(0);
1112        let non_owner = env.get_account(11);
1113
1114        let token_id = U256::from(1);
1115        let metadata = vec![("key".to_string(), "value".to_string())];
1116        cep95.mint(owner, token_id, metadata);
1117
1118        let spender = env.get_account(10);
1119        cep95.approve(spender, token_id);
1120        env.set_caller(non_owner);
1121        let result = cep95.try_revoke_approval(token_id);
1122        assert_eq!(result, Err(Error::NotAnOwnerOrApproved.into()));
1123    }
1124
1125    #[test]
1126    fn test_approval_cleared_after_transfer() {
1127        // SECURITY regression: a per-token approval must not survive a change of
1128        // ownership. Otherwise the previously-approved spender could move the
1129        // token away from its new owner.
1130        let (env, mut cep95) = setup();
1131        let owner = env.get_account(0);
1132        let spender = env.get_account(11);
1133        let recipient = env.get_account(10);
1134        let attacker_dest = env.get_account(12);
1135
1136        let token_id = U256::from(1);
1137        let metadata = vec![("key".to_string(), "value".to_string())];
1138        cep95.mint(owner, token_id, metadata);
1139
1140        // Owner approves spender, then sells/transfers the token to recipient.
1141        cep95.approve(spender, token_id);
1142        cep95.transfer_from(owner, recipient, token_id);
1143
1144        // The stale approval must be gone now that ownership changed.
1145        assert_eq!(cep95.approved_for(token_id), None);
1146
1147        // And the old spender must not be able to steal the token from recipient.
1148        env.set_caller(spender);
1149        let result = cep95.try_transfer_from(recipient, attacker_dest, token_id);
1150        assert_eq!(result, Err(Error::NotAnOwnerOrApproved.into()));
1151        assert_eq!(cep95.owner_of(token_id), Some(recipient));
1152        assert_eq!(cep95.balance_of(recipient), U256::from(1));
1153        assert_eq!(cep95.balance_of(attacker_dest), U256::from(0));
1154    }
1155
1156    #[test]
1157    fn test_metadata() {
1158        let (env, mut cep95) = setup();
1159        let owner = env.caller();
1160
1161        let token_id = U256::from(1);
1162        let metadata = vec![("key".to_string(), "value".to_string())];
1163        cep95.mint(owner, token_id, metadata);
1164
1165        assert_eq!(
1166            cep95.token_metadata(token_id),
1167            vec![("key".to_string(), "value".to_string())]
1168        );
1169    }
1170
1171    #[test]
1172    fn test_transfer_by_operator() {
1173        let (env, mut cep95) = setup();
1174        let owner = env.caller();
1175        let recipient = env.get_account(10);
1176        let operator = env.get_account(11);
1177
1178        let token_id1 = U256::from(1);
1179        let metadata = vec![("key".to_string(), "value".to_string())];
1180        cep95.mint(owner, token_id1, metadata.clone());
1181
1182        cep95.approve_for_all(operator);
1183        let token_id2 = U256::from(2);
1184        cep95.mint(owner, token_id2, metadata);
1185
1186        env.set_caller(operator);
1187        cep95.transfer_from(owner, recipient, token_id1);
1188        cep95.transfer_from(owner, recipient, token_id2);
1189
1190        assert_eq!(cep95.balance_of(owner), U256::from(0));
1191        assert_eq!(cep95.balance_of(recipient), U256::from(2));
1192    }
1193
1194    #[test]
1195    fn test_update_metadata() {
1196        let (env, mut cep95) = setup();
1197        let owner = env.caller();
1198
1199        let token_id = U256::from(1);
1200        let metadata = vec![
1201            ("age".to_string(), "30".to_string()),
1202            ("name".to_string(), "Alice".to_string()),
1203        ];
1204        cep95.mint(owner, token_id, metadata);
1205
1206        let new_metadata = vec![("name".to_string(), "Bob".to_string())];
1207        cep95.update_metadata(token_id, new_metadata);
1208
1209        assert_eq!(
1210            cep95.token_metadata(token_id),
1211            vec![
1212                ("age".to_string(), "30".to_string()),
1213                ("name".to_string(), "Bob".to_string()),
1214            ]
1215        );
1216        assert!(env.emitted(&cep95, "MetadataUpdate"));
1217    }
1218
1219    #[test]
1220    fn test_set_metadata() {
1221        let (env, mut cep95) = setup();
1222        let owner = env.caller();
1223
1224        let token_id = U256::from(1);
1225        let metadata = vec![
1226            ("age".to_string(), "30".to_string()),
1227            ("name".to_string(), "Alice".to_string()),
1228        ];
1229        cep95.mint(owner, token_id, metadata);
1230
1231        let new_metadata = vec![("name".to_string(), "Bob".to_string())];
1232        cep95.set_metadata(token_id, new_metadata);
1233
1234        assert_eq!(
1235            cep95.token_metadata(token_id),
1236            vec![("name".to_string(), "Bob".to_string())]
1237        );
1238        assert!(env.emitted(&cep95, "MetadataUpdate"));
1239    }
1240}