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