polkadot_runtime_common/claims/
mod.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, DecodeWithMemTracking, 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	DecodeWithMemTracking,
89	Clone,
90	Copy,
91	Eq,
92	PartialEq,
93	RuntimeDebug,
94	TypeInfo,
95	Serialize,
96	Deserialize,
97	MaxEncodedLen,
98)]
99pub enum StatementKind {
100	/// Statement required to be made by non-SAFT holders.
101	Regular,
102	/// Statement required to be made by SAFT holders.
103	Saft,
104}
105
106impl StatementKind {
107	/// Convert this to the (English) statement it represents.
108	fn to_text(self) -> &'static [u8] {
109		match self {
110			StatementKind::Regular =>
111				&b"I hereby agree to the terms of the statement whose SHA-256 multihash is \
112				Qmc1XYqT6S39WNp2UeiRUrZichUWUPpGEThDE6dAb3f6Ny. (This may be found at the URL: \
113				https://statement.polkadot.network/regular.html)"[..],
114			StatementKind::Saft =>
115				&b"I hereby agree to the terms of the statement whose SHA-256 multihash is \
116				QmXEkMahfhHJPzT3RjkXiZVFi77ZeVeuxtAjhojGRNYckz. (This may be found at the URL: \
117				https://statement.polkadot.network/saft.html)"[..],
118		}
119	}
120}
121
122impl Default for StatementKind {
123	fn default() -> Self {
124		StatementKind::Regular
125	}
126}
127
128/// An Ethereum address (i.e. 20 bytes, used to represent an Ethereum account).
129///
130/// This gets serialized to the 0x-prefixed hex representation.
131#[derive(
132	Clone,
133	Copy,
134	PartialEq,
135	Eq,
136	Encode,
137	Decode,
138	DecodeWithMemTracking,
139	Default,
140	RuntimeDebug,
141	TypeInfo,
142	MaxEncodedLen,
143)]
144pub struct EthereumAddress(pub [u8; 20]);
145
146impl Serialize for EthereumAddress {
147	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
148	where
149		S: Serializer,
150	{
151		let hex: String = rustc_hex::ToHex::to_hex(&self.0[..]);
152		serializer.serialize_str(&format!("0x{}", hex))
153	}
154}
155
156impl<'de> Deserialize<'de> for EthereumAddress {
157	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
158	where
159		D: Deserializer<'de>,
160	{
161		let base_string = String::deserialize(deserializer)?;
162		let offset = if base_string.starts_with("0x") { 2 } else { 0 };
163		let s = &base_string[offset..];
164		if s.len() != 40 {
165			Err(serde::de::Error::custom(
166				"Bad length of Ethereum address (should be 42 including '0x')",
167			))?;
168		}
169		let raw: Vec<u8> = rustc_hex::FromHex::from_hex(s)
170			.map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?;
171		let mut r = Self::default();
172		r.0.copy_from_slice(&raw);
173		Ok(r)
174	}
175}
176
177#[derive(Encode, Decode, DecodeWithMemTracking, Clone, TypeInfo, MaxEncodedLen)]
178pub struct EcdsaSignature(pub [u8; 65]);
179
180impl PartialEq for EcdsaSignature {
181	fn eq(&self, other: &Self) -> bool {
182		&self.0[..] == &other.0[..]
183	}
184}
185
186impl core::fmt::Debug for EcdsaSignature {
187	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
188		write!(f, "EcdsaSignature({:?})", &self.0[..])
189	}
190}
191
192#[frame_support::pallet]
193pub mod pallet {
194	use super::*;
195	use frame_support::pallet_prelude::*;
196	use frame_system::pallet_prelude::*;
197
198	#[pallet::pallet]
199	pub struct Pallet<T>(_);
200
201	/// Configuration trait.
202	#[pallet::config]
203	pub trait Config: frame_system::Config {
204		/// The overarching event type.
205		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
206		type VestingSchedule: VestingSchedule<Self::AccountId, Moment = BlockNumberFor<Self>>;
207		#[pallet::constant]
208		type Prefix: Get<&'static [u8]>;
209		type MoveClaimOrigin: EnsureOrigin<Self::RuntimeOrigin>;
210		type WeightInfo: WeightInfo;
211	}
212
213	#[pallet::event]
214	#[pallet::generate_deposit(pub(super) fn deposit_event)]
215	pub enum Event<T: Config> {
216		/// Someone claimed some DOTs.
217		Claimed { who: T::AccountId, ethereum_address: EthereumAddress, amount: BalanceOf<T> },
218	}
219
220	#[pallet::error]
221	pub enum Error<T> {
222		/// Invalid Ethereum signature.
223		InvalidEthereumSignature,
224		/// Ethereum address has no claim.
225		SignerHasNoClaim,
226		/// Account ID sending transaction has no claim.
227		SenderHasNoClaim,
228		/// There's not enough in the pot to pay out some unvested amount. Generally implies a
229		/// logic error.
230		PotUnderflow,
231		/// A needed statement was not included.
232		InvalidStatement,
233		/// The account already has a vested balance.
234		VestedBalanceExists,
235	}
236
237	#[pallet::storage]
238	pub type Claims<T: Config> = StorageMap<_, Identity, EthereumAddress, BalanceOf<T>>;
239
240	#[pallet::storage]
241	pub type Total<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
242
243	/// Vesting schedule for a claim.
244	/// First balance is the total amount that should be held for vesting.
245	/// Second balance is how much should be unlocked per block.
246	/// The block number is when the vesting should start.
247	#[pallet::storage]
248	pub type Vesting<T: Config> =
249		StorageMap<_, Identity, EthereumAddress, (BalanceOf<T>, BalanceOf<T>, BlockNumberFor<T>)>;
250
251	/// The statement kind that must be signed, if any.
252	#[pallet::storage]
253	pub type Signing<T> = StorageMap<_, Identity, EthereumAddress, StatementKind>;
254
255	/// Pre-claimed Ethereum accounts, by the Account ID that they are claimed to.
256	#[pallet::storage]
257	pub type Preclaims<T: Config> = StorageMap<_, Identity, T::AccountId, EthereumAddress>;
258
259	#[pallet::genesis_config]
260	#[derive(DefaultNoBound)]
261	pub struct GenesisConfig<T: Config> {
262		pub claims:
263			Vec<(EthereumAddress, BalanceOf<T>, Option<T::AccountId>, Option<StatementKind>)>,
264		pub vesting: Vec<(EthereumAddress, (BalanceOf<T>, BalanceOf<T>, BlockNumberFor<T>))>,
265	}
266
267	#[pallet::genesis_build]
268	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
269		fn build(&self) {
270			// build `Claims`
271			self.claims.iter().map(|(a, b, _, _)| (*a, *b)).for_each(|(a, b)| {
272				Claims::<T>::insert(a, b);
273			});
274			// build `Total`
275			Total::<T>::put(
276				self.claims
277					.iter()
278					.fold(Zero::zero(), |acc: BalanceOf<T>, &(_, b, _, _)| acc + b),
279			);
280			// build `Vesting`
281			self.vesting.iter().for_each(|(k, v)| {
282				Vesting::<T>::insert(k, v);
283			});
284			// build `Signing`
285			self.claims
286				.iter()
287				.filter_map(|(a, _, _, s)| Some((*a, (*s)?)))
288				.for_each(|(a, s)| {
289					Signing::<T>::insert(a, s);
290				});
291			// build `Preclaims`
292			self.claims.iter().filter_map(|(a, _, i, _)| Some((i.clone()?, *a))).for_each(
293				|(i, a)| {
294					Preclaims::<T>::insert(i, a);
295				},
296			);
297		}
298	}
299
300	#[pallet::hooks]
301	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
302
303	#[pallet::call]
304	impl<T: Config> Pallet<T> {
305		/// Make a claim to collect your DOTs.
306		///
307		/// The dispatch origin for this call must be _None_.
308		///
309		/// Unsigned Validation:
310		/// A call to claim is deemed valid if the signature provided matches
311		/// the expected signed message of:
312		///
313		/// > Ethereum Signed Message:
314		/// > (configured prefix string)(address)
315		///
316		/// and `address` matches the `dest` account.
317		///
318		/// Parameters:
319		/// - `dest`: The destination account to payout the claim.
320		/// - `ethereum_signature`: The signature of an ethereum signed message matching the format
321		///   described above.
322		///
323		/// <weight>
324		/// The weight of this call is invariant over the input parameters.
325		/// Weight includes logic to validate unsigned `claim` call.
326		///
327		/// Total Complexity: O(1)
328		/// </weight>
329		#[pallet::call_index(0)]
330		#[pallet::weight(T::WeightInfo::claim())]
331		pub fn claim(
332			origin: OriginFor<T>,
333			dest: T::AccountId,
334			ethereum_signature: EcdsaSignature,
335		) -> DispatchResult {
336			ensure_none(origin)?;
337
338			let data = dest.using_encoded(to_ascii_hex);
339			let signer = Self::eth_recover(&ethereum_signature, &data, &[][..])
340				.ok_or(Error::<T>::InvalidEthereumSignature)?;
341			ensure!(Signing::<T>::get(&signer).is_none(), Error::<T>::InvalidStatement);
342
343			Self::process_claim(signer, dest)?;
344			Ok(())
345		}
346
347		/// Mint a new claim to collect DOTs.
348		///
349		/// The dispatch origin for this call must be _Root_.
350		///
351		/// Parameters:
352		/// - `who`: The Ethereum address allowed to collect this claim.
353		/// - `value`: The number of DOTs that will be claimed.
354		/// - `vesting_schedule`: An optional vesting schedule for these DOTs.
355		///
356		/// <weight>
357		/// The weight of this call is invariant over the input parameters.
358		/// We assume worst case that both vesting and statement is being inserted.
359		///
360		/// Total Complexity: O(1)
361		/// </weight>
362		#[pallet::call_index(1)]
363		#[pallet::weight(T::WeightInfo::mint_claim())]
364		pub fn mint_claim(
365			origin: OriginFor<T>,
366			who: EthereumAddress,
367			value: BalanceOf<T>,
368			vesting_schedule: Option<(BalanceOf<T>, BalanceOf<T>, BlockNumberFor<T>)>,
369			statement: Option<StatementKind>,
370		) -> DispatchResult {
371			ensure_root(origin)?;
372
373			Total::<T>::mutate(|t| *t += value);
374			Claims::<T>::insert(who, value);
375			if let Some(vs) = vesting_schedule {
376				Vesting::<T>::insert(who, vs);
377			}
378			if let Some(s) = statement {
379				Signing::<T>::insert(who, s);
380			}
381			Ok(())
382		}
383
384		/// Make a claim to collect your DOTs by signing a statement.
385		///
386		/// The dispatch origin for this call must be _None_.
387		///
388		/// Unsigned Validation:
389		/// A call to `claim_attest` is deemed valid if the signature provided matches
390		/// the expected signed message of:
391		///
392		/// > Ethereum Signed Message:
393		/// > (configured prefix string)(address)(statement)
394		///
395		/// and `address` matches the `dest` account; the `statement` must match that which is
396		/// expected according to your purchase arrangement.
397		///
398		/// Parameters:
399		/// - `dest`: The destination account to payout the claim.
400		/// - `ethereum_signature`: The signature of an ethereum signed message matching the format
401		///   described above.
402		/// - `statement`: The identity of the statement which is being attested to in the
403		///   signature.
404		///
405		/// <weight>
406		/// The weight of this call is invariant over the input parameters.
407		/// Weight includes logic to validate unsigned `claim_attest` call.
408		///
409		/// Total Complexity: O(1)
410		/// </weight>
411		#[pallet::call_index(2)]
412		#[pallet::weight(T::WeightInfo::claim_attest())]
413		pub fn claim_attest(
414			origin: OriginFor<T>,
415			dest: T::AccountId,
416			ethereum_signature: EcdsaSignature,
417			statement: Vec<u8>,
418		) -> DispatchResult {
419			ensure_none(origin)?;
420
421			let data = dest.using_encoded(to_ascii_hex);
422			let signer = Self::eth_recover(&ethereum_signature, &data, &statement)
423				.ok_or(Error::<T>::InvalidEthereumSignature)?;
424			if let Some(s) = Signing::<T>::get(signer) {
425				ensure!(s.to_text() == &statement[..], Error::<T>::InvalidStatement);
426			}
427			Self::process_claim(signer, dest)?;
428			Ok(())
429		}
430
431		/// Attest to a statement, needed to finalize the claims process.
432		///
433		/// WARNING: Insecure unless your chain includes `PrevalidateAttests` as a
434		/// `TransactionExtension`.
435		///
436		/// Unsigned Validation:
437		/// A call to attest is deemed valid if the sender has a `Preclaim` registered
438		/// and provides a `statement` which is expected for the account.
439		///
440		/// Parameters:
441		/// - `statement`: The identity of the statement which is being attested to in the
442		///   signature.
443		///
444		/// <weight>
445		/// The weight of this call is invariant over the input parameters.
446		/// Weight includes logic to do pre-validation on `attest` call.
447		///
448		/// Total Complexity: O(1)
449		/// </weight>
450		#[pallet::call_index(3)]
451		#[pallet::weight((
452			T::WeightInfo::attest(),
453			DispatchClass::Normal,
454			Pays::No
455		))]
456		pub fn attest(origin: OriginFor<T>, statement: Vec<u8>) -> DispatchResult {
457			let who = ensure_signed(origin)?;
458			let signer = Preclaims::<T>::get(&who).ok_or(Error::<T>::SenderHasNoClaim)?;
459			if let Some(s) = Signing::<T>::get(signer) {
460				ensure!(s.to_text() == &statement[..], Error::<T>::InvalidStatement);
461			}
462			Self::process_claim(signer, who.clone())?;
463			Preclaims::<T>::remove(&who);
464			Ok(())
465		}
466
467		#[pallet::call_index(4)]
468		#[pallet::weight(T::WeightInfo::move_claim())]
469		pub fn move_claim(
470			origin: OriginFor<T>,
471			old: EthereumAddress,
472			new: EthereumAddress,
473			maybe_preclaim: Option<T::AccountId>,
474		) -> DispatchResultWithPostInfo {
475			T::MoveClaimOrigin::try_origin(origin).map(|_| ()).or_else(ensure_root)?;
476
477			Claims::<T>::take(&old).map(|c| Claims::<T>::insert(&new, c));
478			Vesting::<T>::take(&old).map(|c| Vesting::<T>::insert(&new, c));
479			Signing::<T>::take(&old).map(|c| Signing::<T>::insert(&new, c));
480			maybe_preclaim.map(|preclaim| {
481				Preclaims::<T>::mutate(&preclaim, |maybe_o| {
482					if maybe_o.as_ref().map_or(false, |o| o == &old) {
483						*maybe_o = Some(new)
484					}
485				})
486			});
487			Ok(Pays::No.into())
488		}
489	}
490
491	#[pallet::validate_unsigned]
492	impl<T: Config> ValidateUnsigned for Pallet<T> {
493		type Call = Call<T>;
494
495		fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
496			const PRIORITY: u64 = 100;
497
498			let (maybe_signer, maybe_statement) = match call {
499				// <weight>
500				// The weight of this logic is included in the `claim` dispatchable.
501				// </weight>
502				Call::claim { dest: account, ethereum_signature } => {
503					let data = account.using_encoded(to_ascii_hex);
504					(Self::eth_recover(&ethereum_signature, &data, &[][..]), None)
505				},
506				// <weight>
507				// The weight of this logic is included in the `claim_attest` dispatchable.
508				// </weight>
509				Call::claim_attest { dest: account, ethereum_signature, statement } => {
510					let data = account.using_encoded(to_ascii_hex);
511					(
512						Self::eth_recover(&ethereum_signature, &data, &statement),
513						Some(statement.as_slice()),
514					)
515				},
516				_ => return Err(InvalidTransaction::Call.into()),
517			};
518
519			let signer = maybe_signer.ok_or(InvalidTransaction::Custom(
520				ValidityError::InvalidEthereumSignature.into(),
521			))?;
522
523			let e = InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into());
524			ensure!(Claims::<T>::contains_key(&signer), e);
525
526			let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into());
527			match Signing::<T>::get(signer) {
528				None => ensure!(maybe_statement.is_none(), e),
529				Some(s) => ensure!(Some(s.to_text()) == maybe_statement, e),
530			}
531
532			Ok(ValidTransaction {
533				priority: PRIORITY,
534				requires: vec![],
535				provides: vec![("claims", signer).encode()],
536				longevity: TransactionLongevity::max_value(),
537				propagate: true,
538			})
539		}
540	}
541}
542
543/// Converts the given binary data into ASCII-encoded hex. It will be twice the length.
544fn to_ascii_hex(data: &[u8]) -> Vec<u8> {
545	let mut r = Vec::with_capacity(data.len() * 2);
546	let mut push_nibble = |n| r.push(if n < 10 { b'0' + n } else { b'a' - 10 + n });
547	for &b in data.iter() {
548		push_nibble(b / 16);
549		push_nibble(b % 16);
550	}
551	r
552}
553
554impl<T: Config> Pallet<T> {
555	// Constructs the message that Ethereum RPC's `personal_sign` and `eth_sign` would sign.
556	fn ethereum_signable_message(what: &[u8], extra: &[u8]) -> Vec<u8> {
557		let prefix = T::Prefix::get();
558		let mut l = prefix.len() + what.len() + extra.len();
559		let mut rev = Vec::new();
560		while l > 0 {
561			rev.push(b'0' + (l % 10) as u8);
562			l /= 10;
563		}
564		let mut v = b"\x19Ethereum Signed Message:\n".to_vec();
565		v.extend(rev.into_iter().rev());
566		v.extend_from_slice(prefix);
567		v.extend_from_slice(what);
568		v.extend_from_slice(extra);
569		v
570	}
571
572	// Attempts to recover the Ethereum address from a message signature signed by using
573	// the Ethereum RPC's `personal_sign` and `eth_sign`.
574	fn eth_recover(s: &EcdsaSignature, what: &[u8], extra: &[u8]) -> Option<EthereumAddress> {
575		let msg = keccak_256(&Self::ethereum_signable_message(what, extra));
576		let mut res = EthereumAddress::default();
577		res.0
578			.copy_from_slice(&keccak_256(&secp256k1_ecdsa_recover(&s.0, &msg).ok()?[..])[12..]);
579		Some(res)
580	}
581
582	fn process_claim(signer: EthereumAddress, dest: T::AccountId) -> sp_runtime::DispatchResult {
583		let balance_due = Claims::<T>::get(&signer).ok_or(Error::<T>::SignerHasNoClaim)?;
584
585		let new_total =
586			Total::<T>::get().checked_sub(&balance_due).ok_or(Error::<T>::PotUnderflow)?;
587
588		let vesting = Vesting::<T>::get(&signer);
589		if vesting.is_some() && T::VestingSchedule::vesting_balance(&dest).is_some() {
590			return Err(Error::<T>::VestedBalanceExists.into())
591		}
592
593		// We first need to deposit the balance to ensure that the account exists.
594		let _ = CurrencyOf::<T>::deposit_creating(&dest, balance_due);
595
596		// Check if this claim should have a vesting schedule.
597		if let Some(vs) = vesting {
598			// This can only fail if the account already has a vesting schedule,
599			// but this is checked above.
600			T::VestingSchedule::add_vesting_schedule(&dest, vs.0, vs.1, vs.2)
601				.expect("No other vesting schedule exists, as checked above; qed");
602		}
603
604		Total::<T>::put(new_total);
605		Claims::<T>::remove(&signer);
606		Vesting::<T>::remove(&signer);
607		Signing::<T>::remove(&signer);
608
609		// Let's deposit an event to let the outside world know this happened.
610		Self::deposit_event(Event::<T>::Claimed {
611			who: dest,
612			ethereum_address: signer,
613			amount: balance_due,
614		});
615
616		Ok(())
617	}
618}
619
620/// Validate `attest` calls prior to execution. Needed to avoid a DoS attack since they are
621/// otherwise free to place on chain.
622#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)]
623#[scale_info(skip_type_params(T))]
624pub struct PrevalidateAttests<T>(core::marker::PhantomData<fn(T)>);
625
626impl<T: Config> Debug for PrevalidateAttests<T>
627where
628	<T as frame_system::Config>::RuntimeCall: IsSubType<Call<T>>,
629{
630	#[cfg(feature = "std")]
631	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
632		write!(f, "PrevalidateAttests")
633	}
634
635	#[cfg(not(feature = "std"))]
636	fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
637		Ok(())
638	}
639}
640
641impl<T: Config> PrevalidateAttests<T>
642where
643	<T as frame_system::Config>::RuntimeCall: IsSubType<Call<T>>,
644{
645	/// Create new `TransactionExtension` to check runtime version.
646	pub fn new() -> Self {
647		Self(core::marker::PhantomData)
648	}
649}
650
651impl<T: Config> TransactionExtension<T::RuntimeCall> for PrevalidateAttests<T>
652where
653	<T as frame_system::Config>::RuntimeCall: IsSubType<Call<T>>,
654	<<T as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
655		AsSystemOriginSigner<T::AccountId> + AsTransactionAuthorizedOrigin + Clone,
656{
657	const IDENTIFIER: &'static str = "PrevalidateAttests";
658	type Implicit = ();
659	type Pre = ();
660	type Val = ();
661
662	fn weight(&self, call: &T::RuntimeCall) -> Weight {
663		if let Some(Call::attest { .. }) = call.is_sub_type() {
664			T::WeightInfo::prevalidate_attests()
665		} else {
666			Weight::zero()
667		}
668	}
669
670	fn validate(
671		&self,
672		origin: <T::RuntimeCall as Dispatchable>::RuntimeOrigin,
673		call: &T::RuntimeCall,
674		_info: &DispatchInfoOf<T::RuntimeCall>,
675		_len: usize,
676		_self_implicit: Self::Implicit,
677		_inherited_implication: &impl Encode,
678		_source: TransactionSource,
679	) -> Result<
680		(ValidTransaction, Self::Val, <T::RuntimeCall as Dispatchable>::RuntimeOrigin),
681		TransactionValidityError,
682	> {
683		if let Some(Call::attest { statement: attested_statement }) = call.is_sub_type() {
684			let who = origin.as_system_origin_signer().ok_or(InvalidTransaction::BadSigner)?;
685			let signer = Preclaims::<T>::get(who)
686				.ok_or(InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()))?;
687			if let Some(s) = Signing::<T>::get(signer) {
688				let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into());
689				ensure!(&attested_statement[..] == s.to_text(), e);
690			}
691		}
692		Ok((ValidTransaction::default(), (), origin))
693	}
694
695	impl_tx_ext_default!(T::RuntimeCall; prepare);
696}
697
698#[cfg(any(test, feature = "runtime-benchmarks"))]
699mod secp_utils {
700	use super::*;
701
702	pub fn public(secret: &libsecp256k1::SecretKey) -> libsecp256k1::PublicKey {
703		libsecp256k1::PublicKey::from_secret_key(secret)
704	}
705	pub fn eth(secret: &libsecp256k1::SecretKey) -> EthereumAddress {
706		let mut res = EthereumAddress::default();
707		res.0.copy_from_slice(&keccak_256(&public(secret).serialize()[1..65])[12..]);
708		res
709	}
710	pub fn sig<T: Config>(
711		secret: &libsecp256k1::SecretKey,
712		what: &[u8],
713		extra: &[u8],
714	) -> EcdsaSignature {
715		let msg = keccak_256(&super::Pallet::<T>::ethereum_signable_message(
716			&to_ascii_hex(what)[..],
717			extra,
718		));
719		let (sig, recovery_id) = libsecp256k1::sign(&libsecp256k1::Message::parse(&msg), secret);
720		let mut r = [0u8; 65];
721		r[0..64].copy_from_slice(&sig.serialize()[..]);
722		r[64] = recovery_id.serialize();
723		EcdsaSignature(r)
724	}
725}
726
727#[cfg(test)]
728mod mock;
729
730#[cfg(test)]
731mod tests;
732
733#[cfg(feature = "runtime-benchmarks")]
734mod benchmarking;