odra_modules/
erc721_token.rs

1//! A pluggable Odra module implementing Erc721 token with metadata and ownership.
2
3use crate::access::Ownable;
4use crate::erc721::erc721_base::Erc721Base;
5use crate::erc721::events::Transfer;
6use crate::erc721::extensions::erc721_metadata::{Erc721Metadata, Erc721MetadataExtension};
7use crate::erc721::owned_erc721_with_metadata::OwnedErc721WithMetadata;
8use crate::erc721::Erc721;
9use crate::erc721_token::errors::Error;
10use odra::casper_types::{bytesrepr::Bytes, U256};
11use odra::prelude::*;
12
13/// The ERC721 token implementation.
14///
15/// It uses the [ERC721](Erc721Base) base implementation, the [ERC721 metadata](Erc721MetadataExtension) extension
16/// and the [Ownable] module.
17#[odra::module(events = [Transfer], errors = Error)]
18pub struct Erc721Token {
19    core: SubModule<Erc721Base>,
20    metadata: SubModule<Erc721MetadataExtension>,
21    ownable: SubModule<Ownable>
22}
23
24#[odra::module]
25impl OwnedErc721WithMetadata for Erc721Token {
26    fn init(&mut self, name: String, symbol: String, base_uri: String) {
27        self.metadata.init(name, symbol, base_uri);
28        let caller = self.env().caller();
29        self.ownable.init(caller);
30    }
31
32    fn name(&self) -> String {
33        self.metadata.name()
34    }
35
36    fn symbol(&self) -> String {
37        self.metadata.symbol()
38    }
39
40    fn base_uri(&self) -> String {
41        self.metadata.base_uri()
42    }
43
44    fn balance_of(&self, owner: &Address) -> U256 {
45        self.core.balance_of(owner)
46    }
47
48    fn owner_of(&self, token_id: &U256) -> Address {
49        self.core.owner_of(token_id)
50    }
51
52    fn safe_transfer_from(&mut self, from: &Address, to: &Address, token_id: &U256) {
53        self.core.safe_transfer_from(from, to, token_id);
54    }
55
56    fn safe_transfer_from_with_data(
57        &mut self,
58        from: &Address,
59        to: &Address,
60        token_id: &U256,
61        data: &Bytes
62    ) {
63        self.core
64            .safe_transfer_from_with_data(from, to, token_id, data);
65    }
66
67    fn transfer_from(&mut self, from: &Address, to: &Address, token_id: &U256) {
68        self.core.transfer_from(from, to, token_id);
69    }
70
71    fn approve(&mut self, approved: &Option<Address>, token_id: &U256) {
72        self.core.approve(approved, token_id);
73    }
74
75    fn set_approval_for_all(&mut self, operator: &Address, approved: bool) {
76        self.core.set_approval_for_all(operator, approved);
77    }
78
79    fn get_approved(&self, token_id: &U256) -> Option<Address> {
80        self.core.get_approved(token_id)
81    }
82
83    fn is_approved_for_all(&self, owner: &Address, operator: &Address) -> bool {
84        self.core.is_approved_for_all(owner, operator)
85    }
86
87    fn renounce_ownership(&mut self) {
88        self.ownable.renounce_ownership();
89    }
90
91    fn transfer_ownership(&mut self, new_owner: &Address) {
92        self.ownable.transfer_ownership(new_owner);
93    }
94
95    fn owner(&self) -> Address {
96        self.ownable.get_owner()
97    }
98
99    fn mint(&mut self, to: &Address, token_id: &U256) {
100        self.ownable.assert_owner(&self.env().caller());
101
102        if self.core.exists(token_id) {
103            self.env().revert(Error::TokenAlreadyExists)
104        }
105
106        self.core.balances.add(to, U256::from(1));
107        self.core.owners.set(token_id, Some(*to));
108    }
109
110    fn burn(&mut self, token_id: &U256) {
111        self.core.assert_exists(token_id);
112        self.ownable.assert_owner(&self.env().caller());
113
114        let owner = self.core.owner_of(token_id);
115        let balance = self.core.balance_of(&owner);
116        self.core.balances.set(&owner, balance - U256::from(1));
117        self.core.owners.set(token_id, None);
118        self.core.clear_approval(token_id);
119
120        self.env().emit_event(Transfer {
121            from: Some(owner),
122            to: None,
123            token_id: *token_id
124        });
125    }
126}
127
128/// Erc721 errors.
129pub mod errors {
130    use odra::prelude::OdraError;
131
132    /// Erc721 errors.
133    #[odra::odra_error]
134    pub enum Error {
135        /// Token with a given id already exists.
136        TokenAlreadyExists = 35_000
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::{Erc721Token, Erc721TokenHostRef, Erc721TokenInitArgs};
143    use crate::access::errors::Error as AccessError;
144    use crate::erc20::{Erc20, Erc20InitArgs};
145    use crate::erc721::errors::Error::{InvalidTokenId, NotAnOwnerOrApproved};
146    use crate::erc721::owned_erc721_with_metadata::OwnedErc721WithMetadata;
147    use crate::erc721_receiver::events::Received;
148    use crate::erc721_receiver::Erc721Receiver;
149    use crate::erc721_token::errors::Error::TokenAlreadyExists;
150    use odra::host::{Deployer, HostEnv, NoArgs};
151    use odra::prelude::*;
152    use odra::{casper_types::U256, VmError};
153    const NAME: &str = "PlascoinNFT";
154    const SYMBOL: &str = "PLSNFT";
155    const BASE_URI: &str = "https://plascoin.org/";
156
157    struct TokenEnv {
158        env: HostEnv,
159        token: Erc721TokenHostRef,
160        alice: Address,
161        bob: Address,
162        carol: Address
163    }
164
165    fn setup() -> TokenEnv {
166        let env = odra_test::env();
167        TokenEnv {
168            env: env.clone(),
169            token: Erc721Token::deploy(
170                &env,
171                Erc721TokenInitArgs {
172                    name: NAME.to_string(),
173                    symbol: SYMBOL.to_string(),
174                    base_uri: BASE_URI.to_string()
175                }
176            ),
177            alice: env.get_account(1),
178            bob: env.get_account(2),
179            carol: env.get_account(3)
180        }
181    }
182
183    #[test]
184    fn mints_nft() {
185        // When deploy a contract with the initial supply.
186        let mut erc721_env = setup();
187
188        // And mint a token to Alice.
189        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
190
191        // Then Alice has a balance of 1 and Bob has a balance of 0.
192        assert_eq!(
193            erc721_env.token.balance_of(&erc721_env.alice),
194            U256::from(1)
195        );
196        assert_eq!(erc721_env.token.balance_of(&erc721_env.bob), U256::from(0));
197
198        // And the owner of the token is Alice.
199        assert_eq!(erc721_env.token.owner_of(&U256::from(1)), erc721_env.alice);
200    }
201
202    #[test]
203    fn balance_of() {
204        // When deploy a contract with the initial supply.
205        let mut erc721_env = setup();
206
207        // And mint a token to Alice.
208        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
209
210        // Then Alice has a balance of 1 and Bob has a balance of 0.
211        assert_eq!(
212            erc721_env.token.balance_of(&erc721_env.alice),
213            U256::from(1)
214        );
215
216        // When we mint another token to Alice.
217        erc721_env.token.mint(&erc721_env.alice, &U256::from(2));
218
219        // Then Alice has a balance of 2.
220        assert_eq!(
221            erc721_env.token.balance_of(&erc721_env.alice),
222            U256::from(2)
223        );
224    }
225
226    #[test]
227    fn minting_same_id() {
228        // When deploy a contract with the initial supply.
229        let mut erc721_env = setup();
230
231        // And mint a token to Alice.
232        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
233
234        // Then minting the same token again throws an error.
235        let err = erc721_env
236            .token
237            .try_mint(&erc721_env.alice, &U256::from(1))
238            .unwrap_err();
239        assert_eq!(err, TokenAlreadyExists.into());
240    }
241
242    #[test]
243    fn finding_owner() {
244        // When deploy a contract with the initial supply.
245        let mut erc721_env = setup();
246
247        // And mint a token to Alice.
248        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
249
250        // Then the owner of the token is Alice.
251        assert_eq!(erc721_env.token.owner_of(&U256::from(1)), erc721_env.alice);
252    }
253
254    #[test]
255    fn finding_owner_of_non_existing_token() {
256        // When deploy a contract with the initial supply.
257        let erc721_env = setup();
258
259        // Then the owner of the token is Alice.
260        assert_eq!(
261            erc721_env.token.try_owner_of(&U256::from(1)).unwrap_err(),
262            InvalidTokenId.into()
263        );
264    }
265
266    #[test]
267    fn approve() {
268        // When deploy a contract with the initial supply.
269        let mut erc721_env = setup();
270
271        // And mint a token to Alice.
272        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
273
274        // And approve Bob to transfer the token.
275        erc721_env.env.set_caller(erc721_env.alice);
276        erc721_env
277            .token
278            .approve(&Some(erc721_env.bob), &U256::from(1));
279
280        // Then Bob is approved to transfer the token.
281        assert_eq!(
282            erc721_env.token.get_approved(&U256::from(1)),
283            Some(erc721_env.bob)
284        );
285    }
286
287    #[test]
288    fn cancel_approve() {
289        // When deploy a contract with the initial supply.
290        let mut erc721_env = setup();
291
292        // And mint a token to Alice.
293        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
294
295        // And approve Bob to transfer the token.
296        erc721_env.env.set_caller(erc721_env.alice);
297        erc721_env
298            .token
299            .approve(&Some(erc721_env.bob), &U256::from(1));
300
301        // And cancel the approval.
302        erc721_env.env.set_caller(erc721_env.alice);
303        erc721_env.token.approve(&None, &U256::from(1));
304
305        // Then Bob is not approved to transfer the token.
306        assert_eq!(erc721_env.token.get_approved(&U256::from(1)), None);
307    }
308
309    #[test]
310    fn approve_non_existing_token() {
311        // When deploy a contract with the initial supply.
312        let mut erc721_env = setup();
313
314        // Then approving a non existing token throws an error.
315        assert_eq!(
316            erc721_env
317                .token
318                .try_approve(&Some(erc721_env.bob), &U256::from(1))
319                .unwrap_err(),
320            InvalidTokenId.into()
321        );
322    }
323
324    #[test]
325    fn approve_not_owned_token() {
326        // When deploy a contract with the initial supply.
327        let mut erc721_env = setup();
328
329        // And mint a token to Alice.
330        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
331
332        // Then approving a token that is not owned by the caller throws an error.
333        assert_eq!(
334            erc721_env
335                .token
336                .try_approve(&Some(erc721_env.bob), &U256::from(1))
337                .unwrap_err(),
338            NotAnOwnerOrApproved.into()
339        );
340    }
341
342    #[test]
343    fn set_an_operator() {
344        // When deploy a contract with the initial supply.
345        let mut erc721_env = setup();
346
347        // And mint a token to Alice.
348        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
349
350        // And set Bob as an operator.
351        erc721_env.env.set_caller(erc721_env.alice);
352        erc721_env.token.set_approval_for_all(&erc721_env.bob, true);
353
354        // Then Bob is an operator.
355        assert!(erc721_env
356            .token
357            .is_approved_for_all(&erc721_env.alice, &erc721_env.bob));
358    }
359
360    #[test]
361    fn cancel_an_operator() {
362        // When deploy a contract with the initial supply.
363        let mut erc721_env = setup();
364
365        // And mint a token to Alice.
366        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
367
368        // And set Bob as an operator.
369        erc721_env.env.set_caller(erc721_env.alice);
370        erc721_env.token.set_approval_for_all(&erc721_env.bob, true);
371
372        // And cancel Bob as an operator.
373        erc721_env.env.set_caller(erc721_env.alice);
374        erc721_env
375            .token
376            .set_approval_for_all(&erc721_env.bob, false);
377
378        // Then Bob is not an operator.
379        assert!(!erc721_env
380            .token
381            .is_approved_for_all(&erc721_env.alice, &erc721_env.bob));
382    }
383
384    #[test]
385    fn transfer_nft() {
386        // When deploy a contract with the initial supply.
387        let mut erc721_env = setup();
388
389        // And mint a token to Alice.
390        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
391
392        // And transfer the token to Bob.
393        erc721_env.env.set_caller(erc721_env.alice);
394        erc721_env
395            .token
396            .transfer_from(&erc721_env.alice, &erc721_env.bob, &U256::from(1));
397
398        // Then the owner of the token is Bob.
399        assert_eq!(erc721_env.token.owner_of(&U256::from(1)), erc721_env.bob);
400    }
401
402    #[test]
403    fn transfer_approved() {
404        // When deploy a contract with the initial supply.
405        let mut erc721_env = setup();
406
407        // And mint a token to Alice.
408        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
409
410        // And approve Bob to transfer the token.
411        erc721_env.env.set_caller(erc721_env.alice);
412        erc721_env
413            .token
414            .approve(&Some(erc721_env.bob), &U256::from(1));
415
416        // And transfer the token to Carol.
417        erc721_env.env.set_caller(erc721_env.bob);
418        erc721_env
419            .token
420            .transfer_from(&erc721_env.alice, &erc721_env.carol, &U256::from(1));
421
422        // Then the owner of the token is Carol.
423        assert_eq!(erc721_env.token.owner_of(&U256::from(1)), erc721_env.carol);
424    }
425
426    #[test]
427    fn transfer_operator() {
428        // When deploy a contract with the initial supply.
429        let mut erc721_env = setup();
430
431        // And mint a token to Alice.
432        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
433
434        // And set Bob as an operator.
435        erc721_env.env.set_caller(erc721_env.alice);
436        erc721_env.token.set_approval_for_all(&erc721_env.bob, true);
437
438        // And transfer the token to Carol.
439        erc721_env.env.set_caller(erc721_env.bob);
440        erc721_env
441            .token
442            .transfer_from(&erc721_env.alice, &erc721_env.carol, &U256::from(1));
443
444        // Then the owner of the token is Carol.
445        assert_eq!(erc721_env.token.owner_of(&U256::from(1)), erc721_env.carol);
446    }
447
448    #[test]
449    fn transfer_not_owned() {
450        // When deploy a contract with the initial supply.
451        let mut erc721_env = setup();
452
453        // And mint a token to Alice.
454        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
455
456        // Then transferring a token that is not owned by the caller throws an error.
457        erc721_env.env.set_caller(erc721_env.bob);
458        let err = erc721_env
459            .token
460            .try_transfer_from(&erc721_env.alice, &erc721_env.carol, &U256::from(1))
461            .unwrap_err();
462        assert_eq!(err, NotAnOwnerOrApproved.into());
463    }
464
465    #[test]
466    fn transferring_invalid_nft() {
467        // When deploy a contract with the initial supply.
468        let mut erc721_env = setup();
469
470        // Then transferring a token that does not exist throws an error.
471        let err = erc721_env
472            .token
473            .try_transfer_from(&erc721_env.alice, &erc721_env.carol, &U256::from(1))
474            .unwrap_err();
475        assert_eq!(err, InvalidTokenId.into());
476    }
477
478    #[test]
479    fn safe_transfer() {
480        // When deploy a contract with the initial supply.
481        let mut erc721_env = setup();
482
483        assert!(erc721_env.token.address().is_contract());
484        assert!(!erc721_env.alice.is_contract());
485
486        // And mint a token to Alice.
487        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
488
489        // And safe transfer the token to Bob.
490        erc721_env.env.set_caller(erc721_env.alice);
491        erc721_env
492            .token
493            .safe_transfer_from(&erc721_env.alice, &erc721_env.bob, &U256::from(1));
494
495        // Then the owner of the token is Bob.
496        assert_eq!(erc721_env.token.owner_of(&U256::from(1)), erc721_env.bob);
497    }
498
499    #[test]
500    fn safe_transfer_to_contract_which_does_not_support_nft() {
501        // When deploy a contract with the initial supply
502        let mut erc721_env = setup();
503        // And another contract which does not support nfts
504        let erc20 = Erc20::deploy(
505            &erc721_env.env,
506            Erc20InitArgs {
507                name: "PLASCOIN".to_string(),
508                symbol: "PLS".to_string(),
509                decimals: 10,
510                initial_supply: None
511            }
512        );
513
514        // And mint a token to Alice.
515        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
516
517        // Then safe transfer the token to the contract which does not support nfts throws an error.
518        erc721_env.env.set_caller(erc721_env.alice);
519
520        assert_eq!(
521            Err(OdraError::VmError(VmError::NoSuchMethod(
522                "on_erc721_received".to_string()
523            ))),
524            erc721_env.token.try_safe_transfer_from(
525                &erc721_env.alice,
526                &erc20.address(),
527                &U256::from(1)
528            )
529        );
530    }
531
532    #[test]
533    fn safe_transfer_to_contract_which_supports_nft() {
534        // When deploy a contract with the initial supply
535        let mut erc721_env = setup();
536        // And another contract which does not support nfts
537        let receiver = Erc721Receiver::deploy(&erc721_env.env, NoArgs);
538
539        // And mint a token to Alice.
540        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
541
542        // And transfer the token to the contract which does support nfts
543        erc721_env.env.set_caller(erc721_env.alice);
544        erc721_env
545            .token
546            .safe_transfer_from(&erc721_env.alice, &receiver.address(), &U256::from(1));
547
548        // Then the owner of the token is the contract
549        assert_eq!(
550            erc721_env.token.owner_of(&U256::from(1)),
551            receiver.address()
552        );
553        // And the receiver contract is aware of the transfer
554        erc721_env.env.emitted_event(
555            &receiver,
556            Received {
557                operator: Some(erc721_env.alice),
558                from: Some(erc721_env.alice),
559                token_id: U256::from(1),
560                data: None
561            }
562        );
563    }
564
565    #[test]
566    fn safe_transfer_to_contract_with_data() {
567        // When deploy a contract with the initial supply
568        let mut erc721_env = setup();
569        // And another contract which does not support nfts
570        let receiver = Erc721Receiver::deploy(&erc721_env.env, NoArgs);
571
572        // And mint a token to Alice.
573        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
574
575        // And transfer the token to the contract which does support nfts
576        erc721_env.env.set_caller(erc721_env.alice);
577        erc721_env.token.safe_transfer_from_with_data(
578            &erc721_env.alice,
579            &receiver.address(),
580            &U256::from(1),
581            &b"data".to_vec().into()
582        );
583
584        // Then the owner of the token is the contract
585        assert_eq!(
586            erc721_env.token.owner_of(&U256::from(1)),
587            receiver.address()
588        );
589        // And the receiver contract is aware of the transfer
590        erc721_env.env.emitted_event(
591            &receiver,
592            Received {
593                operator: Some(erc721_env.alice),
594                from: Some(erc721_env.alice),
595                token_id: U256::from(1),
596                data: Some(b"data".to_vec().into())
597            }
598        );
599    }
600
601    #[test]
602    fn burn() {
603        // When deploy a contract with the initial supply.
604        let mut erc721_env = setup();
605
606        // And mint a token to Alice.
607        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
608
609        // And burn the token.
610        erc721_env.token.burn(&U256::from(1));
611
612        // Then the owner of throws an error.
613        let err = erc721_env.token.try_owner_of(&U256::from(1)).unwrap_err();
614        assert_eq!(err, InvalidTokenId.into());
615    }
616
617    #[test]
618    fn burn_error() {
619        // When deploy a contract with the initial supply.
620        let mut erc721_env = setup();
621
622        // And mint a token to Alice.
623        erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
624
625        // Then burn the token as Alice errors out
626        erc721_env.env.set_caller(erc721_env.alice);
627        let err = erc721_env.token.try_burn(&U256::from(1)).unwrap_err();
628        assert_eq!(err, AccessError::CallerNotTheOwner.into());
629    }
630
631    #[test]
632    fn burn_non_existing_nft() {
633        // When deploy a contract with the initial supply.
634        let mut erc721_env = setup();
635
636        // Then burning a token that does not exist throws an error.
637        let err = erc721_env.token.try_burn(&U256::from(1)).unwrap_err();
638        assert_eq!(err, InvalidTokenId.into());
639    }
640
641    #[test]
642    fn metadata() {
643        // When deploy a contract with the initial supply.
644        let erc721 = setup();
645
646        // Then the contract has the metadata set.
647        assert_eq!(erc721.token.symbol(), SYMBOL.to_string());
648        assert_eq!(erc721.token.name(), NAME.to_string());
649        assert_eq!(erc721.token.base_uri(), BASE_URI.to_string());
650    }
651
652    #[test]
653    fn minting_by_not_an_owner() {
654        // When deploy a contract with the initial supply.
655        let mut erc721_env = setup();
656
657        // Then minting a token by not an owner throws an error.
658        erc721_env.env.set_caller(erc721_env.bob);
659        let err = erc721_env
660            .token
661            .try_mint(&erc721_env.alice, &U256::from(1))
662            .unwrap_err();
663        assert_eq!(err, AccessError::CallerNotTheOwner.into());
664    }
665}