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 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
127pub mod errors {
129 use odra::prelude::OdraError;
130
131 #[odra::odra_error]
133 pub enum Error {
134 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 let mut erc721_env = setup();
186
187 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
189
190 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 assert_eq!(erc721_env.token.owner_of(&U256::from(1)), erc721_env.alice);
199 }
200
201 #[test]
202 fn balance_of() {
203 let mut erc721_env = setup();
205
206 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
208
209 assert_eq!(
211 erc721_env.token.balance_of(&erc721_env.alice),
212 U256::from(1)
213 );
214
215 erc721_env.token.mint(&erc721_env.alice, &U256::from(2));
217
218 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 let mut erc721_env = setup();
229
230 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
232
233 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 let mut erc721_env = setup();
245
246 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
248
249 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 let erc721_env = setup();
257
258 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 let mut erc721_env = setup();
269
270 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
272
273 erc721_env.env.set_caller(erc721_env.alice);
275 erc721_env
276 .token
277 .approve(&Some(erc721_env.bob), &U256::from(1));
278
279 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 let mut erc721_env = setup();
290
291 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
293
294 erc721_env.env.set_caller(erc721_env.alice);
296 erc721_env
297 .token
298 .approve(&Some(erc721_env.bob), &U256::from(1));
299
300 erc721_env.env.set_caller(erc721_env.alice);
302 erc721_env.token.approve(&None, &U256::from(1));
303
304 assert_eq!(erc721_env.token.get_approved(&U256::from(1)), None);
306 }
307
308 #[test]
309 fn approve_non_existing_token() {
310 let mut erc721_env = setup();
312
313 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 let mut erc721_env = setup();
327
328 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
330
331 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 let mut erc721_env = setup();
345
346 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
348
349 erc721_env.env.set_caller(erc721_env.alice);
351 erc721_env.token.set_approval_for_all(&erc721_env.bob, true);
352
353 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 let mut erc721_env = setup();
363
364 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
366
367 erc721_env.env.set_caller(erc721_env.alice);
369 erc721_env.token.set_approval_for_all(&erc721_env.bob, true);
370
371 erc721_env.env.set_caller(erc721_env.alice);
373 erc721_env
374 .token
375 .set_approval_for_all(&erc721_env.bob, false);
376
377 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 let mut erc721_env = setup();
387
388 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
390
391 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 assert_eq!(erc721_env.token.owner_of(&U256::from(1)), erc721_env.bob);
399 }
400
401 #[test]
402 fn transfer_approved() {
403 let mut erc721_env = setup();
405
406 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
408
409 erc721_env.env.set_caller(erc721_env.alice);
411 erc721_env
412 .token
413 .approve(&Some(erc721_env.bob), &U256::from(1));
414
415 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 assert_eq!(erc721_env.token.owner_of(&U256::from(1)), erc721_env.carol);
423 }
424
425 #[test]
426 fn transfer_operator() {
427 let mut erc721_env = setup();
429
430 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
432
433 erc721_env.env.set_caller(erc721_env.alice);
435 erc721_env.token.set_approval_for_all(&erc721_env.bob, true);
436
437 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 assert_eq!(erc721_env.token.owner_of(&U256::from(1)), erc721_env.carol);
445 }
446
447 #[test]
448 fn transfer_not_owned() {
449 let mut erc721_env = setup();
451
452 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
454
455 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 let mut erc721_env = setup();
468
469 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 let mut erc721_env = setup();
481
482 assert!(erc721_env.token.address().is_contract());
483 assert!(!erc721_env.alice.is_contract());
484
485 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
487
488 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 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 let mut erc721_env = setup();
502 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 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
515
516 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 let mut erc721_env = setup();
535 let receiver = Erc721Receiver::deploy(&erc721_env.env, NoArgs);
537
538 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
540
541 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 assert_eq!(
549 erc721_env.token.owner_of(&U256::from(1)),
550 *receiver.address()
551 );
552 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 let mut erc721_env = setup();
568 let receiver = Erc721Receiver::deploy(&erc721_env.env, NoArgs);
570
571 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
573
574 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 assert_eq!(
585 erc721_env.token.owner_of(&U256::from(1)),
586 receiver.address().clone()
587 );
588 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 let mut erc721_env = setup();
604
605 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
607
608 erc721_env.token.burn(&U256::from(1));
610
611 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 let mut erc721_env = setup();
620
621 erc721_env.token.mint(&erc721_env.alice, &U256::from(1));
623
624 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 let mut erc721_env = setup();
634
635 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 let erc721 = setup();
644
645 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 let mut erc721_env = setup();
655
656 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}