1#[cfg(not(feature = "std"))]
20use alloc::{format, string::String};
21use alloc::{vec, vec::Vec};
22use codec::{Decode, Encode, MaxEncodedLen};
23use core::fmt::Debug;
24use frame_support::{
25 ensure,
26 traits::{Currency, Get, IsSubType, VestingSchedule},
27 weights::Weight,
28 DefaultNoBound,
29};
30pub use pallet::*;
31use polkadot_primitives::ValidityError;
32use scale_info::TypeInfo;
33use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
34use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256};
35use sp_runtime::{
36 impl_tx_ext_default,
37 traits::{
38 AsSystemOriginSigner, AsTransactionAuthorizedOrigin, CheckedSub, DispatchInfoOf,
39 Dispatchable, TransactionExtension, Zero,
40 },
41 transaction_validity::{
42 InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError,
43 ValidTransaction,
44 },
45 RuntimeDebug,
46};
47
48type CurrencyOf<T> = <<T as Config>::VestingSchedule as VestingSchedule<
49 <T as frame_system::Config>::AccountId,
50>>::Currency;
51type BalanceOf<T> = <CurrencyOf<T> as Currency<<T as frame_system::Config>::AccountId>>::Balance;
52
53pub trait WeightInfo {
54 fn claim() -> Weight;
55 fn mint_claim() -> Weight;
56 fn claim_attest() -> Weight;
57 fn attest() -> Weight;
58 fn move_claim() -> Weight;
59 fn prevalidate_attests() -> Weight;
60}
61
62pub struct TestWeightInfo;
63impl WeightInfo for TestWeightInfo {
64 fn claim() -> Weight {
65 Weight::zero()
66 }
67 fn mint_claim() -> Weight {
68 Weight::zero()
69 }
70 fn claim_attest() -> Weight {
71 Weight::zero()
72 }
73 fn attest() -> Weight {
74 Weight::zero()
75 }
76 fn move_claim() -> Weight {
77 Weight::zero()
78 }
79 fn prevalidate_attests() -> Weight {
80 Weight::zero()
81 }
82}
83
84#[derive(
86 Encode,
87 Decode,
88 Clone,
89 Copy,
90 Eq,
91 PartialEq,
92 RuntimeDebug,
93 TypeInfo,
94 Serialize,
95 Deserialize,
96 MaxEncodedLen,
97)]
98pub enum StatementKind {
99 Regular,
101 Saft,
103}
104
105impl StatementKind {
106 fn to_text(self) -> &'static [u8] {
108 match self {
109 StatementKind::Regular =>
110 &b"I hereby agree to the terms of the statement whose SHA-256 multihash is \
111 Qmc1XYqT6S39WNp2UeiRUrZichUWUPpGEThDE6dAb3f6Ny. (This may be found at the URL: \
112 https://statement.polkadot.network/regular.html)"[..],
113 StatementKind::Saft =>
114 &b"I hereby agree to the terms of the statement whose SHA-256 multihash is \
115 QmXEkMahfhHJPzT3RjkXiZVFi77ZeVeuxtAjhojGRNYckz. (This may be found at the URL: \
116 https://statement.polkadot.network/saft.html)"[..],
117 }
118 }
119}
120
121impl Default for StatementKind {
122 fn default() -> Self {
123 StatementKind::Regular
124 }
125}
126
127#[derive(
131 Clone, Copy, PartialEq, Eq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen,
132)]
133pub struct EthereumAddress([u8; 20]);
134
135impl Serialize for EthereumAddress {
136 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
137 where
138 S: Serializer,
139 {
140 let hex: String = rustc_hex::ToHex::to_hex(&self.0[..]);
141 serializer.serialize_str(&format!("0x{}", hex))
142 }
143}
144
145impl<'de> Deserialize<'de> for EthereumAddress {
146 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
147 where
148 D: Deserializer<'de>,
149 {
150 let base_string = String::deserialize(deserializer)?;
151 let offset = if base_string.starts_with("0x") { 2 } else { 0 };
152 let s = &base_string[offset..];
153 if s.len() != 40 {
154 Err(serde::de::Error::custom(
155 "Bad length of Ethereum address (should be 42 including '0x')",
156 ))?;
157 }
158 let raw: Vec<u8> = rustc_hex::FromHex::from_hex(s)
159 .map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?;
160 let mut r = Self::default();
161 r.0.copy_from_slice(&raw);
162 Ok(r)
163 }
164}
165
166#[derive(Encode, Decode, Clone, TypeInfo, MaxEncodedLen)]
167pub struct EcdsaSignature(pub [u8; 65]);
168
169impl PartialEq for EcdsaSignature {
170 fn eq(&self, other: &Self) -> bool {
171 &self.0[..] == &other.0[..]
172 }
173}
174
175impl core::fmt::Debug for EcdsaSignature {
176 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
177 write!(f, "EcdsaSignature({:?})", &self.0[..])
178 }
179}
180
181#[frame_support::pallet]
182pub mod pallet {
183 use super::*;
184 use frame_support::pallet_prelude::*;
185 use frame_system::pallet_prelude::*;
186
187 #[pallet::pallet]
188 pub struct Pallet<T>(_);
189
190 #[pallet::config]
192 pub trait Config: frame_system::Config {
193 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
195 type VestingSchedule: VestingSchedule<Self::AccountId, Moment = BlockNumberFor<Self>>;
196 #[pallet::constant]
197 type Prefix: Get<&'static [u8]>;
198 type MoveClaimOrigin: EnsureOrigin<Self::RuntimeOrigin>;
199 type WeightInfo: WeightInfo;
200 }
201
202 #[pallet::event]
203 #[pallet::generate_deposit(pub(super) fn deposit_event)]
204 pub enum Event<T: Config> {
205 Claimed { who: T::AccountId, ethereum_address: EthereumAddress, amount: BalanceOf<T> },
207 }
208
209 #[pallet::error]
210 pub enum Error<T> {
211 InvalidEthereumSignature,
213 SignerHasNoClaim,
215 SenderHasNoClaim,
217 PotUnderflow,
220 InvalidStatement,
222 VestedBalanceExists,
224 }
225
226 #[pallet::storage]
227 pub type Claims<T: Config> = StorageMap<_, Identity, EthereumAddress, BalanceOf<T>>;
228
229 #[pallet::storage]
230 pub type Total<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
231
232 #[pallet::storage]
237 pub type Vesting<T: Config> =
238 StorageMap<_, Identity, EthereumAddress, (BalanceOf<T>, BalanceOf<T>, BlockNumberFor<T>)>;
239
240 #[pallet::storage]
242 pub(super) type Signing<T> = StorageMap<_, Identity, EthereumAddress, StatementKind>;
243
244 #[pallet::storage]
246 pub(super) type Preclaims<T: Config> = StorageMap<_, Identity, T::AccountId, EthereumAddress>;
247
248 #[pallet::genesis_config]
249 #[derive(DefaultNoBound)]
250 pub struct GenesisConfig<T: Config> {
251 pub claims:
252 Vec<(EthereumAddress, BalanceOf<T>, Option<T::AccountId>, Option<StatementKind>)>,
253 pub vesting: Vec<(EthereumAddress, (BalanceOf<T>, BalanceOf<T>, BlockNumberFor<T>))>,
254 }
255
256 #[pallet::genesis_build]
257 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
258 fn build(&self) {
259 self.claims.iter().map(|(a, b, _, _)| (*a, *b)).for_each(|(a, b)| {
261 Claims::<T>::insert(a, b);
262 });
263 Total::<T>::put(
265 self.claims
266 .iter()
267 .fold(Zero::zero(), |acc: BalanceOf<T>, &(_, b, _, _)| acc + b),
268 );
269 self.vesting.iter().for_each(|(k, v)| {
271 Vesting::<T>::insert(k, v);
272 });
273 self.claims
275 .iter()
276 .filter_map(|(a, _, _, s)| Some((*a, (*s)?)))
277 .for_each(|(a, s)| {
278 Signing::<T>::insert(a, s);
279 });
280 self.claims.iter().filter_map(|(a, _, i, _)| Some((i.clone()?, *a))).for_each(
282 |(i, a)| {
283 Preclaims::<T>::insert(i, a);
284 },
285 );
286 }
287 }
288
289 #[pallet::hooks]
290 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
291
292 #[pallet::call]
293 impl<T: Config> Pallet<T> {
294 #[pallet::call_index(0)]
319 #[pallet::weight(T::WeightInfo::claim())]
320 pub fn claim(
321 origin: OriginFor<T>,
322 dest: T::AccountId,
323 ethereum_signature: EcdsaSignature,
324 ) -> DispatchResult {
325 ensure_none(origin)?;
326
327 let data = dest.using_encoded(to_ascii_hex);
328 let signer = Self::eth_recover(ðereum_signature, &data, &[][..])
329 .ok_or(Error::<T>::InvalidEthereumSignature)?;
330 ensure!(Signing::<T>::get(&signer).is_none(), Error::<T>::InvalidStatement);
331
332 Self::process_claim(signer, dest)?;
333 Ok(())
334 }
335
336 #[pallet::call_index(1)]
352 #[pallet::weight(T::WeightInfo::mint_claim())]
353 pub fn mint_claim(
354 origin: OriginFor<T>,
355 who: EthereumAddress,
356 value: BalanceOf<T>,
357 vesting_schedule: Option<(BalanceOf<T>, BalanceOf<T>, BlockNumberFor<T>)>,
358 statement: Option<StatementKind>,
359 ) -> DispatchResult {
360 ensure_root(origin)?;
361
362 Total::<T>::mutate(|t| *t += value);
363 Claims::<T>::insert(who, value);
364 if let Some(vs) = vesting_schedule {
365 Vesting::<T>::insert(who, vs);
366 }
367 if let Some(s) = statement {
368 Signing::<T>::insert(who, s);
369 }
370 Ok(())
371 }
372
373 #[pallet::call_index(2)]
401 #[pallet::weight(T::WeightInfo::claim_attest())]
402 pub fn claim_attest(
403 origin: OriginFor<T>,
404 dest: T::AccountId,
405 ethereum_signature: EcdsaSignature,
406 statement: Vec<u8>,
407 ) -> DispatchResult {
408 ensure_none(origin)?;
409
410 let data = dest.using_encoded(to_ascii_hex);
411 let signer = Self::eth_recover(ðereum_signature, &data, &statement)
412 .ok_or(Error::<T>::InvalidEthereumSignature)?;
413 if let Some(s) = Signing::<T>::get(signer) {
414 ensure!(s.to_text() == &statement[..], Error::<T>::InvalidStatement);
415 }
416 Self::process_claim(signer, dest)?;
417 Ok(())
418 }
419
420 #[pallet::call_index(3)]
440 #[pallet::weight((
441 T::WeightInfo::attest(),
442 DispatchClass::Normal,
443 Pays::No
444 ))]
445 pub fn attest(origin: OriginFor<T>, statement: Vec<u8>) -> DispatchResult {
446 let who = ensure_signed(origin)?;
447 let signer = Preclaims::<T>::get(&who).ok_or(Error::<T>::SenderHasNoClaim)?;
448 if let Some(s) = Signing::<T>::get(signer) {
449 ensure!(s.to_text() == &statement[..], Error::<T>::InvalidStatement);
450 }
451 Self::process_claim(signer, who.clone())?;
452 Preclaims::<T>::remove(&who);
453 Ok(())
454 }
455
456 #[pallet::call_index(4)]
457 #[pallet::weight(T::WeightInfo::move_claim())]
458 pub fn move_claim(
459 origin: OriginFor<T>,
460 old: EthereumAddress,
461 new: EthereumAddress,
462 maybe_preclaim: Option<T::AccountId>,
463 ) -> DispatchResultWithPostInfo {
464 T::MoveClaimOrigin::try_origin(origin).map(|_| ()).or_else(ensure_root)?;
465
466 Claims::<T>::take(&old).map(|c| Claims::<T>::insert(&new, c));
467 Vesting::<T>::take(&old).map(|c| Vesting::<T>::insert(&new, c));
468 Signing::<T>::take(&old).map(|c| Signing::<T>::insert(&new, c));
469 maybe_preclaim.map(|preclaim| {
470 Preclaims::<T>::mutate(&preclaim, |maybe_o| {
471 if maybe_o.as_ref().map_or(false, |o| o == &old) {
472 *maybe_o = Some(new)
473 }
474 })
475 });
476 Ok(Pays::No.into())
477 }
478 }
479
480 #[pallet::validate_unsigned]
481 impl<T: Config> ValidateUnsigned for Pallet<T> {
482 type Call = Call<T>;
483
484 fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
485 const PRIORITY: u64 = 100;
486
487 let (maybe_signer, maybe_statement) = match call {
488 Call::claim { dest: account, ethereum_signature } => {
492 let data = account.using_encoded(to_ascii_hex);
493 (Self::eth_recover(ðereum_signature, &data, &[][..]), None)
494 },
495 Call::claim_attest { dest: account, ethereum_signature, statement } => {
499 let data = account.using_encoded(to_ascii_hex);
500 (
501 Self::eth_recover(ðereum_signature, &data, &statement),
502 Some(statement.as_slice()),
503 )
504 },
505 _ => return Err(InvalidTransaction::Call.into()),
506 };
507
508 let signer = maybe_signer.ok_or(InvalidTransaction::Custom(
509 ValidityError::InvalidEthereumSignature.into(),
510 ))?;
511
512 let e = InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into());
513 ensure!(Claims::<T>::contains_key(&signer), e);
514
515 let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into());
516 match Signing::<T>::get(signer) {
517 None => ensure!(maybe_statement.is_none(), e),
518 Some(s) => ensure!(Some(s.to_text()) == maybe_statement, e),
519 }
520
521 Ok(ValidTransaction {
522 priority: PRIORITY,
523 requires: vec![],
524 provides: vec![("claims", signer).encode()],
525 longevity: TransactionLongevity::max_value(),
526 propagate: true,
527 })
528 }
529 }
530}
531
532fn to_ascii_hex(data: &[u8]) -> Vec<u8> {
534 let mut r = Vec::with_capacity(data.len() * 2);
535 let mut push_nibble = |n| r.push(if n < 10 { b'0' + n } else { b'a' - 10 + n });
536 for &b in data.iter() {
537 push_nibble(b / 16);
538 push_nibble(b % 16);
539 }
540 r
541}
542
543impl<T: Config> Pallet<T> {
544 fn ethereum_signable_message(what: &[u8], extra: &[u8]) -> Vec<u8> {
546 let prefix = T::Prefix::get();
547 let mut l = prefix.len() + what.len() + extra.len();
548 let mut rev = Vec::new();
549 while l > 0 {
550 rev.push(b'0' + (l % 10) as u8);
551 l /= 10;
552 }
553 let mut v = b"\x19Ethereum Signed Message:\n".to_vec();
554 v.extend(rev.into_iter().rev());
555 v.extend_from_slice(prefix);
556 v.extend_from_slice(what);
557 v.extend_from_slice(extra);
558 v
559 }
560
561 fn eth_recover(s: &EcdsaSignature, what: &[u8], extra: &[u8]) -> Option<EthereumAddress> {
564 let msg = keccak_256(&Self::ethereum_signable_message(what, extra));
565 let mut res = EthereumAddress::default();
566 res.0
567 .copy_from_slice(&keccak_256(&secp256k1_ecdsa_recover(&s.0, &msg).ok()?[..])[12..]);
568 Some(res)
569 }
570
571 fn process_claim(signer: EthereumAddress, dest: T::AccountId) -> sp_runtime::DispatchResult {
572 let balance_due = Claims::<T>::get(&signer).ok_or(Error::<T>::SignerHasNoClaim)?;
573
574 let new_total =
575 Total::<T>::get().checked_sub(&balance_due).ok_or(Error::<T>::PotUnderflow)?;
576
577 let vesting = Vesting::<T>::get(&signer);
578 if vesting.is_some() && T::VestingSchedule::vesting_balance(&dest).is_some() {
579 return Err(Error::<T>::VestedBalanceExists.into())
580 }
581
582 let _ = CurrencyOf::<T>::deposit_creating(&dest, balance_due);
584
585 if let Some(vs) = vesting {
587 T::VestingSchedule::add_vesting_schedule(&dest, vs.0, vs.1, vs.2)
590 .expect("No other vesting schedule exists, as checked above; qed");
591 }
592
593 Total::<T>::put(new_total);
594 Claims::<T>::remove(&signer);
595 Vesting::<T>::remove(&signer);
596 Signing::<T>::remove(&signer);
597
598 Self::deposit_event(Event::<T>::Claimed {
600 who: dest,
601 ethereum_address: signer,
602 amount: balance_due,
603 });
604
605 Ok(())
606 }
607}
608
609#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
612#[scale_info(skip_type_params(T))]
613pub struct PrevalidateAttests<T>(core::marker::PhantomData<fn(T)>);
614
615impl<T: Config> Debug for PrevalidateAttests<T>
616where
617 <T as frame_system::Config>::RuntimeCall: IsSubType<Call<T>>,
618{
619 #[cfg(feature = "std")]
620 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
621 write!(f, "PrevalidateAttests")
622 }
623
624 #[cfg(not(feature = "std"))]
625 fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
626 Ok(())
627 }
628}
629
630impl<T: Config> PrevalidateAttests<T>
631where
632 <T as frame_system::Config>::RuntimeCall: IsSubType<Call<T>>,
633{
634 pub fn new() -> Self {
636 Self(core::marker::PhantomData)
637 }
638}
639
640impl<T: Config> TransactionExtension<T::RuntimeCall> for PrevalidateAttests<T>
641where
642 <T as frame_system::Config>::RuntimeCall: IsSubType<Call<T>>,
643 <<T as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
644 AsSystemOriginSigner<T::AccountId> + AsTransactionAuthorizedOrigin + Clone,
645{
646 const IDENTIFIER: &'static str = "PrevalidateAttests";
647 type Implicit = ();
648 type Pre = ();
649 type Val = ();
650
651 fn weight(&self, call: &T::RuntimeCall) -> Weight {
652 if let Some(Call::attest { .. }) = call.is_sub_type() {
653 T::WeightInfo::prevalidate_attests()
654 } else {
655 Weight::zero()
656 }
657 }
658
659 fn validate(
660 &self,
661 origin: <T::RuntimeCall as Dispatchable>::RuntimeOrigin,
662 call: &T::RuntimeCall,
663 _info: &DispatchInfoOf<T::RuntimeCall>,
664 _len: usize,
665 _self_implicit: Self::Implicit,
666 _inherited_implication: &impl Encode,
667 _source: TransactionSource,
668 ) -> Result<
669 (ValidTransaction, Self::Val, <T::RuntimeCall as Dispatchable>::RuntimeOrigin),
670 TransactionValidityError,
671 > {
672 if let Some(Call::attest { statement: attested_statement }) = call.is_sub_type() {
673 let who = origin.as_system_origin_signer().ok_or(InvalidTransaction::BadSigner)?;
674 let signer = Preclaims::<T>::get(who)
675 .ok_or(InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()))?;
676 if let Some(s) = Signing::<T>::get(signer) {
677 let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into());
678 ensure!(&attested_statement[..] == s.to_text(), e);
679 }
680 }
681 Ok((ValidTransaction::default(), (), origin))
682 }
683
684 impl_tx_ext_default!(T::RuntimeCall; prepare);
685}
686
687#[cfg(any(test, feature = "runtime-benchmarks"))]
688mod secp_utils {
689 use super::*;
690
691 pub fn public(secret: &libsecp256k1::SecretKey) -> libsecp256k1::PublicKey {
692 libsecp256k1::PublicKey::from_secret_key(secret)
693 }
694 pub fn eth(secret: &libsecp256k1::SecretKey) -> EthereumAddress {
695 let mut res = EthereumAddress::default();
696 res.0.copy_from_slice(&keccak_256(&public(secret).serialize()[1..65])[12..]);
697 res
698 }
699 pub fn sig<T: Config>(
700 secret: &libsecp256k1::SecretKey,
701 what: &[u8],
702 extra: &[u8],
703 ) -> EcdsaSignature {
704 let msg = keccak_256(&super::Pallet::<T>::ethereum_signable_message(
705 &to_ascii_hex(what)[..],
706 extra,
707 ));
708 let (sig, recovery_id) = libsecp256k1::sign(&libsecp256k1::Message::parse(&msg), secret);
709 let mut r = [0u8; 65];
710 r[0..64].copy_from_slice(&sig.serialize()[..]);
711 r[64] = recovery_id.serialize();
712 EcdsaSignature(r)
713 }
714}
715
716#[cfg(test)]
717mod tests {
718 use super::*;
719 use hex_literal::hex;
720 use secp_utils::*;
721 use sp_runtime::transaction_validity::TransactionSource::External;
722
723 use codec::Encode;
724 use crate::claims;
727 use claims::Call as ClaimsCall;
728 use frame_support::{
729 assert_err, assert_noop, assert_ok, derive_impl,
730 dispatch::{GetDispatchInfo, Pays},
731 ord_parameter_types, parameter_types,
732 traits::{ExistenceRequirement, WithdrawReasons},
733 };
734 use pallet_balances;
735 use sp_runtime::{
736 traits::{DispatchTransaction, Identity},
737 transaction_validity::TransactionLongevity,
738 BuildStorage,
739 DispatchError::BadOrigin,
740 TokenError,
741 };
742
743 type Block = frame_system::mocking::MockBlock<Test>;
744
745 frame_support::construct_runtime!(
746 pub enum Test
747 {
748 System: frame_system,
749 Balances: pallet_balances,
750 Vesting: pallet_vesting,
751 Claims: claims,
752 }
753 );
754
755 #[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
756 impl frame_system::Config for Test {
757 type RuntimeOrigin = RuntimeOrigin;
758 type RuntimeCall = RuntimeCall;
759 type Block = Block;
760 type RuntimeEvent = RuntimeEvent;
761 type AccountData = pallet_balances::AccountData<u64>;
762 type MaxConsumers = frame_support::traits::ConstU32<16>;
763 }
764
765 #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
766 impl pallet_balances::Config for Test {
767 type AccountStore = System;
768 }
769
770 parameter_types! {
771 pub const MinVestedTransfer: u64 = 1;
772 pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons =
773 WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE);
774 }
775
776 impl pallet_vesting::Config for Test {
777 type RuntimeEvent = RuntimeEvent;
778 type Currency = Balances;
779 type BlockNumberToBalance = Identity;
780 type MinVestedTransfer = MinVestedTransfer;
781 type WeightInfo = ();
782 type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons;
783 type BlockNumberProvider = System;
784 const MAX_VESTING_SCHEDULES: u32 = 28;
785 }
786
787 parameter_types! {
788 pub Prefix: &'static [u8] = b"Pay RUSTs to the TEST account:";
789 }
790 ord_parameter_types! {
791 pub const Six: u64 = 6;
792 }
793
794 impl Config for Test {
795 type RuntimeEvent = RuntimeEvent;
796 type VestingSchedule = Vesting;
797 type Prefix = Prefix;
798 type MoveClaimOrigin = frame_system::EnsureSignedBy<Six, u64>;
799 type WeightInfo = TestWeightInfo;
800 }
801
802 fn alice() -> libsecp256k1::SecretKey {
803 libsecp256k1::SecretKey::parse(&keccak_256(b"Alice")).unwrap()
804 }
805 fn bob() -> libsecp256k1::SecretKey {
806 libsecp256k1::SecretKey::parse(&keccak_256(b"Bob")).unwrap()
807 }
808 fn dave() -> libsecp256k1::SecretKey {
809 libsecp256k1::SecretKey::parse(&keccak_256(b"Dave")).unwrap()
810 }
811 fn eve() -> libsecp256k1::SecretKey {
812 libsecp256k1::SecretKey::parse(&keccak_256(b"Eve")).unwrap()
813 }
814 fn frank() -> libsecp256k1::SecretKey {
815 libsecp256k1::SecretKey::parse(&keccak_256(b"Frank")).unwrap()
816 }
817
818 pub fn new_test_ext() -> sp_io::TestExternalities {
821 let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
822 pallet_balances::GenesisConfig::<Test>::default()
824 .assimilate_storage(&mut t)
825 .unwrap();
826 claims::GenesisConfig::<Test> {
827 claims: vec![
828 (eth(&alice()), 100, None, None),
829 (eth(&dave()), 200, None, Some(StatementKind::Regular)),
830 (eth(&eve()), 300, Some(42), Some(StatementKind::Saft)),
831 (eth(&frank()), 400, Some(43), None),
832 ],
833 vesting: vec![(eth(&alice()), (50, 10, 1))],
834 }
835 .assimilate_storage(&mut t)
836 .unwrap();
837 t.into()
838 }
839
840 fn total_claims() -> u64 {
841 100 + 200 + 300 + 400
842 }
843
844 #[test]
845 fn basic_setup_works() {
846 new_test_ext().execute_with(|| {
847 assert_eq!(claims::Total::<Test>::get(), total_claims());
848 assert_eq!(claims::Claims::<Test>::get(ð(&alice())), Some(100));
849 assert_eq!(claims::Claims::<Test>::get(ð(&dave())), Some(200));
850 assert_eq!(claims::Claims::<Test>::get(ð(&eve())), Some(300));
851 assert_eq!(claims::Claims::<Test>::get(ð(&frank())), Some(400));
852 assert_eq!(claims::Claims::<Test>::get(&EthereumAddress::default()), None);
853 assert_eq!(claims::Vesting::<Test>::get(ð(&alice())), Some((50, 10, 1)));
854 });
855 }
856
857 #[test]
858 fn serde_works() {
859 let x = EthereumAddress(hex!["0123456789abcdef0123456789abcdef01234567"]);
860 let y = serde_json::to_string(&x).unwrap();
861 assert_eq!(y, "\"0x0123456789abcdef0123456789abcdef01234567\"");
862 let z: EthereumAddress = serde_json::from_str(&y).unwrap();
863 assert_eq!(x, z);
864 }
865
866 #[test]
867 fn claiming_works() {
868 new_test_ext().execute_with(|| {
869 assert_eq!(Balances::free_balance(42), 0);
870 assert_ok!(Claims::claim(
871 RuntimeOrigin::none(),
872 42,
873 sig::<Test>(&alice(), &42u64.encode(), &[][..])
874 ));
875 assert_eq!(Balances::free_balance(&42), 100);
876 assert_eq!(Vesting::vesting_balance(&42), Some(50));
877 assert_eq!(claims::Total::<Test>::get(), total_claims() - 100);
878 });
879 }
880
881 #[test]
882 fn basic_claim_moving_works() {
883 new_test_ext().execute_with(|| {
884 assert_eq!(Balances::free_balance(42), 0);
885 assert_noop!(
886 Claims::move_claim(RuntimeOrigin::signed(1), eth(&alice()), eth(&bob()), None),
887 BadOrigin
888 );
889 assert_ok!(Claims::move_claim(
890 RuntimeOrigin::signed(6),
891 eth(&alice()),
892 eth(&bob()),
893 None
894 ));
895 assert_noop!(
896 Claims::claim(
897 RuntimeOrigin::none(),
898 42,
899 sig::<Test>(&alice(), &42u64.encode(), &[][..])
900 ),
901 Error::<Test>::SignerHasNoClaim
902 );
903 assert_ok!(Claims::claim(
904 RuntimeOrigin::none(),
905 42,
906 sig::<Test>(&bob(), &42u64.encode(), &[][..])
907 ));
908 assert_eq!(Balances::free_balance(&42), 100);
909 assert_eq!(Vesting::vesting_balance(&42), Some(50));
910 assert_eq!(claims::Total::<Test>::get(), total_claims() - 100);
911 });
912 }
913
914 #[test]
915 fn claim_attest_moving_works() {
916 new_test_ext().execute_with(|| {
917 assert_ok!(Claims::move_claim(
918 RuntimeOrigin::signed(6),
919 eth(&dave()),
920 eth(&bob()),
921 None
922 ));
923 let s = sig::<Test>(&bob(), &42u64.encode(), StatementKind::Regular.to_text());
924 assert_ok!(Claims::claim_attest(
925 RuntimeOrigin::none(),
926 42,
927 s,
928 StatementKind::Regular.to_text().to_vec()
929 ));
930 assert_eq!(Balances::free_balance(&42), 200);
931 });
932 }
933
934 #[test]
935 fn attest_moving_works() {
936 new_test_ext().execute_with(|| {
937 assert_ok!(Claims::move_claim(
938 RuntimeOrigin::signed(6),
939 eth(&eve()),
940 eth(&bob()),
941 Some(42)
942 ));
943 assert_ok!(Claims::attest(
944 RuntimeOrigin::signed(42),
945 StatementKind::Saft.to_text().to_vec()
946 ));
947 assert_eq!(Balances::free_balance(&42), 300);
948 });
949 }
950
951 #[test]
952 fn claiming_does_not_bypass_signing() {
953 new_test_ext().execute_with(|| {
954 assert_ok!(Claims::claim(
955 RuntimeOrigin::none(),
956 42,
957 sig::<Test>(&alice(), &42u64.encode(), &[][..])
958 ));
959 assert_noop!(
960 Claims::claim(
961 RuntimeOrigin::none(),
962 42,
963 sig::<Test>(&dave(), &42u64.encode(), &[][..])
964 ),
965 Error::<Test>::InvalidStatement,
966 );
967 assert_noop!(
968 Claims::claim(
969 RuntimeOrigin::none(),
970 42,
971 sig::<Test>(&eve(), &42u64.encode(), &[][..])
972 ),
973 Error::<Test>::InvalidStatement,
974 );
975 assert_ok!(Claims::claim(
976 RuntimeOrigin::none(),
977 42,
978 sig::<Test>(&frank(), &42u64.encode(), &[][..])
979 ));
980 });
981 }
982
983 #[test]
984 fn attest_claiming_works() {
985 new_test_ext().execute_with(|| {
986 assert_eq!(Balances::free_balance(42), 0);
987 let s = sig::<Test>(&dave(), &42u64.encode(), StatementKind::Saft.to_text());
988 let r = Claims::claim_attest(
989 RuntimeOrigin::none(),
990 42,
991 s.clone(),
992 StatementKind::Saft.to_text().to_vec(),
993 );
994 assert_noop!(r, Error::<Test>::InvalidStatement);
995
996 let r = Claims::claim_attest(
997 RuntimeOrigin::none(),
998 42,
999 s,
1000 StatementKind::Regular.to_text().to_vec(),
1001 );
1002 assert_noop!(r, Error::<Test>::SignerHasNoClaim);
1003 let s = sig::<Test>(&dave(), &42u64.encode(), StatementKind::Regular.to_text());
1007 assert_ok!(Claims::claim_attest(
1008 RuntimeOrigin::none(),
1009 42,
1010 s,
1011 StatementKind::Regular.to_text().to_vec()
1012 ));
1013 assert_eq!(Balances::free_balance(&42), 200);
1014 assert_eq!(claims::Total::<Test>::get(), total_claims() - 200);
1015
1016 let s = sig::<Test>(&dave(), &42u64.encode(), StatementKind::Regular.to_text());
1017 let r = Claims::claim_attest(
1018 RuntimeOrigin::none(),
1019 42,
1020 s,
1021 StatementKind::Regular.to_text().to_vec(),
1022 );
1023 assert_noop!(r, Error::<Test>::SignerHasNoClaim);
1024 });
1025 }
1026
1027 #[test]
1028 fn attesting_works() {
1029 new_test_ext().execute_with(|| {
1030 assert_eq!(Balances::free_balance(42), 0);
1031 assert_noop!(
1032 Claims::attest(RuntimeOrigin::signed(69), StatementKind::Saft.to_text().to_vec()),
1033 Error::<Test>::SenderHasNoClaim
1034 );
1035 assert_noop!(
1036 Claims::attest(
1037 RuntimeOrigin::signed(42),
1038 StatementKind::Regular.to_text().to_vec()
1039 ),
1040 Error::<Test>::InvalidStatement
1041 );
1042 assert_ok!(Claims::attest(
1043 RuntimeOrigin::signed(42),
1044 StatementKind::Saft.to_text().to_vec()
1045 ));
1046 assert_eq!(Balances::free_balance(&42), 300);
1047 assert_eq!(claims::Total::<Test>::get(), total_claims() - 300);
1048 });
1049 }
1050
1051 #[test]
1052 fn claim_cannot_clobber_preclaim() {
1053 new_test_ext().execute_with(|| {
1054 assert_eq!(Balances::free_balance(42), 0);
1055 assert_ok!(Claims::claim(
1057 RuntimeOrigin::none(),
1058 42,
1059 sig::<Test>(&alice(), &42u64.encode(), &[][..])
1060 ));
1061 assert_eq!(Balances::free_balance(&42), 100);
1062 assert_ok!(Claims::attest(
1064 RuntimeOrigin::signed(42),
1065 StatementKind::Saft.to_text().to_vec()
1066 ));
1067 assert_eq!(Balances::free_balance(&42), 100 + 300);
1068 assert_eq!(claims::Total::<Test>::get(), total_claims() - 400);
1069 });
1070 }
1071
1072 #[test]
1073 fn valid_attest_transactions_are_free() {
1074 new_test_ext().execute_with(|| {
1075 let p = PrevalidateAttests::<Test>::new();
1076 let c = RuntimeCall::Claims(ClaimsCall::attest {
1077 statement: StatementKind::Saft.to_text().to_vec(),
1078 });
1079 let di = c.get_dispatch_info();
1080 assert_eq!(di.pays_fee, Pays::No);
1081 let r = p.validate_only(Some(42).into(), &c, &di, 20, External, 0);
1082 assert_eq!(r.unwrap().0, ValidTransaction::default());
1083 });
1084 }
1085
1086 #[test]
1087 fn invalid_attest_transactions_are_recognized() {
1088 new_test_ext().execute_with(|| {
1089 let p = PrevalidateAttests::<Test>::new();
1090 let c = RuntimeCall::Claims(ClaimsCall::attest {
1091 statement: StatementKind::Regular.to_text().to_vec(),
1092 });
1093 let di = c.get_dispatch_info();
1094 let r = p.validate_only(Some(42).into(), &c, &di, 20, External, 0);
1095 assert!(r.is_err());
1096 let c = RuntimeCall::Claims(ClaimsCall::attest {
1097 statement: StatementKind::Saft.to_text().to_vec(),
1098 });
1099 let di = c.get_dispatch_info();
1100 let r = p.validate_only(Some(69).into(), &c, &di, 20, External, 0);
1101 assert!(r.is_err());
1102 });
1103 }
1104
1105 #[test]
1106 fn cannot_bypass_attest_claiming() {
1107 new_test_ext().execute_with(|| {
1108 assert_eq!(Balances::free_balance(42), 0);
1109 let s = sig::<Test>(&dave(), &42u64.encode(), &[]);
1110 let r = Claims::claim(RuntimeOrigin::none(), 42, s.clone());
1111 assert_noop!(r, Error::<Test>::InvalidStatement);
1112 });
1113 }
1114
1115 #[test]
1116 fn add_claim_works() {
1117 new_test_ext().execute_with(|| {
1118 assert_noop!(
1119 Claims::mint_claim(RuntimeOrigin::signed(42), eth(&bob()), 200, None, None),
1120 sp_runtime::traits::BadOrigin,
1121 );
1122 assert_eq!(Balances::free_balance(42), 0);
1123 assert_noop!(
1124 Claims::claim(
1125 RuntimeOrigin::none(),
1126 69,
1127 sig::<Test>(&bob(), &69u64.encode(), &[][..])
1128 ),
1129 Error::<Test>::SignerHasNoClaim,
1130 );
1131 assert_ok!(Claims::mint_claim(RuntimeOrigin::root(), eth(&bob()), 200, None, None));
1132 assert_eq!(claims::Total::<Test>::get(), total_claims() + 200);
1133 assert_ok!(Claims::claim(
1134 RuntimeOrigin::none(),
1135 69,
1136 sig::<Test>(&bob(), &69u64.encode(), &[][..])
1137 ));
1138 assert_eq!(Balances::free_balance(&69), 200);
1139 assert_eq!(Vesting::vesting_balance(&69), None);
1140 assert_eq!(claims::Total::<Test>::get(), total_claims());
1141 });
1142 }
1143
1144 #[test]
1145 fn add_claim_with_vesting_works() {
1146 new_test_ext().execute_with(|| {
1147 assert_noop!(
1148 Claims::mint_claim(
1149 RuntimeOrigin::signed(42),
1150 eth(&bob()),
1151 200,
1152 Some((50, 10, 1)),
1153 None
1154 ),
1155 sp_runtime::traits::BadOrigin,
1156 );
1157 assert_eq!(Balances::free_balance(42), 0);
1158 assert_noop!(
1159 Claims::claim(
1160 RuntimeOrigin::none(),
1161 69,
1162 sig::<Test>(&bob(), &69u64.encode(), &[][..])
1163 ),
1164 Error::<Test>::SignerHasNoClaim,
1165 );
1166 assert_ok!(Claims::mint_claim(
1167 RuntimeOrigin::root(),
1168 eth(&bob()),
1169 200,
1170 Some((50, 10, 1)),
1171 None
1172 ));
1173 assert_ok!(Claims::claim(
1174 RuntimeOrigin::none(),
1175 69,
1176 sig::<Test>(&bob(), &69u64.encode(), &[][..])
1177 ));
1178 assert_eq!(Balances::free_balance(&69), 200);
1179 assert_eq!(Vesting::vesting_balance(&69), Some(50));
1180
1181 assert_err!(
1183 <Balances as Currency<_>>::transfer(
1184 &69,
1185 &80,
1186 180,
1187 ExistenceRequirement::AllowDeath
1188 ),
1189 TokenError::Frozen,
1190 );
1191 });
1192 }
1193
1194 #[test]
1195 fn add_claim_with_statement_works() {
1196 new_test_ext().execute_with(|| {
1197 assert_noop!(
1198 Claims::mint_claim(
1199 RuntimeOrigin::signed(42),
1200 eth(&bob()),
1201 200,
1202 None,
1203 Some(StatementKind::Regular)
1204 ),
1205 sp_runtime::traits::BadOrigin,
1206 );
1207 assert_eq!(Balances::free_balance(42), 0);
1208 let signature = sig::<Test>(&bob(), &69u64.encode(), StatementKind::Regular.to_text());
1209 assert_noop!(
1210 Claims::claim_attest(
1211 RuntimeOrigin::none(),
1212 69,
1213 signature.clone(),
1214 StatementKind::Regular.to_text().to_vec()
1215 ),
1216 Error::<Test>::SignerHasNoClaim
1217 );
1218 assert_ok!(Claims::mint_claim(
1219 RuntimeOrigin::root(),
1220 eth(&bob()),
1221 200,
1222 None,
1223 Some(StatementKind::Regular)
1224 ));
1225 assert_noop!(
1226 Claims::claim_attest(RuntimeOrigin::none(), 69, signature.clone(), vec![],),
1227 Error::<Test>::SignerHasNoClaim
1228 );
1229 assert_ok!(Claims::claim_attest(
1230 RuntimeOrigin::none(),
1231 69,
1232 signature.clone(),
1233 StatementKind::Regular.to_text().to_vec()
1234 ));
1235 assert_eq!(Balances::free_balance(&69), 200);
1236 });
1237 }
1238
1239 #[test]
1240 fn origin_signed_claiming_fail() {
1241 new_test_ext().execute_with(|| {
1242 assert_eq!(Balances::free_balance(42), 0);
1243 assert_err!(
1244 Claims::claim(
1245 RuntimeOrigin::signed(42),
1246 42,
1247 sig::<Test>(&alice(), &42u64.encode(), &[][..])
1248 ),
1249 sp_runtime::traits::BadOrigin,
1250 );
1251 });
1252 }
1253
1254 #[test]
1255 fn double_claiming_doesnt_work() {
1256 new_test_ext().execute_with(|| {
1257 assert_eq!(Balances::free_balance(42), 0);
1258 assert_ok!(Claims::claim(
1259 RuntimeOrigin::none(),
1260 42,
1261 sig::<Test>(&alice(), &42u64.encode(), &[][..])
1262 ));
1263 assert_noop!(
1264 Claims::claim(
1265 RuntimeOrigin::none(),
1266 42,
1267 sig::<Test>(&alice(), &42u64.encode(), &[][..])
1268 ),
1269 Error::<Test>::SignerHasNoClaim
1270 );
1271 });
1272 }
1273
1274 #[test]
1275 fn claiming_while_vested_doesnt_work() {
1276 new_test_ext().execute_with(|| {
1277 CurrencyOf::<Test>::make_free_balance_be(&69, total_claims());
1278 assert_eq!(Balances::free_balance(69), total_claims());
1279 assert_ok!(<Test as Config>::VestingSchedule::add_vesting_schedule(
1281 &69,
1282 total_claims(),
1283 100,
1284 10
1285 ));
1286 assert_ok!(Claims::mint_claim(
1287 RuntimeOrigin::root(),
1288 eth(&bob()),
1289 200,
1290 Some((50, 10, 1)),
1291 None
1292 ));
1293 assert_eq!(claims::Total::<Test>::get(), total_claims() + 200);
1295
1296 assert_noop!(
1298 Claims::claim(
1299 RuntimeOrigin::none(),
1300 69,
1301 sig::<Test>(&bob(), &69u64.encode(), &[][..])
1302 ),
1303 Error::<Test>::VestedBalanceExists,
1304 );
1305 });
1306 }
1307
1308 #[test]
1309 fn non_sender_sig_doesnt_work() {
1310 new_test_ext().execute_with(|| {
1311 assert_eq!(Balances::free_balance(42), 0);
1312 assert_noop!(
1313 Claims::claim(
1314 RuntimeOrigin::none(),
1315 42,
1316 sig::<Test>(&alice(), &69u64.encode(), &[][..])
1317 ),
1318 Error::<Test>::SignerHasNoClaim
1319 );
1320 });
1321 }
1322
1323 #[test]
1324 fn non_claimant_doesnt_work() {
1325 new_test_ext().execute_with(|| {
1326 assert_eq!(Balances::free_balance(42), 0);
1327 assert_noop!(
1328 Claims::claim(
1329 RuntimeOrigin::none(),
1330 42,
1331 sig::<Test>(&bob(), &69u64.encode(), &[][..])
1332 ),
1333 Error::<Test>::SignerHasNoClaim
1334 );
1335 });
1336 }
1337
1338 #[test]
1339 fn real_eth_sig_works() {
1340 new_test_ext().execute_with(|| {
1341 let sig = hex!["444023e89b67e67c0562ed0305d252a5dd12b2af5ac51d6d3cb69a0b486bc4b3191401802dc29d26d586221f7256cd3329fe82174bdf659baea149a40e1c495d1c"];
1343 let sig = EcdsaSignature(sig);
1344 let who = 42u64.using_encoded(to_ascii_hex);
1345 let signer = Claims::eth_recover(&sig, &who, &[][..]).unwrap();
1346 assert_eq!(signer.0, hex!["6d31165d5d932d571f3b44695653b46dcc327e84"]);
1347 });
1348 }
1349
1350 #[test]
1351 fn validate_unsigned_works() {
1352 use sp_runtime::traits::ValidateUnsigned;
1353 let source = sp_runtime::transaction_validity::TransactionSource::External;
1354
1355 new_test_ext().execute_with(|| {
1356 assert_eq!(
1357 Pallet::<Test>::validate_unsigned(
1358 source,
1359 &ClaimsCall::claim {
1360 dest: 1,
1361 ethereum_signature: sig::<Test>(&alice(), &1u64.encode(), &[][..])
1362 }
1363 ),
1364 Ok(ValidTransaction {
1365 priority: 100,
1366 requires: vec![],
1367 provides: vec![("claims", eth(&alice())).encode()],
1368 longevity: TransactionLongevity::max_value(),
1369 propagate: true,
1370 })
1371 );
1372 assert_eq!(
1373 Pallet::<Test>::validate_unsigned(
1374 source,
1375 &ClaimsCall::claim { dest: 0, ethereum_signature: EcdsaSignature([0; 65]) }
1376 ),
1377 InvalidTransaction::Custom(ValidityError::InvalidEthereumSignature.into()).into(),
1378 );
1379 assert_eq!(
1380 Pallet::<Test>::validate_unsigned(
1381 source,
1382 &ClaimsCall::claim {
1383 dest: 1,
1384 ethereum_signature: sig::<Test>(&bob(), &1u64.encode(), &[][..])
1385 }
1386 ),
1387 InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(),
1388 );
1389 let s = sig::<Test>(&dave(), &1u64.encode(), StatementKind::Regular.to_text());
1390 let call = ClaimsCall::claim_attest {
1391 dest: 1,
1392 ethereum_signature: s,
1393 statement: StatementKind::Regular.to_text().to_vec(),
1394 };
1395 assert_eq!(
1396 Pallet::<Test>::validate_unsigned(source, &call),
1397 Ok(ValidTransaction {
1398 priority: 100,
1399 requires: vec![],
1400 provides: vec![("claims", eth(&dave())).encode()],
1401 longevity: TransactionLongevity::max_value(),
1402 propagate: true,
1403 })
1404 );
1405 assert_eq!(
1406 Pallet::<Test>::validate_unsigned(
1407 source,
1408 &ClaimsCall::claim_attest {
1409 dest: 1,
1410 ethereum_signature: EcdsaSignature([0; 65]),
1411 statement: StatementKind::Regular.to_text().to_vec()
1412 }
1413 ),
1414 InvalidTransaction::Custom(ValidityError::InvalidEthereumSignature.into()).into(),
1415 );
1416
1417 let s = sig::<Test>(&bob(), &1u64.encode(), StatementKind::Regular.to_text());
1418 let call = ClaimsCall::claim_attest {
1419 dest: 1,
1420 ethereum_signature: s,
1421 statement: StatementKind::Regular.to_text().to_vec(),
1422 };
1423 assert_eq!(
1424 Pallet::<Test>::validate_unsigned(source, &call),
1425 InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(),
1426 );
1427
1428 let s = sig::<Test>(&dave(), &1u64.encode(), StatementKind::Saft.to_text());
1429 let call = ClaimsCall::claim_attest {
1430 dest: 1,
1431 ethereum_signature: s,
1432 statement: StatementKind::Regular.to_text().to_vec(),
1433 };
1434 assert_eq!(
1435 Pallet::<Test>::validate_unsigned(source, &call),
1436 InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(),
1437 );
1438
1439 let s = sig::<Test>(&dave(), &1u64.encode(), StatementKind::Saft.to_text());
1440 let call = ClaimsCall::claim_attest {
1441 dest: 1,
1442 ethereum_signature: s,
1443 statement: StatementKind::Saft.to_text().to_vec(),
1444 };
1445 assert_eq!(
1446 Pallet::<Test>::validate_unsigned(source, &call),
1447 InvalidTransaction::Custom(ValidityError::InvalidStatement.into()).into(),
1448 );
1449 });
1450 }
1451}
1452
1453#[cfg(feature = "runtime-benchmarks")]
1454mod benchmarking {
1455 use super::*;
1456 use crate::claims::Call;
1457 use frame_benchmarking::v2::*;
1458 use frame_support::{
1459 dispatch::{DispatchInfo, GetDispatchInfo},
1460 traits::UnfilteredDispatchable,
1461 };
1462 use frame_system::RawOrigin;
1463 use secp_utils::*;
1464 use sp_runtime::{
1465 traits::{DispatchTransaction, ValidateUnsigned},
1466 DispatchResult,
1467 };
1468
1469 const SEED: u32 = 0;
1470
1471 const MAX_CLAIMS: u32 = 10_000;
1472 const VALUE: u32 = 1_000_000;
1473
1474 fn create_claim<T: Config>(input: u32) -> DispatchResult {
1475 let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&input.encode())).unwrap();
1476 let eth_address = eth(&secret_key);
1477 let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into()));
1478 super::Pallet::<T>::mint_claim(
1479 RawOrigin::Root.into(),
1480 eth_address,
1481 VALUE.into(),
1482 vesting,
1483 None,
1484 )?;
1485 Ok(())
1486 }
1487
1488 fn create_claim_attest<T: Config>(input: u32) -> DispatchResult {
1489 let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&input.encode())).unwrap();
1490 let eth_address = eth(&secret_key);
1491 let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into()));
1492 super::Pallet::<T>::mint_claim(
1493 RawOrigin::Root.into(),
1494 eth_address,
1495 VALUE.into(),
1496 vesting,
1497 Some(Default::default()),
1498 )?;
1499 Ok(())
1500 }
1501
1502 #[benchmarks(
1503 where
1504 <T as frame_system::Config>::RuntimeCall: IsSubType<Call<T>> + From<Call<T>>,
1505 <T as frame_system::Config>::RuntimeCall: Dispatchable<Info = DispatchInfo> + GetDispatchInfo,
1506 <<T as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin: AsSystemOriginSigner<T::AccountId> + AsTransactionAuthorizedOrigin + Clone,
1507 <<T as frame_system::Config>::RuntimeCall as Dispatchable>::PostInfo: Default,
1508 )]
1509 mod benchmarks {
1510 use super::*;
1511
1512 #[benchmark]
1514 fn claim() -> Result<(), BenchmarkError> {
1515 let c = MAX_CLAIMS;
1516 for _ in 0..c / 2 {
1517 create_claim::<T>(c)?;
1518 create_claim_attest::<T>(u32::MAX - c)?;
1519 }
1520 let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&c.encode())).unwrap();
1521 let eth_address = eth(&secret_key);
1522 let account: T::AccountId = account("user", c, SEED);
1523 let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into()));
1524 let signature = sig::<T>(&secret_key, &account.encode(), &[][..]);
1525 super::Pallet::<T>::mint_claim(
1526 RawOrigin::Root.into(),
1527 eth_address,
1528 VALUE.into(),
1529 vesting,
1530 None,
1531 )?;
1532 assert_eq!(Claims::<T>::get(eth_address), Some(VALUE.into()));
1533 let source = sp_runtime::transaction_validity::TransactionSource::External;
1534 let call_enc =
1535 Call::<T>::claim { dest: account.clone(), ethereum_signature: signature.clone() }
1536 .encode();
1537
1538 #[block]
1539 {
1540 let call = <Call<T> as Decode>::decode(&mut &*call_enc)
1541 .expect("call is encoded above, encoding must be correct");
1542 super::Pallet::<T>::validate_unsigned(source, &call)
1543 .map_err(|e| -> &'static str { e.into() })?;
1544 call.dispatch_bypass_filter(RawOrigin::None.into())?;
1545 }
1546
1547 assert_eq!(Claims::<T>::get(eth_address), None);
1548 Ok(())
1549 }
1550
1551 #[benchmark]
1553 fn mint_claim() -> Result<(), BenchmarkError> {
1554 let c = MAX_CLAIMS;
1555 for _ in 0..c / 2 {
1556 create_claim::<T>(c)?;
1557 create_claim_attest::<T>(u32::MAX - c)?;
1558 }
1559 let eth_address = account("eth_address", 0, SEED);
1560 let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into()));
1561 let statement = StatementKind::Regular;
1562
1563 #[extrinsic_call]
1564 _(RawOrigin::Root, eth_address, VALUE.into(), vesting, Some(statement));
1565
1566 assert_eq!(Claims::<T>::get(eth_address), Some(VALUE.into()));
1567 Ok(())
1568 }
1569
1570 #[benchmark]
1572 fn claim_attest() -> Result<(), BenchmarkError> {
1573 let c = MAX_CLAIMS;
1574 for _ in 0..c / 2 {
1575 create_claim::<T>(c)?;
1576 create_claim_attest::<T>(u32::MAX - c)?;
1577 }
1578 let attest_c = u32::MAX - c;
1580 let secret_key =
1581 libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap();
1582 let eth_address = eth(&secret_key);
1583 let account: T::AccountId = account("user", c, SEED);
1584 let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into()));
1585 let statement = StatementKind::Regular;
1586 let signature = sig::<T>(&secret_key, &account.encode(), statement.to_text());
1587 super::Pallet::<T>::mint_claim(
1588 RawOrigin::Root.into(),
1589 eth_address,
1590 VALUE.into(),
1591 vesting,
1592 Some(statement),
1593 )?;
1594 assert_eq!(Claims::<T>::get(eth_address), Some(VALUE.into()));
1595 let call_enc = Call::<T>::claim_attest {
1596 dest: account.clone(),
1597 ethereum_signature: signature.clone(),
1598 statement: StatementKind::Regular.to_text().to_vec(),
1599 }
1600 .encode();
1601 let source = sp_runtime::transaction_validity::TransactionSource::External;
1602
1603 #[block]
1604 {
1605 let call = <Call<T> as Decode>::decode(&mut &*call_enc)
1606 .expect("call is encoded above, encoding must be correct");
1607 super::Pallet::<T>::validate_unsigned(source, &call)
1608 .map_err(|e| -> &'static str { e.into() })?;
1609 call.dispatch_bypass_filter(RawOrigin::None.into())?;
1610 }
1611
1612 assert_eq!(Claims::<T>::get(eth_address), None);
1613 Ok(())
1614 }
1615
1616 #[benchmark]
1618 fn attest() -> Result<(), BenchmarkError> {
1619 let c = MAX_CLAIMS;
1620 for _ in 0..c / 2 {
1621 create_claim::<T>(c)?;
1622 create_claim_attest::<T>(u32::MAX - c)?;
1623 }
1624 let attest_c = u32::MAX - c;
1625 let secret_key =
1626 libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap();
1627 let eth_address = eth(&secret_key);
1628 let account: T::AccountId = account("user", c, SEED);
1629 let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into()));
1630 let statement = StatementKind::Regular;
1631 super::Pallet::<T>::mint_claim(
1632 RawOrigin::Root.into(),
1633 eth_address,
1634 VALUE.into(),
1635 vesting,
1636 Some(statement),
1637 )?;
1638 Preclaims::<T>::insert(&account, eth_address);
1639 assert_eq!(Claims::<T>::get(eth_address), Some(VALUE.into()));
1640
1641 let stmt = StatementKind::Regular.to_text().to_vec();
1642
1643 #[extrinsic_call]
1644 _(RawOrigin::Signed(account), stmt);
1645
1646 assert_eq!(Claims::<T>::get(eth_address), None);
1647 Ok(())
1648 }
1649
1650 #[benchmark]
1651 fn move_claim() -> Result<(), BenchmarkError> {
1652 let c = MAX_CLAIMS;
1653 for _ in 0..c / 2 {
1654 create_claim::<T>(c)?;
1655 create_claim_attest::<T>(u32::MAX - c)?;
1656 }
1657 let attest_c = u32::MAX - c;
1658 let secret_key =
1659 libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap();
1660 let eth_address = eth(&secret_key);
1661
1662 let new_secret_key =
1663 libsecp256k1::SecretKey::parse(&keccak_256(&(u32::MAX / 2).encode())).unwrap();
1664 let new_eth_address = eth(&new_secret_key);
1665
1666 let account: T::AccountId = account("user", c, SEED);
1667 Preclaims::<T>::insert(&account, eth_address);
1668
1669 assert!(Claims::<T>::contains_key(eth_address));
1670 assert!(!Claims::<T>::contains_key(new_eth_address));
1671
1672 #[extrinsic_call]
1673 _(RawOrigin::Root, eth_address, new_eth_address, Some(account));
1674
1675 assert!(!Claims::<T>::contains_key(eth_address));
1676 assert!(Claims::<T>::contains_key(new_eth_address));
1677 Ok(())
1678 }
1679
1680 #[benchmark(extra)]
1682 fn keccak256(i: Linear<0, 10_000>) {
1683 let bytes = (i).encode();
1684
1685 #[block]
1686 {
1687 for _ in 0..i {
1688 let _hash = keccak_256(&bytes);
1689 }
1690 }
1691 }
1692
1693 #[benchmark(extra)]
1695 fn eth_recover(i: Linear<0, 1_000>) {
1696 let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&i.encode())).unwrap();
1698 let account: T::AccountId = account("user", i, SEED);
1699 let signature = sig::<T>(&secret_key, &account.encode(), &[][..]);
1700 let data = account.using_encoded(to_ascii_hex);
1701 let extra = StatementKind::default().to_text();
1702
1703 #[block]
1704 {
1705 for _ in 0..i {
1706 assert!(super::Pallet::<T>::eth_recover(&signature, &data, extra).is_some());
1707 }
1708 }
1709 }
1710
1711 #[benchmark]
1712 fn prevalidate_attests() -> Result<(), BenchmarkError> {
1713 let c = MAX_CLAIMS;
1714 for _ in 0..c / 2 {
1715 create_claim::<T>(c)?;
1716 create_claim_attest::<T>(u32::MAX - c)?;
1717 }
1718 let ext = PrevalidateAttests::<T>::new();
1719 let call = super::Call::attest { statement: StatementKind::Regular.to_text().to_vec() };
1720 let call: <T as frame_system::Config>::RuntimeCall = call.into();
1721 let info = call.get_dispatch_info();
1722 let attest_c = u32::MAX - c;
1723 let secret_key =
1724 libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap();
1725 let eth_address = eth(&secret_key);
1726 let account: T::AccountId = account("user", c, SEED);
1727 let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into()));
1728 let statement = StatementKind::Regular;
1729 super::Pallet::<T>::mint_claim(
1730 RawOrigin::Root.into(),
1731 eth_address,
1732 VALUE.into(),
1733 vesting,
1734 Some(statement),
1735 )?;
1736 Preclaims::<T>::insert(&account, eth_address);
1737 assert_eq!(Claims::<T>::get(eth_address), Some(VALUE.into()));
1738
1739 #[block]
1740 {
1741 assert!(ext
1742 .test_run(RawOrigin::Signed(account).into(), &call, &info, 0, 0, |_| {
1743 Ok(Default::default())
1744 })
1745 .unwrap()
1746 .is_ok());
1747 }
1748
1749 Ok(())
1750 }
1751
1752 impl_benchmark_test_suite!(
1753 Pallet,
1754 crate::claims::tests::new_test_ext(),
1755 crate::claims::tests::Test,
1756 );
1757 }
1758}