1use 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#[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
128pub mod errors {
130 use odra::prelude::OdraError;
131
132 #[odra::odra_error]
134 pub enum Error {
135 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 let mut erc721_env = setup();
187
188 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
190
191 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 assert_eq!(erc721_env.token.owner_of(&U256::from(1)), erc721_env.alice);
200 }
201
202 #[test]
203 fn balance_of() {
204 let mut erc721_env = setup();
206
207 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
209
210 assert_eq!(
212 erc721_env.token.balance_of(&erc721_env.alice),
213 U256::from(1)
214 );
215
216 erc721_env.token.mint(&erc721_env.alice, &U256::from(2));
218
219 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 let mut erc721_env = setup();
230
231 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
233
234 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 let mut erc721_env = setup();
246
247 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
249
250 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 let erc721_env = setup();
258
259 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 let mut erc721_env = setup();
270
271 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
273
274 erc721_env.env.set_caller(erc721_env.alice);
276 erc721_env
277 .token
278 .approve(&Some(erc721_env.bob), &U256::from(1));
279
280 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 let mut erc721_env = setup();
291
292 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
294
295 erc721_env.env.set_caller(erc721_env.alice);
297 erc721_env
298 .token
299 .approve(&Some(erc721_env.bob), &U256::from(1));
300
301 erc721_env.env.set_caller(erc721_env.alice);
303 erc721_env.token.approve(&None, &U256::from(1));
304
305 assert_eq!(erc721_env.token.get_approved(&U256::from(1)), None);
307 }
308
309 #[test]
310 fn approve_non_existing_token() {
311 let mut erc721_env = setup();
313
314 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 let mut erc721_env = setup();
328
329 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
331
332 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 let mut erc721_env = setup();
346
347 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
349
350 erc721_env.env.set_caller(erc721_env.alice);
352 erc721_env.token.set_approval_for_all(&erc721_env.bob, true);
353
354 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 let mut erc721_env = setup();
364
365 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
367
368 erc721_env.env.set_caller(erc721_env.alice);
370 erc721_env.token.set_approval_for_all(&erc721_env.bob, true);
371
372 erc721_env.env.set_caller(erc721_env.alice);
374 erc721_env
375 .token
376 .set_approval_for_all(&erc721_env.bob, false);
377
378 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 let mut erc721_env = setup();
388
389 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
391
392 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 assert_eq!(erc721_env.token.owner_of(&U256::from(1)), erc721_env.bob);
400 }
401
402 #[test]
403 fn transfer_approved() {
404 let mut erc721_env = setup();
406
407 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
409
410 erc721_env.env.set_caller(erc721_env.alice);
412 erc721_env
413 .token
414 .approve(&Some(erc721_env.bob), &U256::from(1));
415
416 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 assert_eq!(erc721_env.token.owner_of(&U256::from(1)), erc721_env.carol);
424 }
425
426 #[test]
427 fn transfer_operator() {
428 let mut erc721_env = setup();
430
431 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
433
434 erc721_env.env.set_caller(erc721_env.alice);
436 erc721_env.token.set_approval_for_all(&erc721_env.bob, true);
437
438 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 assert_eq!(erc721_env.token.owner_of(&U256::from(1)), erc721_env.carol);
446 }
447
448 #[test]
449 fn transfer_not_owned() {
450 let mut erc721_env = setup();
452
453 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
455
456 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 let mut erc721_env = setup();
469
470 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 let mut erc721_env = setup();
482
483 assert!(erc721_env.token.address().is_contract());
484 assert!(!erc721_env.alice.is_contract());
485
486 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
488
489 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 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 let mut erc721_env = setup();
503 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 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
516
517 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 let mut erc721_env = setup();
536 let receiver = Erc721Receiver::deploy(&erc721_env.env, NoArgs);
538
539 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
541
542 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 assert_eq!(
550 erc721_env.token.owner_of(&U256::from(1)),
551 receiver.address()
552 );
553 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 let mut erc721_env = setup();
569 let receiver = Erc721Receiver::deploy(&erc721_env.env, NoArgs);
571
572 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
574
575 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 assert_eq!(
586 erc721_env.token.owner_of(&U256::from(1)),
587 receiver.address()
588 );
589 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 let mut erc721_env = setup();
605
606 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
608
609 erc721_env.token.burn(&U256::from(1));
611
612 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 let mut erc721_env = setup();
621
622 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
624
625 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 let mut erc721_env = setup();
635
636 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 let erc721 = setup();
645
646 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 let mut erc721_env = setup();
656
657 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}