1#![allow(clippy::too_many_arguments)]
2#![allow(missing_docs)]
3use crate::{cep18::events::Transfer, cep18_token::Cep18, eip712};
55use casper_eip_712::DomainSeparator;
56use odra::{
57 casper_types::{bytesrepr::Bytes, PublicKey, U256},
58 named_keys::{compound_key_value_storage, single_value_storage},
59 prelude::*
60};
61
62const TRANSFER_WITH_AUTHORIZATION_TYPEHASH: [u8; 32] = [
64 0x7c, 0x7c, 0x6c, 0xdb, 0x67, 0xa1, 0x87, 0x43, 0xf4, 0x9e, 0xc6, 0xfa, 0x9b, 0x35, 0xf5, 0x0d,
65 0x52, 0xed, 0x05, 0xcb, 0xed, 0x4c, 0xc5, 0x92, 0xe1, 0x3b, 0x44, 0x50, 0x1c, 0x1a, 0x22, 0x67
66];
67
68const RECEIVE_WITH_AUTHORIZATION_TYPEHASH: [u8; 32] = [
70 0xd0, 0x99, 0xcc, 0x98, 0xef, 0x71, 0x10, 0x7a, 0x61, 0x6c, 0x4f, 0x0f, 0x94, 0x1f, 0x04, 0xc3,
71 0x22, 0xd8, 0xe2, 0x54, 0xfe, 0x26, 0xb3, 0xc6, 0x66, 0x8d, 0xb8, 0x7a, 0xae, 0x41, 0x3d, 0xe8
72];
73
74const CANCEL_AUTHORIZATION_TYPEHASH: [u8; 32] = [
76 0x15, 0x8b, 0x0a, 0x9e, 0xdf, 0x7a, 0x82, 0x8a, 0xad, 0x02, 0xf6, 0x3c, 0xd5, 0x15, 0xc6, 0x8e,
77 0xf2, 0xf5, 0x0b, 0xa8, 0x07, 0x39, 0x6f, 0x6d, 0x12, 0x84, 0x28, 0x33, 0xa1, 0x59, 0x74, 0x29
78];
79
80#[odra::event]
85pub struct AuthorizationUsed {
86 authorizer: Address,
87 nonce: Bytes
88}
89
90#[odra::event]
94pub struct AuthorizationCanceled {
95 authorizer: Address,
96 nonce: Bytes
97}
98
99#[odra::odra_error]
101pub enum Error {
102 NonceAlreadyUsed = 37_000,
105 AuthorizationExpired = 37_001,
108 AuthorizationNotYetValid = 37_002,
111 InvalidSignature = 37_003,
115 InvalidPublicKey = 37_004,
119 InvalidCaller = 37_005,
122 AuthorizationUsed = 37_006,
125 InvalidNonceLength = 37_007
131}
132
133const CHAIN_NAME_KEY: &str = "chain_name";
135const USED_NONCES_KEY: &str = "used_nonces";
136
137const DOMAIN_VERSION: &str = "1";
139
140single_value_storage!(
141 CEP3009ChainNameStorage,
142 String,
143 CHAIN_NAME_KEY,
144 ExecutionError::KeyNotFound
145);
146
147compound_key_value_storage!(
148 CEP3009UsedNoncesStorage,
149 USED_NONCES_KEY,
150 Address,
151 Bytes,
152 bool
153);
154
155#[odra::module(events = [AuthorizationUsed, AuthorizationCanceled, Transfer], errors = Error)]
161pub struct CEP3009 {
162 used_nonces: SubModule<CEP3009UsedNoncesStorage>,
165 chain_name: SubModule<CEP3009ChainNameStorage>,
167 token: SubModule<Cep18>
169}
170
171#[odra::module]
172impl CEP3009 {
173 pub fn init(&mut self, chain_name: String) {
176 self.chain_name.set(chain_name);
177 self.used_nonces.init();
178 }
179
180 pub fn authorization_state(&self, authorizer: Address, nonce: Bytes) -> bool {
184 self.used_nonces.get_or_default(&authorizer, &nonce)
185 }
186
187 pub fn transfer_with_authorization(
198 &mut self,
199 from: Address,
200 to: Address,
201 amount: U256,
202 valid_after: u64,
203 valid_before: u64,
204 nonce: Bytes,
205 public_key: PublicKey,
206 signature: Bytes
207 ) {
208 self.raw_transfer_with_authorization(
209 TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
210 from,
211 to,
212 amount,
213 valid_after,
214 valid_before,
215 nonce,
216 public_key,
217 signature
218 );
219 }
220
221 pub fn receive_with_authorization(
230 &mut self,
231 from: Address,
232 to: Address,
233 amount: U256,
234 valid_after: u64,
235 valid_before: u64,
236 nonce: Bytes,
237 public_key: PublicKey,
238 signature: Bytes
239 ) {
240 let caller = self.env().caller();
241 if caller != to {
242 self.env().revert(Error::InvalidCaller);
243 }
244
245 self.raw_transfer_with_authorization(
246 RECEIVE_WITH_AUTHORIZATION_TYPEHASH,
247 from,
248 to,
249 amount,
250 valid_after,
251 valid_before,
252 nonce,
253 public_key,
254 signature
255 );
256 }
257
258 pub fn cancel_authorization(
272 &mut self,
273 authorizer: Address,
274 nonce: Bytes,
275 public_key: PublicKey,
276 signature: Bytes
277 ) {
278 let Ok(nonce_bytes) = <[u8; 32]>::try_from(nonce.as_slice()) else {
279 self.env().revert(Error::InvalidNonceLength)
280 };
281
282 if self.authorization_state(authorizer, nonce.clone()) {
283 self.env().revert(Error::AuthorizationUsed);
284 }
285
286 if Address::from(public_key.clone()) != authorizer {
287 self.env().revert(Error::InvalidPublicKey);
288 }
289
290 let message = self.build_cancel_message(authorizer, nonce_bytes);
291 if !self
292 .env()
293 .verify_signature(&message, &signature, &public_key)
294 {
295 self.env().revert(Error::InvalidSignature);
296 }
297
298 self.used_nonces.set(&authorizer, &nonce, true);
299 self.env()
300 .emit_event(AuthorizationCanceled { authorizer, nonce });
301 }
302}
303
304impl CEP3009 {
305 fn raw_transfer_with_authorization(
310 &mut self,
311 typehash: [u8; 32],
312 from: Address,
313 to: Address,
314 amount: U256,
315 valid_after: u64,
316 valid_before: u64,
317 nonce: Bytes,
318 public_key: PublicKey,
319 signature: Bytes
320 ) {
321 let Ok(nonce_bytes) = <[u8; 32]>::try_from(nonce.as_slice()) else {
324 self.env().revert(Error::InvalidNonceLength)
325 };
326
327 if self.used_nonces.get_or_default(&from, &nonce) {
329 self.env().revert(Error::NonceAlreadyUsed);
330 }
331
332 let now_secs = self.env().get_block_time_secs();
334
335 if now_secs <= valid_after {
337 self.env().revert(Error::AuthorizationNotYetValid);
338 }
339
340 if now_secs >= valid_before {
342 self.env().revert(Error::AuthorizationExpired);
343 }
344
345 let derived_address = Address::from(public_key.clone());
347 if derived_address != from {
348 self.env().revert(Error::InvalidPublicKey);
349 }
350
351 let message = self.build_authorization_message(
353 typehash,
354 from,
355 to,
356 &amount,
357 valid_after,
358 valid_before,
359 nonce_bytes
360 );
361
362 if !self
363 .env()
364 .verify_signature(&message, &signature, &public_key)
365 {
366 self.env().revert(Error::InvalidSignature);
367 }
368
369 self.used_nonces.set(&from, &nonce, true);
371
372 self.env().emit_event(AuthorizationUsed {
374 authorizer: from,
375 nonce
376 });
377
378 self.token.raw_transfer(&from, &to, &amount);
380
381 self.env().emit_event(Transfer {
383 sender: from,
384 recipient: to,
385 amount
386 });
387 }
388
389 fn build_authorization_message(
393 &self,
394 typehash: [u8; 32],
395 from: Address,
396 to: Address,
397 amount: &U256,
398 valid_after: u64,
399 valid_before: u64,
400 nonce: [u8; 32]
401 ) -> Bytes {
402 let mut value_bytes = [0u8; 32];
403 amount.to_big_endian(&mut value_bytes);
404
405 let mut encoded_data = Vec::with_capacity(6 * 32);
406 encoded_data.extend(eip712::encode_address(from));
407 encoded_data.extend(eip712::encode_address(to));
408 encoded_data.extend(casper_eip_712::encode_uint256(value_bytes));
409 encoded_data.extend(casper_eip_712::encode_uint64(valid_after));
410 encoded_data.extend(casper_eip_712::encode_uint64(valid_before));
411 encoded_data.extend(casper_eip_712::encode_bytes32(nonce));
412 let domain = self.domain_separator();
413
414 Bytes::from(crate::eip712::hash_typed_data(domain, typehash, encoded_data).to_vec())
415 }
416
417 fn build_cancel_message(&self, authorizer: Address, nonce: [u8; 32]) -> Bytes {
420 let mut encoded_data = Vec::with_capacity(64);
421 encoded_data.extend(eip712::encode_address(authorizer));
422 encoded_data.extend(casper_eip_712::encode_bytes32(nonce));
423 let domain = self.domain_separator();
424
425 Bytes::from(
426 eip712::hash_typed_data(domain, CANCEL_AUTHORIZATION_TYPEHASH, encoded_data).to_vec()
427 )
428 }
429
430 fn domain_separator(&self) -> DomainSeparator {
434 let self_address = self.env().self_address();
435 let name = self.token.name();
436 let chain_name = self.chain_name.get();
437 eip712::domain_separator(&name, DOMAIN_VERSION, chain_name, self_address)
438 }
439}
440
441#[odra::module]
443pub struct CEP3009Wrapper {
444 cep3009: SubModule<CEP3009>,
445 token: SubModule<Cep18>
446}
447
448#[odra::module]
449impl CEP3009Wrapper {
450 pub fn init(
453 &mut self,
454 chain_name: String,
455 symbol: String,
456 name: String,
457 decimals: u8,
458 initial_supply: U256
459 ) {
460 self.cep3009.init(chain_name);
461 self.token.init(symbol, name, decimals, initial_supply);
462 }
463
464 delegate! {
465 to self.cep3009 {
466 fn authorization_state(&self, authorizer: Address, nonce: Bytes) -> bool;
467 fn transfer_with_authorization(
468 &mut self,
469 from: Address,
470 to: Address,
471 amount: U256,
472 valid_after: u64,
473 valid_before: u64,
474 nonce: Bytes,
475 public_key: PublicKey,
476 signature: Bytes
477 );
478 fn receive_with_authorization(
479 &mut self,
480 from: Address,
481 to: Address,
482 amount: U256,
483 valid_after: u64,
484 valid_before: u64,
485 nonce: Bytes,
486 public_key: PublicKey,
487 signature: Bytes
488 );
489 fn cancel_authorization(
490 &mut self,
491 authorizer: Address,
492 nonce: Bytes,
493 public_key: PublicKey,
494 signature: Bytes
495 );
496 }
497
498 to self.token {
499 fn balance_of(&self, address: &Address) -> U256;
500 }
501 }
502}
503
504#[cfg(test)]
505mod tests {
506 use super::*;
507 use odra::host::{Deployer, HostEnv};
508
509 const TOKEN_NAME: &str = "Test Token";
510 const TOKEN_SYMBOL: &str = "TEST";
511 const TOKEN_DECIMALS: u8 = 8;
512 const INITIAL_SUPPLY: u64 = 1_000_000;
513 const CHAIN_NAME: &str = "casper:casper";
514
515 struct Setup {
516 env: HostEnv,
517 wrapper: CEP3009WrapperHostRef,
518 alice: Address,
519 bob: Address,
520 charlie: Address,
521 alice_pubkey: PublicKey
522 }
523
524 fn setup() -> Setup {
527 let env = odra_test::env();
528 let alice = env.get_account(0);
529 let bob = env.get_account(1);
530 let charlie = env.get_account(2);
531 let alice_pubkey = env.public_key(&alice);
532
533 let wrapper = CEP3009Wrapper::deploy(
534 &env,
535 CEP3009WrapperInitArgs {
536 chain_name: CHAIN_NAME.to_string(),
537 symbol: TOKEN_SYMBOL.to_string(),
538 name: TOKEN_NAME.to_string(),
539 decimals: TOKEN_DECIMALS,
540 initial_supply: INITIAL_SUPPLY.into()
541 }
542 );
543
544 env.advance_block_time(1_000);
545
546 Setup {
547 env,
548 wrapper,
549 alice,
550 bob,
551 charlie,
552 alice_pubkey
553 }
554 }
555
556 fn fresh_nonce(seed: u8) -> Bytes {
557 Bytes::from(vec![seed; 32])
558 }
559
560 #[test]
561 fn transfer_with_authorization_happy_path() {
562 let Setup {
563 env,
564 mut wrapper,
565 alice,
566 bob,
567 charlie,
568 alice_pubkey
569 } = setup();
570
571 assert_eq!(wrapper.balance_of(&alice), INITIAL_SUPPLY.into());
572 assert_eq!(wrapper.balance_of(&bob), U256::zero());
573
574 let amount: U256 = 100u64.into();
575 let valid_after: u64 = 0;
576 let valid_before: u64 = u64::MAX;
577 let nonce = fresh_nonce(1);
578
579 let signature = sign_transfer_authorization(
580 &env,
581 &alice,
582 wrapper.address(),
583 TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
584 alice,
585 bob,
586 amount,
587 valid_after,
588 valid_before,
589 &nonce
590 );
591
592 env.set_caller(charlie);
594 wrapper.transfer_with_authorization(
595 alice,
596 bob,
597 amount,
598 valid_after,
599 valid_before,
600 nonce.clone(),
601 alice_pubkey,
602 signature
603 );
604
605 assert_eq!(
606 wrapper.balance_of(&alice),
607 U256::from(INITIAL_SUPPLY) - amount
608 );
609 assert_eq!(wrapper.balance_of(&bob), amount);
610 assert!(wrapper.authorization_state(alice, nonce.clone()));
611 assert!(env.emitted_event(
612 &wrapper,
613 AuthorizationUsed {
614 authorizer: alice,
615 nonce
616 }
617 ));
618 }
619
620 #[test]
621 fn transfer_with_authorization_replay_protection() {
622 let Setup {
623 env,
624 mut wrapper,
625 alice,
626 bob,
627 charlie,
628 alice_pubkey
629 } = setup();
630
631 let amount: U256 = 100u64.into();
632 let valid_after: u64 = 0;
633 let valid_before: u64 = u64::MAX;
634 let nonce = fresh_nonce(2);
635
636 let signature = sign_transfer_authorization(
637 &env,
638 &alice,
639 wrapper.address(),
640 TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
641 alice,
642 bob,
643 amount,
644 valid_after,
645 valid_before,
646 &nonce
647 );
648
649 env.set_caller(charlie);
650 wrapper.transfer_with_authorization(
651 alice,
652 bob,
653 amount,
654 valid_after,
655 valid_before,
656 nonce.clone(),
657 alice_pubkey.clone(),
658 signature.clone()
659 );
660
661 assert_eq!(
663 wrapper.try_transfer_with_authorization(
664 alice,
665 bob,
666 amount,
667 valid_after,
668 valid_before,
669 nonce,
670 alice_pubkey,
671 signature
672 ),
673 Err(Error::NonceAlreadyUsed.into())
674 );
675 }
676
677 #[test]
678 fn transfer_with_authorization_not_yet_valid() {
679 let Setup {
680 env,
681 mut wrapper,
682 alice,
683 bob,
684 charlie,
685 alice_pubkey
686 } = setup();
687
688 let valid_after: u64 = 60;
690 let valid_before: u64 = u64::MAX;
691 let amount: U256 = 100u64.into();
692 let nonce = fresh_nonce(3);
693
694 let signature = sign_transfer_authorization(
695 &env,
696 &alice,
697 wrapper.address(),
698 TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
699 alice,
700 bob,
701 amount,
702 valid_after,
703 valid_before,
704 &nonce
705 );
706
707 env.set_caller(charlie);
708 assert_eq!(
709 wrapper.try_transfer_with_authorization(
710 alice,
711 bob,
712 amount,
713 valid_after,
714 valid_before,
715 nonce,
716 alice_pubkey,
717 signature
718 ),
719 Err(Error::AuthorizationNotYetValid.into())
720 );
721 }
722
723 #[test]
724 fn transfer_with_authorization_expired() {
725 let Setup {
726 env,
727 mut wrapper,
728 alice,
729 bob,
730 charlie,
731 alice_pubkey
732 } = setup();
733
734 env.advance_block_time(60_000);
736
737 let valid_after: u64 = 0;
738 let valid_before: u64 = 10;
739 let amount: U256 = 100u64.into();
740 let nonce = fresh_nonce(4);
741
742 let signature = sign_transfer_authorization(
743 &env,
744 &alice,
745 wrapper.address(),
746 TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
747 alice,
748 bob,
749 amount,
750 valid_after,
751 valid_before,
752 &nonce
753 );
754
755 env.set_caller(charlie);
756 assert_eq!(
757 wrapper.try_transfer_with_authorization(
758 alice,
759 bob,
760 amount,
761 valid_after,
762 valid_before,
763 nonce,
764 alice_pubkey,
765 signature
766 ),
767 Err(Error::AuthorizationExpired.into())
768 );
769 }
770
771 #[test]
772 fn transfer_with_authorization_invalid_signature() {
773 let Setup {
774 env,
775 mut wrapper,
776 alice,
777 bob,
778 charlie,
779 alice_pubkey
780 } = setup();
781
782 let valid_after: u64 = 0;
783 let valid_before: u64 = u64::MAX;
784 let nonce = fresh_nonce(5);
785
786 let signature = sign_transfer_authorization(
788 &env,
789 &alice,
790 wrapper.address(),
791 TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
792 alice,
793 bob,
794 U256::from(100u64),
795 valid_after,
796 valid_before,
797 &nonce
798 );
799
800 env.set_caller(charlie);
803 assert_eq!(
804 wrapper.try_transfer_with_authorization(
805 alice,
806 bob,
807 U256::from(200u64),
808 valid_after,
809 valid_before,
810 nonce,
811 alice_pubkey,
812 signature
813 ),
814 Err(Error::InvalidSignature.into())
815 );
816 }
817
818 #[test]
819 fn transfer_with_authorization_public_key_mismatch() {
820 let Setup {
821 env,
822 mut wrapper,
823 alice,
824 bob,
825 charlie,
826 ..
827 } = setup();
828
829 let valid_after: u64 = 0;
830 let valid_before: u64 = u64::MAX;
831 let amount: U256 = 100u64.into();
832 let nonce = fresh_nonce(6);
833
834 let bob_pubkey = env.public_key(&bob);
836 let signature = sign_transfer_authorization(
837 &env,
838 &bob,
839 wrapper.address(),
840 TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
841 alice,
842 bob,
843 amount,
844 valid_after,
845 valid_before,
846 &nonce
847 );
848
849 env.set_caller(charlie);
850 assert_eq!(
851 wrapper.try_transfer_with_authorization(
852 alice,
853 bob,
854 amount,
855 valid_after,
856 valid_before,
857 nonce,
858 bob_pubkey,
859 signature
860 ),
861 Err(Error::InvalidPublicKey.into())
862 );
863 }
864
865 #[test]
866 fn receive_with_authorization_happy_path() {
867 let Setup {
868 env,
869 mut wrapper,
870 alice,
871 bob,
872 alice_pubkey,
873 ..
874 } = setup();
875
876 let amount: U256 = 250u64.into();
877 let valid_after: u64 = 0;
878 let valid_before: u64 = u64::MAX;
879 let nonce = fresh_nonce(7);
880
881 let signature = sign_transfer_authorization(
882 &env,
883 &alice,
884 wrapper.address(),
885 RECEIVE_WITH_AUTHORIZATION_TYPEHASH,
886 alice,
887 bob,
888 amount,
889 valid_after,
890 valid_before,
891 &nonce
892 );
893
894 env.set_caller(bob);
896 wrapper.receive_with_authorization(
897 alice,
898 bob,
899 amount,
900 valid_after,
901 valid_before,
902 nonce.clone(),
903 alice_pubkey,
904 signature
905 );
906
907 assert_eq!(
908 wrapper.balance_of(&alice),
909 U256::from(INITIAL_SUPPLY) - amount
910 );
911 assert_eq!(wrapper.balance_of(&bob), amount);
912 assert!(wrapper.authorization_state(alice, nonce));
913 }
914
915 #[test]
916 fn receive_with_authorization_wrong_caller_reverts() {
917 let Setup {
918 env,
919 mut wrapper,
920 alice,
921 bob,
922 charlie,
923 alice_pubkey
924 } = setup();
925
926 let amount: U256 = 250u64.into();
927 let valid_after: u64 = 0;
928 let valid_before: u64 = u64::MAX;
929 let nonce = fresh_nonce(8);
930
931 let signature = sign_transfer_authorization(
932 &env,
933 &alice,
934 wrapper.address(),
935 RECEIVE_WITH_AUTHORIZATION_TYPEHASH,
936 alice,
937 bob,
938 amount,
939 valid_after,
940 valid_before,
941 &nonce
942 );
943
944 env.set_caller(charlie);
946 assert_eq!(
947 wrapper.try_receive_with_authorization(
948 alice,
949 bob,
950 amount,
951 valid_after,
952 valid_before,
953 nonce,
954 alice_pubkey,
955 signature
956 ),
957 Err(Error::InvalidCaller.into())
958 );
959 }
960
961 #[test]
962 fn cancel_authorization_happy_path() {
963 let Setup {
964 env,
965 mut wrapper,
966 alice,
967 bob,
968 charlie,
969 alice_pubkey
970 } = setup();
971
972 let nonce = fresh_nonce(9);
973 let cancel_signature =
974 sign_cancel_authorization(&env, &alice, wrapper.address(), alice, &nonce);
975
976 env.set_caller(charlie);
977 wrapper.cancel_authorization(alice, nonce.clone(), alice_pubkey.clone(), cancel_signature);
978
979 assert!(wrapper.authorization_state(alice, nonce.clone()));
980 assert!(env.emitted_event(
981 &wrapper,
982 AuthorizationCanceled {
983 authorizer: alice,
984 nonce: nonce.clone()
985 }
986 ));
987
988 let amount: U256 = 1u64.into();
990 let valid_after: u64 = 0;
991 let valid_before: u64 = u64::MAX;
992 let transfer_signature = sign_transfer_authorization(
993 &env,
994 &alice,
995 wrapper.address(),
996 TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
997 alice,
998 bob,
999 amount,
1000 valid_after,
1001 valid_before,
1002 &nonce
1003 );
1004 assert_eq!(
1005 wrapper.try_transfer_with_authorization(
1006 alice,
1007 bob,
1008 amount,
1009 valid_after,
1010 valid_before,
1011 nonce,
1012 alice_pubkey,
1013 transfer_signature
1014 ),
1015 Err(Error::NonceAlreadyUsed.into())
1016 );
1017 }
1018
1019 #[test]
1020 fn cancel_authorization_already_used_reverts() {
1021 let Setup {
1022 env,
1023 mut wrapper,
1024 alice,
1025 bob,
1026 charlie,
1027 alice_pubkey
1028 } = setup();
1029
1030 let amount: U256 = 100u64.into();
1031 let valid_after: u64 = 0;
1032 let valid_before: u64 = u64::MAX;
1033 let nonce = fresh_nonce(10);
1034
1035 let transfer_signature = sign_transfer_authorization(
1037 &env,
1038 &alice,
1039 wrapper.address(),
1040 TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
1041 alice,
1042 bob,
1043 amount,
1044 valid_after,
1045 valid_before,
1046 &nonce
1047 );
1048 env.set_caller(charlie);
1049 wrapper.transfer_with_authorization(
1050 alice,
1051 bob,
1052 amount,
1053 valid_after,
1054 valid_before,
1055 nonce.clone(),
1056 alice_pubkey.clone(),
1057 transfer_signature
1058 );
1059
1060 let cancel_signature =
1062 sign_cancel_authorization(&env, &alice, wrapper.address(), alice, &nonce);
1063 assert_eq!(
1064 wrapper.try_cancel_authorization(alice, nonce, alice_pubkey, cancel_signature),
1065 Err(Error::AuthorizationUsed.into())
1066 );
1067 }
1068
1069 #[test]
1070 fn cancel_authorization_with_mismatched_public_key_reverts() {
1071 let Setup {
1072 env,
1073 mut wrapper,
1074 alice,
1075 bob,
1076 charlie,
1077 ..
1078 } = setup();
1079
1080 let nonce = fresh_nonce(11);
1081
1082 let bob_pubkey = env.public_key(&bob);
1085 let cancel_signature =
1086 sign_cancel_authorization(&env, &bob, wrapper.address(), alice, &nonce);
1087
1088 env.set_caller(charlie);
1091 assert_eq!(
1092 wrapper.try_cancel_authorization(alice, nonce.clone(), bob_pubkey, cancel_signature),
1093 Err(Error::InvalidPublicKey.into())
1094 );
1095
1096 assert!(!wrapper.authorization_state(alice, nonce));
1098 }
1099
1100 #[test]
1101 fn transfer_with_authorization_rejects_appended_byte_nonce_alias() {
1102 let Setup {
1103 env,
1104 mut wrapper,
1105 alice,
1106 bob,
1107 charlie,
1108 alice_pubkey
1109 } = setup();
1110
1111 let amount: U256 = 100u64.into();
1112 let valid_after: u64 = 0;
1113 let valid_before: u64 = u64::MAX;
1114 let nonce = fresh_nonce(20);
1115
1116 let signature = sign_transfer_authorization(
1117 &env,
1118 &alice,
1119 wrapper.address(),
1120 TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
1121 alice,
1122 bob,
1123 amount,
1124 valid_after,
1125 valid_before,
1126 &nonce
1127 );
1128
1129 env.set_caller(charlie);
1131 wrapper.transfer_with_authorization(
1132 alice,
1133 bob,
1134 amount,
1135 valid_after,
1136 valid_before,
1137 nonce.clone(),
1138 alice_pubkey.clone(),
1139 signature.clone()
1140 );
1141
1142 let mut aliased = nonce.to_vec();
1147 aliased.push(0);
1148 let aliased_nonce = Bytes::from(aliased);
1149
1150 assert_eq!(
1151 wrapper.try_transfer_with_authorization(
1152 alice,
1153 bob,
1154 amount,
1155 valid_after,
1156 valid_before,
1157 aliased_nonce,
1158 alice_pubkey,
1159 signature
1160 ),
1161 Err(Error::InvalidNonceLength.into())
1162 );
1163
1164 assert_eq!(wrapper.balance_of(&bob), amount);
1166 }
1167
1168 #[test]
1169 fn transfer_with_authorization_rejects_short_nonce() {
1170 let Setup {
1171 env,
1172 mut wrapper,
1173 alice,
1174 bob,
1175 charlie,
1176 alice_pubkey
1177 } = setup();
1178
1179 let amount: U256 = 100u64.into();
1180 let valid_after: u64 = 0;
1181 let valid_before: u64 = u64::MAX;
1182 let nonce = Bytes::from(vec![21u8; 31]);
1184
1185 let signature = sign_transfer_authorization(
1186 &env,
1187 &alice,
1188 wrapper.address(),
1189 TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
1190 alice,
1191 bob,
1192 amount,
1193 valid_after,
1194 valid_before,
1195 &nonce
1196 );
1197
1198 env.set_caller(charlie);
1199 assert_eq!(
1200 wrapper.try_transfer_with_authorization(
1201 alice,
1202 bob,
1203 amount,
1204 valid_after,
1205 valid_before,
1206 nonce,
1207 alice_pubkey,
1208 signature
1209 ),
1210 Err(Error::InvalidNonceLength.into())
1211 );
1212 }
1213
1214 #[test]
1215 fn cancel_authorization_rejects_nonstandard_nonce() {
1216 let Setup {
1217 env,
1218 mut wrapper,
1219 alice,
1220 charlie,
1221 alice_pubkey,
1222 ..
1223 } = setup();
1224
1225 let nonce = Bytes::from(vec![22u8; 33]);
1227 let cancel_signature =
1228 sign_cancel_authorization(&env, &alice, wrapper.address(), alice, &nonce);
1229
1230 env.set_caller(charlie);
1231 assert_eq!(
1232 wrapper.try_cancel_authorization(alice, nonce.clone(), alice_pubkey, cancel_signature),
1233 Err(Error::InvalidNonceLength.into())
1234 );
1235
1236 assert!(!wrapper.authorization_state(alice, nonce));
1238 }
1239
1240 fn sign_transfer_authorization(
1241 env: &HostEnv,
1242 signer: &Address,
1243 contract_address: Address,
1244 typehash: [u8; 32],
1245 from: Address,
1246 to: Address,
1247 amount: U256,
1248 valid_after: u64,
1249 valid_before: u64,
1250 nonce: &Bytes
1251 ) -> Bytes {
1252 let mut value_bytes = [0u8; 32];
1253 amount.to_big_endian(&mut value_bytes);
1254
1255 let mut nonce_padded = [0u8; 32];
1256 let len = nonce.len().min(32);
1257 nonce_padded[..len].copy_from_slice(&nonce[..len]);
1258
1259 let mut encoded_data = Vec::with_capacity(6 * 32);
1260 encoded_data.extend(crate::eip712::encode_address(from));
1261 encoded_data.extend(crate::eip712::encode_address(to));
1262 encoded_data.extend(casper_eip_712::encode_uint256(value_bytes));
1263 encoded_data.extend(casper_eip_712::encode_uint64(valid_after));
1264 encoded_data.extend(casper_eip_712::encode_uint64(valid_before));
1265 encoded_data.extend(casper_eip_712::encode_bytes32(nonce_padded));
1266
1267 let domain = crate::eip712::domain_separator(
1268 TOKEN_NAME,
1269 DOMAIN_VERSION,
1270 CHAIN_NAME.to_string(),
1271 contract_address
1272 );
1273 let message_hash = crate::eip712::hash_typed_data(domain, typehash, encoded_data);
1274 let message = Bytes::from(message_hash.to_vec());
1275
1276 env.sign_message(&message, signer)
1277 }
1278
1279 fn sign_cancel_authorization(
1280 env: &HostEnv,
1281 signer: &Address,
1282 contract_address: Address,
1283 authorizer: Address,
1284 nonce: &Bytes
1285 ) -> Bytes {
1286 let mut nonce_padded = [0u8; 32];
1287 let len = nonce.len().min(32);
1288 nonce_padded[..len].copy_from_slice(&nonce[..len]);
1289
1290 let mut encoded_data = Vec::with_capacity(64);
1291 encoded_data.extend(crate::eip712::encode_address(authorizer));
1292 encoded_data.extend(casper_eip_712::encode_bytes32(nonce_padded));
1293
1294 let domain = crate::eip712::domain_separator(
1295 TOKEN_NAME,
1296 DOMAIN_VERSION,
1297 CHAIN_NAME.to_string(),
1298 contract_address
1299 );
1300 let message_hash =
1301 crate::eip712::hash_typed_data(domain, CANCEL_AUTHORIZATION_TYPEHASH, encoded_data);
1302 let message = Bytes::from(message_hash.to_vec());
1303
1304 env.sign_message(&message, signer)
1305 }
1306}