polkadot_runtime_common/
claims.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Pallet to process claims from Ethereum addresses.
18
19#[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/// The kind of statement an account needs to make for a claim to be valid.
85#[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	/// Statement required to be made by non-SAFT holders.
100	Regular,
101	/// Statement required to be made by SAFT holders.
102	Saft,
103}
104
105impl StatementKind {
106	/// Convert this to the (English) statement it represents.
107	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/// An Ethereum address (i.e. 20 bytes, used to represent an Ethereum account).
128///
129/// This gets serialized to the 0x-prefixed hex representation.
130#[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	/// Configuration trait.
191	#[pallet::config]
192	pub trait Config: frame_system::Config {
193		/// The overarching event type.
194		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		/// Someone claimed some DOTs.
206		Claimed { who: T::AccountId, ethereum_address: EthereumAddress, amount: BalanceOf<T> },
207	}
208
209	#[pallet::error]
210	pub enum Error<T> {
211		/// Invalid Ethereum signature.
212		InvalidEthereumSignature,
213		/// Ethereum address has no claim.
214		SignerHasNoClaim,
215		/// Account ID sending transaction has no claim.
216		SenderHasNoClaim,
217		/// There's not enough in the pot to pay out some unvested amount. Generally implies a
218		/// logic error.
219		PotUnderflow,
220		/// A needed statement was not included.
221		InvalidStatement,
222		/// The account already has a vested balance.
223		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	/// Vesting schedule for a claim.
233	/// First balance is the total amount that should be held for vesting.
234	/// Second balance is how much should be unlocked per block.
235	/// The block number is when the vesting should start.
236	#[pallet::storage]
237	pub type Vesting<T: Config> =
238		StorageMap<_, Identity, EthereumAddress, (BalanceOf<T>, BalanceOf<T>, BlockNumberFor<T>)>;
239
240	/// The statement kind that must be signed, if any.
241	#[pallet::storage]
242	pub(super) type Signing<T> = StorageMap<_, Identity, EthereumAddress, StatementKind>;
243
244	/// Pre-claimed Ethereum accounts, by the Account ID that they are claimed to.
245	#[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			// build `Claims`
260			self.claims.iter().map(|(a, b, _, _)| (*a, *b)).for_each(|(a, b)| {
261				Claims::<T>::insert(a, b);
262			});
263			// build `Total`
264			Total::<T>::put(
265				self.claims
266					.iter()
267					.fold(Zero::zero(), |acc: BalanceOf<T>, &(_, b, _, _)| acc + b),
268			);
269			// build `Vesting`
270			self.vesting.iter().for_each(|(k, v)| {
271				Vesting::<T>::insert(k, v);
272			});
273			// build `Signing`
274			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			// build `Preclaims`
281			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		/// Make a claim to collect your DOTs.
295		///
296		/// The dispatch origin for this call must be _None_.
297		///
298		/// Unsigned Validation:
299		/// A call to claim is deemed valid if the signature provided matches
300		/// the expected signed message of:
301		///
302		/// > Ethereum Signed Message:
303		/// > (configured prefix string)(address)
304		///
305		/// and `address` matches the `dest` account.
306		///
307		/// Parameters:
308		/// - `dest`: The destination account to payout the claim.
309		/// - `ethereum_signature`: The signature of an ethereum signed message matching the format
310		///   described above.
311		///
312		/// <weight>
313		/// The weight of this call is invariant over the input parameters.
314		/// Weight includes logic to validate unsigned `claim` call.
315		///
316		/// Total Complexity: O(1)
317		/// </weight>
318		#[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(&ethereum_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		/// Mint a new claim to collect DOTs.
337		///
338		/// The dispatch origin for this call must be _Root_.
339		///
340		/// Parameters:
341		/// - `who`: The Ethereum address allowed to collect this claim.
342		/// - `value`: The number of DOTs that will be claimed.
343		/// - `vesting_schedule`: An optional vesting schedule for these DOTs.
344		///
345		/// <weight>
346		/// The weight of this call is invariant over the input parameters.
347		/// We assume worst case that both vesting and statement is being inserted.
348		///
349		/// Total Complexity: O(1)
350		/// </weight>
351		#[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		/// Make a claim to collect your DOTs by signing a statement.
374		///
375		/// The dispatch origin for this call must be _None_.
376		///
377		/// Unsigned Validation:
378		/// A call to `claim_attest` is deemed valid if the signature provided matches
379		/// the expected signed message of:
380		///
381		/// > Ethereum Signed Message:
382		/// > (configured prefix string)(address)(statement)
383		///
384		/// and `address` matches the `dest` account; the `statement` must match that which is
385		/// expected according to your purchase arrangement.
386		///
387		/// Parameters:
388		/// - `dest`: The destination account to payout the claim.
389		/// - `ethereum_signature`: The signature of an ethereum signed message matching the format
390		///   described above.
391		/// - `statement`: The identity of the statement which is being attested to in the
392		///   signature.
393		///
394		/// <weight>
395		/// The weight of this call is invariant over the input parameters.
396		/// Weight includes logic to validate unsigned `claim_attest` call.
397		///
398		/// Total Complexity: O(1)
399		/// </weight>
400		#[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(&ethereum_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		/// Attest to a statement, needed to finalize the claims process.
421		///
422		/// WARNING: Insecure unless your chain includes `PrevalidateAttests` as a
423		/// `TransactionExtension`.
424		///
425		/// Unsigned Validation:
426		/// A call to attest is deemed valid if the sender has a `Preclaim` registered
427		/// and provides a `statement` which is expected for the account.
428		///
429		/// Parameters:
430		/// - `statement`: The identity of the statement which is being attested to in the
431		///   signature.
432		///
433		/// <weight>
434		/// The weight of this call is invariant over the input parameters.
435		/// Weight includes logic to do pre-validation on `attest` call.
436		///
437		/// Total Complexity: O(1)
438		/// </weight>
439		#[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				// <weight>
489				// The weight of this logic is included in the `claim` dispatchable.
490				// </weight>
491				Call::claim { dest: account, ethereum_signature } => {
492					let data = account.using_encoded(to_ascii_hex);
493					(Self::eth_recover(&ethereum_signature, &data, &[][..]), None)
494				},
495				// <weight>
496				// The weight of this logic is included in the `claim_attest` dispatchable.
497				// </weight>
498				Call::claim_attest { dest: account, ethereum_signature, statement } => {
499					let data = account.using_encoded(to_ascii_hex);
500					(
501						Self::eth_recover(&ethereum_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
532/// Converts the given binary data into ASCII-encoded hex. It will be twice the length.
533fn 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	// Constructs the message that Ethereum RPC's `personal_sign` and `eth_sign` would sign.
545	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	// Attempts to recover the Ethereum address from a message signature signed by using
562	// the Ethereum RPC's `personal_sign` and `eth_sign`.
563	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		// We first need to deposit the balance to ensure that the account exists.
583		let _ = CurrencyOf::<T>::deposit_creating(&dest, balance_due);
584
585		// Check if this claim should have a vesting schedule.
586		if let Some(vs) = vesting {
587			// This can only fail if the account already has a vesting schedule,
588			// but this is checked above.
589			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		// Let's deposit an event to let the outside world know this happened.
599		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/// Validate `attest` calls prior to execution. Needed to avoid a DoS attack since they are
610/// otherwise free to place on chain.
611#[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	/// Create new `TransactionExtension` to check runtime version.
635	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	// The testing primitives are very useful for avoiding having to work with signatures
725	// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required.
726	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	// This function basically just builds a genesis storage key/value store according to
819	// our desired mockup.
820	pub fn new_test_ext() -> sp_io::TestExternalities {
821		let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
822		// We use default for brevity, but you can configure as desired if needed.
823		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(&eth(&alice())), Some(100));
849			assert_eq!(claims::Claims::<Test>::get(&eth(&dave())), Some(200));
850			assert_eq!(claims::Claims::<Test>::get(&eth(&eve())), Some(300));
851			assert_eq!(claims::Claims::<Test>::get(&eth(&frank())), Some(400));
852			assert_eq!(claims::Claims::<Test>::get(&EthereumAddress::default()), None);
853			assert_eq!(claims::Vesting::<Test>::get(&eth(&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			// ^^^ we use ecdsa_recover, so an invalid signature just results in a random signer id
1004			// being recovered, which realistically will never have a claim.
1005
1006			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			// Alice's claim is 100
1056			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			// Eve's claim is 300 through Account 42
1063			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			// Make sure we can not transfer the vested balance.
1182			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			// A user is already vested
1280			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			// New total
1294			assert_eq!(claims::Total::<Test>::get(), total_claims() + 200);
1295
1296			// They should not be able to claim
1297			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			// "Pay RUSTs to the TEST account:2a00000000000000"
1342			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 `claim` including `validate_unsigned` logic.
1513		#[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 `mint_claim` when there already exists `c` claims in storage.
1552		#[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 `claim_attest` including `validate_unsigned` logic.
1571		#[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			// Crate signature
1579			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 `attest` including prevalidate logic.
1617		#[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 the time it takes to do `repeat` number of keccak256 hashes
1681		#[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 the time it takes to do `repeat` number of `eth_recover`
1694		#[benchmark(extra)]
1695		fn eth_recover(i: Linear<0, 1_000>) {
1696			// Crate signature
1697			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}