claims.rs 47.8 KB
Newer Older
Shawn Tabrizi's avatar
Shawn Tabrizi committed
1
// Copyright 2017-2020 Parity Technologies (UK) Ltd.
2
// This file is part of Polkadot.
Gav Wood's avatar
Gav Wood committed
3
4
5
6
7
8
9
10
11
12
13
14
15
16

// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Substrate.  If not, see <http://www.gnu.org/licenses/>.

17
//! Pallet to process claims from Ethereum addresses.
Gav Wood's avatar
Gav Wood committed
18

Shawn Tabrizi's avatar
Shawn Tabrizi committed
19
20
21
22
23
24
25
26
use frame_support::{
	ensure,
	traits::{Currency, Get, IsSubType, VestingSchedule},
	weights::Weight,
};
pub use pallet::*;
use parity_scale_codec::{Decode, Encode};
use primitives::v1::ValidityError;
Gav Wood's avatar
Gav Wood committed
27
#[cfg(feature = "std")]
Shawn Tabrizi's avatar
Shawn Tabrizi committed
28
29
use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256};
30
31
#[cfg(feature = "std")]
use sp_runtime::traits::Zero;
32
use sp_runtime::{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
33
	traits::{CheckedSub, DispatchInfoOf, SignedExtension},
34
	transaction_validity::{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
35
		InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,
Gavin Wood's avatar
Gavin Wood committed
36
	},
Shawn Tabrizi's avatar
Shawn Tabrizi committed
37
	RuntimeDebug,
38
};
Shawn Tabrizi's avatar
Shawn Tabrizi committed
39
use sp_std::{fmt::Debug, prelude::*};
40

Shawn Tabrizi's avatar
Shawn Tabrizi committed
41
42
43
type CurrencyOf<T> = <<T as Config>::VestingSchedule as VestingSchedule<
	<T as frame_system::Config>::AccountId,
>>::Currency;
44
type BalanceOf<T> = <CurrencyOf<T> as Currency<<T as frame_system::Config>::AccountId>>::Balance;
Gav Wood's avatar
Gav Wood committed
45

46
47
48
49
50
51
52
53
54
55
pub trait WeightInfo {
	fn claim() -> Weight;
	fn mint_claim() -> Weight;
	fn claim_attest() -> Weight;
	fn attest() -> Weight;
	fn move_claim() -> Weight;
}

pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
	fn claim() -> Weight {
		0
	}
	fn mint_claim() -> Weight {
		0
	}
	fn claim_attest() -> Weight {
		0
	}
	fn attest() -> Weight {
		0
	}
	fn move_claim() -> Weight {
		0
	}
Gav Wood's avatar
Gav Wood committed
71
72
}

Bernhard Schuster's avatar
Bernhard Schuster committed
73
/// The kind of statement an account needs to make for a claim to be valid.
74
75
76
#[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub enum StatementKind {
Gavin Wood's avatar
Gavin Wood committed
77
78
79
80
	/// Statement required to be made by non-SAFT holders.
	Regular,
	/// Statement required to be made by SAFT holders.
	Saft,
81
82
83
84
85
86
}

impl StatementKind {
	/// Convert this to the (English) statement it represents.
	fn to_text(self) -> &'static [u8] {
		match self {
Gavin Wood's avatar
Gavin Wood committed
87
88
89
90
91
92
93
94
			StatementKind::Regular =>
				&b"I hereby agree to the terms of the statement whose SHA-256 multihash is \
				Qmc1XYqT6S39WNp2UeiRUrZichUWUPpGEThDE6dAb3f6Ny. (This may be found at the URL: \
				https://statement.polkadot.network/regular.html)"[..],
			StatementKind::Saft =>
				&b"I hereby agree to the terms of the statement whose SHA-256 multihash is \
				QmXEkMahfhHJPzT3RjkXiZVFi77ZeVeuxtAjhojGRNYckz. (This may be found at the URL: \
				https://statement.polkadot.network/saft.html)"[..],
95
96
97
98
99
100
		}
	}
}

impl Default for StatementKind {
	fn default() -> Self {
Gavin Wood's avatar
Gavin Wood committed
101
		StatementKind::Regular
102
103
104
	}
}

105
106
107
/// An Ethereum address (i.e. 20 bytes, used to represent an Ethereum account).
///
/// This gets serialized to the 0x-prefixed hex representation.
Gavin Wood's avatar
Gavin Wood committed
108
#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, Default, RuntimeDebug)]
109
110
111
112
pub struct EthereumAddress([u8; 20]);

#[cfg(feature = "std")]
impl Serialize for EthereumAddress {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
113
114
115
116
	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: Serializer,
	{
117
118
119
120
121
122
123
		let hex: String = rustc_hex::ToHex::to_hex(&self.0[..]);
		serializer.serialize_str(&format!("0x{}", hex))
	}
}

#[cfg(feature = "std")]
impl<'de> Deserialize<'de> for EthereumAddress {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
124
125
126
127
	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
	where
		D: Deserializer<'de>,
	{
128
129
130
131
		let base_string = String::deserialize(deserializer)?;
		let offset = if base_string.starts_with("0x") { 2 } else { 0 };
		let s = &base_string[offset..];
		if s.len() != 40 {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
132
133
134
			Err(serde::de::Error::custom(
				"Bad length of Ethereum address (should be 42 including '0x')",
			))?;
135
136
137
138
139
140
141
142
		}
		let raw: Vec<u8> = rustc_hex::FromHex::from_hex(s)
			.map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?;
		let mut r = Self::default();
		r.0.copy_from_slice(&raw);
		Ok(r)
	}
}
143

144
145
#[derive(Encode, Decode, Clone)]
pub struct EcdsaSignature(pub [u8; 65]);
146

147
148
149
impl PartialEq for EcdsaSignature {
	fn eq(&self, other: &Self) -> bool {
		&self.0[..] == &other.0[..]
150
151
	}
}
Gav Wood's avatar
Gav Wood committed
152

153
154
impl sp_std::fmt::Debug for EcdsaSignature {
	fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result {
155
156
		write!(f, "EcdsaSignature({:?})", &self.0[..])
	}
157
158
}

159
160
#[frame_support::pallet]
pub mod pallet {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
161
	use super::*;
162
163
164
165
166
167
168
169
170
171
172
173
	use frame_support::pallet_prelude::*;
	use frame_system::pallet_prelude::*;

	#[pallet::pallet]
	#[pallet::generate_store(pub(super) trait Store)]
	pub struct Pallet<T>(_);

	/// Configuration trait.
	#[pallet::config]
	pub trait Config: frame_system::Config {
		/// The overarching event type.
		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
174
		type VestingSchedule: VestingSchedule<Self::AccountId, Moment = Self::BlockNumber>;
175
176
177
178
179
180
181
182
183
184
		#[pallet::constant]
		type Prefix: Get<&'static [u8]>;
		type MoveClaimOrigin: EnsureOrigin<Self::Origin>;
		type WeightInfo: WeightInfo;
	}

	#[pallet::event]
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
	#[pallet::metadata(T::AccountId = "AccountId", BalanceOf<T> = "Balance")]
	pub enum Event<T: Config> {
Denis_P's avatar
Denis_P committed
185
		/// Someone claimed some DOTs. `[who, ethereum_address, amount]`
186
		Claimed(T::AccountId, EthereumAddress, BalanceOf<T>),
Gav Wood's avatar
Gav Wood committed
187
188
	}

189
190
	#[pallet::error]
	pub enum Error<T> {
191
192
193
194
		/// Invalid Ethereum signature.
		InvalidEthereumSignature,
		/// Ethereum address has no claim.
		SignerHasNoClaim,
Denis_P's avatar
Denis_P committed
195
		/// Account ID sending transaction has no claim.
196
		SenderHasNoClaim,
Gavin Wood's avatar
Gavin Wood committed
197
198
199
		/// There's not enough in the pot to pay out some unvested amount. Generally implies a logic
		/// error.
		PotUnderflow,
200
201
		/// A needed statement was not included.
		InvalidStatement,
202
203
		/// The account already has a vested balance.
		VestedBalanceExists,
204
205
	}

206
207
208
	#[pallet::storage]
	#[pallet::getter(fn claims)]
	pub(super) type Claims<T: Config> = StorageMap<_, Identity, EthereumAddress, BalanceOf<T>>;
209

210
211
212
213
214
215
216
217
218
219
	#[pallet::storage]
	#[pallet::getter(fn total)]
	pub(super) type Total<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;

	/// Vesting schedule for a claim.
	/// First balance is the total amount that should be held for vesting.
	/// Second balance is how much should be unlocked per block.
	/// The block number is when the vesting should start.
	#[pallet::storage]
	#[pallet::getter(fn vesting)]
Shawn Tabrizi's avatar
Shawn Tabrizi committed
220
221
	pub(super) type Vesting<T: Config> =
		StorageMap<_, Identity, EthereumAddress, (BalanceOf<T>, BalanceOf<T>, T::BlockNumber)>;
222
223
224
225
226
227
228
229
230
231
232

	/// The statement kind that must be signed, if any.
	#[pallet::storage]
	pub(super) type Signing<T> = StorageMap<_, Identity, EthereumAddress, StatementKind>;

	/// Pre-claimed Ethereum accounts, by the Account ID that they are claimed to.
	#[pallet::storage]
	pub(super) type Preclaims<T: Config> = StorageMap<_, Identity, T::AccountId, EthereumAddress>;

	#[pallet::genesis_config]
	pub struct GenesisConfig<T: Config> {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
233
234
		pub claims:
			Vec<(EthereumAddress, BalanceOf<T>, Option<T::AccountId>, Option<StatementKind>)>,
235
		pub vesting: Vec<(EthereumAddress, (BalanceOf<T>, BalanceOf<T>, T::BlockNumber))>,
Gav Wood's avatar
Gav Wood committed
236
237
	}

238
239
240
	#[cfg(feature = "std")]
	impl<T: Config> Default for GenesisConfig<T> {
		fn default() -> Self {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
241
			GenesisConfig { claims: Default::default(), vesting: Default::default() }
242
243
		}
	}
244

245
246
247
248
	#[pallet::genesis_build]
	impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
		fn build(&self) {
			// build `Claims`
Shawn Tabrizi's avatar
Shawn Tabrizi committed
249
250
251
252
253
254
			self.claims
				.iter()
				.map(|(a, b, _, _)| (a.clone(), b.clone()))
				.for_each(|(a, b)| {
					Claims::<T>::insert(a, b);
				});
255
256
			// build `Total`
			Total::<T>::put(
Shawn Tabrizi's avatar
Shawn Tabrizi committed
257
258
259
				self.claims
					.iter()
					.fold(Zero::zero(), |acc: BalanceOf<T>, &(_, b, _, _)| acc + b),
260
261
			);
			// build `Vesting`
Shawn Tabrizi's avatar
Shawn Tabrizi committed
262
263
264
			self.vesting.iter().for_each(|(k, v)| {
				Vesting::<T>::insert(k, v);
			});
265
			// build `Signing`
Shawn Tabrizi's avatar
Shawn Tabrizi committed
266
267
			self.claims
				.iter()
268
269
270
271
272
				.filter_map(|(a, _, _, s)| Some((a.clone(), s.clone()?)))
				.for_each(|(a, s)| {
					Signing::<T>::insert(a, s);
				});
			// build `Preclaims`
Shawn Tabrizi's avatar
Shawn Tabrizi committed
273
274
			self.claims
				.iter()
275
276
277
278
279
280
				.filter_map(|(a, _, i, _)| Some((i.clone()?, a.clone())))
				.for_each(|(i, a)| {
					Preclaims::<T>::insert(i, a);
				});
		}
	}
281

282
283
	#[pallet::hooks]
	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
Gav Wood's avatar
Gav Wood committed
284

285
286
	#[pallet::call]
	impl<T: Config> Pallet<T> {
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
		/// Make a claim to collect your DOTs.
		///
		/// The dispatch origin for this call must be _None_.
		///
		/// Unsigned Validation:
		/// A call to claim is deemed valid if the signature provided matches
		/// the expected signed message of:
		///
		/// > Ethereum Signed Message:
		/// > (configured prefix string)(address)
		///
		/// and `address` matches the `dest` account.
		///
		/// Parameters:
		/// - `dest`: The destination account to payout the claim.
		/// - `ethereum_signature`: The signature of an ethereum signed message
		///    matching the format described above.
		///
		/// <weight>
		/// The weight of this call is invariant over the input parameters.
307
		/// Weight includes logic to validate unsigned `claim` call.
308
309
310
		///
		/// Total Complexity: O(1)
		/// </weight>
311
		#[pallet::weight(T::WeightInfo::claim())]
312
		pub fn claim(
313
314
			origin: OriginFor<T>,
			dest: T::AccountId,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
315
			ethereum_signature: EcdsaSignature,
316
		) -> DispatchResult {
317
			ensure_none(origin)?;
318

319
			let data = dest.using_encoded(to_ascii_hex);
320
			let signer = Self::eth_recover(&ethereum_signature, &data, &[][..])
321
				.ok_or(Error::<T>::InvalidEthereumSignature)?;
322
			ensure!(Signing::<T>::get(&signer).is_none(), Error::<T>::InvalidStatement);
323

324
			Self::process_claim(signer, dest)?;
325
			Ok(())
326
		}
Gavin Wood's avatar
Gavin Wood committed
327

328
329
330
331
332
333
334
335
336
337
338
		/// Mint a new claim to collect DOTs.
		///
		/// The dispatch origin for this call must be _Root_.
		///
		/// Parameters:
		/// - `who`: The Ethereum address allowed to collect this claim.
		/// - `value`: The number of DOTs that will be claimed.
		/// - `vesting_schedule`: An optional vesting schedule for these DOTs.
		///
		/// <weight>
		/// The weight of this call is invariant over the input parameters.
339
		/// We assume worst case that both vesting and statement is being inserted.
340
341
342
		///
		/// Total Complexity: O(1)
		/// </weight>
343
		#[pallet::weight(T::WeightInfo::mint_claim())]
344
		pub fn mint_claim(
345
			origin: OriginFor<T>,
346
347
348
			who: EthereumAddress,
			value: BalanceOf<T>,
			vesting_schedule: Option<(BalanceOf<T>, BalanceOf<T>, T::BlockNumber)>,
349
			statement: Option<StatementKind>,
350
		) -> DispatchResult {
Gavin Wood's avatar
Gavin Wood committed
351
352
353
354
			ensure_root(origin)?;

			<Total<T>>::mutate(|t| *t += value);
			<Claims<T>>::insert(who, value);
355
356
357
			if let Some(vs) = vesting_schedule {
				<Vesting<T>>::insert(who, vs);
			}
358
			if let Some(s) = statement {
359
				Signing::<T>::insert(who, s);
360
			}
361
			Ok(())
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
		}

		/// Make a claim to collect your DOTs by signing a statement.
		///
		/// The dispatch origin for this call must be _None_.
		///
		/// Unsigned Validation:
		/// A call to `claim_attest` is deemed valid if the signature provided matches
		/// the expected signed message of:
		///
		/// > Ethereum Signed Message:
		/// > (configured prefix string)(address)(statement)
		///
		/// and `address` matches the `dest` account; the `statement` must match that which is
		/// expected according to your purchase arrangement.
		///
		/// Parameters:
		/// - `dest`: The destination account to payout the claim.
		/// - `ethereum_signature`: The signature of an ethereum signed message
		///    matching the format described above.
		/// - `statement`: The identity of the statement which is being attested to in the signature.
		///
		/// <weight>
		/// The weight of this call is invariant over the input parameters.
386
		/// Weight includes logic to validate unsigned `claim_attest` call.
387
388
389
		///
		/// Total Complexity: O(1)
		/// </weight>
390
		#[pallet::weight(T::WeightInfo::claim_attest())]
391
		pub fn claim_attest(
392
			origin: OriginFor<T>,
393
394
395
			dest: T::AccountId,
			ethereum_signature: EcdsaSignature,
			statement: Vec<u8>,
396
		) -> DispatchResult {
397
398
399
400
401
			ensure_none(origin)?;

			let data = dest.using_encoded(to_ascii_hex);
			let signer = Self::eth_recover(&ethereum_signature, &data, &statement)
				.ok_or(Error::<T>::InvalidEthereumSignature)?;
402
			if let Some(s) = Signing::<T>::get(signer) {
403
404
405
				ensure!(s.to_text() == &statement[..], Error::<T>::InvalidStatement);
			}
			Self::process_claim(signer, dest)?;
406
			Ok(())
407
408
409
410
411
412
413
414
415
416
417
418
419
420
		}

		/// Attest to a statement, needed to finalize the claims process.
		///
		/// WARNING: Insecure unless your chain includes `PrevalidateAttests` as a `SignedExtension`.
		///
		/// Unsigned Validation:
		/// A call to attest is deemed valid if the sender has a `Preclaim` registered
		/// and provides a `statement` which is expected for the account.
		///
		/// Parameters:
		/// - `statement`: The identity of the statement which is being attested to in the signature.
		///
		/// <weight>
421
422
423
		/// The weight of this call is invariant over the input parameters.
		/// Weight includes logic to do pre-validation on `attest` call.
		///
424
425
		/// Total Complexity: O(1)
		/// </weight>
426
		#[pallet::weight((
427
			T::WeightInfo::attest(),
428
429
			DispatchClass::Normal,
			Pays::No
430
		))]
431
		pub fn attest(origin: OriginFor<T>, statement: Vec<u8>) -> DispatchResult {
432
433
			let who = ensure_signed(origin)?;
			let signer = Preclaims::<T>::get(&who).ok_or(Error::<T>::SenderHasNoClaim)?;
434
			if let Some(s) = Signing::<T>::get(signer) {
435
436
437
438
				ensure!(s.to_text() == &statement[..], Error::<T>::InvalidStatement);
			}
			Self::process_claim(signer, who.clone())?;
			Preclaims::<T>::remove(&who);
439
			Ok(())
Gavin Wood's avatar
Gavin Wood committed
440
		}
441

442
		#[pallet::weight(T::WeightInfo::move_claim())]
443
		pub fn move_claim(
444
			origin: OriginFor<T>,
445
446
447
			old: EthereumAddress,
			new: EthereumAddress,
			maybe_preclaim: Option<T::AccountId>,
448
		) -> DispatchResultWithPostInfo {
449
450
451
452
			T::MoveClaimOrigin::try_origin(origin).map(|_| ()).or_else(ensure_root)?;

			Claims::<T>::take(&old).map(|c| Claims::<T>::insert(&new, c));
			Vesting::<T>::take(&old).map(|c| Vesting::<T>::insert(&new, c));
453
			Signing::<T>::take(&old).map(|c| Signing::<T>::insert(&new, c));
Shawn Tabrizi's avatar
Shawn Tabrizi committed
454
455
456
457
458
459
460
			maybe_preclaim.map(|preclaim| {
				Preclaims::<T>::mutate(&preclaim, |maybe_o| {
					if maybe_o.as_ref().map_or(false, |o| o == &old) {
						*maybe_o = Some(new)
					}
				})
			});
461
			Ok(Pays::No.into())
462
		}
463
	}
Keith Yeung's avatar
Keith Yeung committed
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478

	#[pallet::validate_unsigned]
	impl<T: Config> ValidateUnsigned for Pallet<T> {
		type Call = Call<T>;

		fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
			const PRIORITY: u64 = 100;

			let (maybe_signer, maybe_statement) = match call {
				// <weight>
				// The weight of this logic is included in the `claim` dispatchable.
				// </weight>
				Call::claim(account, ethereum_signature) => {
					let data = account.using_encoded(to_ascii_hex);
					(Self::eth_recover(&ethereum_signature, &data, &[][..]), None)
Shawn Tabrizi's avatar
Shawn Tabrizi committed
479
				},
Keith Yeung's avatar
Keith Yeung committed
480
481
482
483
484
				// <weight>
				// The weight of this logic is included in the `claim_attest` dispatchable.
				// </weight>
				Call::claim_attest(account, ethereum_signature, statement) => {
					let data = account.using_encoded(to_ascii_hex);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
485
486
487
488
489
					(
						Self::eth_recover(&ethereum_signature, &data, &statement),
						Some(statement.as_slice()),
					)
				},
Keith Yeung's avatar
Keith Yeung committed
490
491
492
				_ => return Err(InvalidTransaction::Call.into()),
			};

Shawn Tabrizi's avatar
Shawn Tabrizi committed
493
494
495
			let signer = maybe_signer.ok_or(InvalidTransaction::Custom(
				ValidityError::InvalidEthereumSignature.into(),
			))?;
Keith Yeung's avatar
Keith Yeung committed
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514

			let e = InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into());
			ensure!(<Claims<T>>::contains_key(&signer), e);

			let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into());
			match Signing::<T>::get(signer) {
				None => ensure!(maybe_statement.is_none(), e),
				Some(s) => ensure!(Some(s.to_text()) == maybe_statement, e),
			}

			Ok(ValidTransaction {
				priority: PRIORITY,
				requires: vec![],
				provides: vec![("claims", signer).encode()],
				longevity: TransactionLongevity::max_value(),
				propagate: true,
			})
		}
	}
515
516
}

517
518
519
520
521
522
523
524
525
526
527
/// Converts the given binary data into ASCII-encoded hex. It will be twice the length.
fn to_ascii_hex(data: &[u8]) -> Vec<u8> {
	let mut r = Vec::with_capacity(data.len() * 2);
	let mut push_nibble = |n| r.push(if n < 10 { b'0' + n } else { b'a' - 10 + n });
	for &b in data.iter() {
		push_nibble(b / 16);
		push_nibble(b % 16);
	}
	r
}

528
impl<T: Config> Pallet<T> {
529
	// Constructs the message that Ethereum RPC's `personal_sign` and `eth_sign` would sign.
530
	fn ethereum_signable_message(what: &[u8], extra: &[u8]) -> Vec<u8> {
531
		let prefix = T::Prefix::get();
532
		let mut l = prefix.len() + what.len() + extra.len();
533
534
535
536
537
538
539
540
541
		let mut rev = Vec::new();
		while l > 0 {
			rev.push(b'0' + (l % 10) as u8);
			l /= 10;
		}
		let mut v = b"\x19Ethereum Signed Message:\n".to_vec();
		v.extend(rev.into_iter().rev());
		v.extend_from_slice(&prefix[..]);
		v.extend_from_slice(what);
542
		v.extend_from_slice(extra);
543
544
545
546
547
		v
	}

	// Attempts to recover the Ethereum address from a message signature signed by using
	// the Ethereum RPC's `personal_sign` and `eth_sign`.
548
549
	fn eth_recover(s: &EcdsaSignature, what: &[u8], extra: &[u8]) -> Option<EthereumAddress> {
		let msg = keccak_256(&Self::ethereum_signable_message(what, extra));
550
		let mut res = EthereumAddress::default();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
551
552
		res.0
			.copy_from_slice(&keccak_256(&secp256k1_ecdsa_recover(&s.0, &msg).ok()?[..])[12..]);
553
554
		Some(res)
	}
555

556
	fn process_claim(signer: EthereumAddress, dest: T::AccountId) -> sp_runtime::DispatchResult {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
557
		let balance_due = <Claims<T>>::get(&signer).ok_or(Error::<T>::SignerHasNoClaim)?;
558
559
560

		let new_total = Self::total().checked_sub(&balance_due).ok_or(Error::<T>::PotUnderflow)?;

561
562
563
564
565
566
567
568
		let vesting = Vesting::<T>::get(&signer);
		if vesting.is_some() && T::VestingSchedule::vesting_balance(&dest).is_some() {
			return Err(Error::<T>::VestedBalanceExists.into())
		}

		// We first need to deposit the balance to ensure that the account exists.
		CurrencyOf::<T>::deposit_creating(&dest, balance_due);

569
		// Check if this claim should have a vesting schedule.
570
571
572
		if let Some(vs) = vesting {
			// This can only fail if the account already has a vesting schedule,
			// but this is checked above.
573
			T::VestingSchedule::add_vesting_schedule(&dest, vs.0, vs.1, vs.2)
574
				.expect("No other vesting schedule exists, as checked above; qed");
575
576
577
578
579
		}

		<Total<T>>::put(new_total);
		<Claims<T>>::remove(&signer);
		<Vesting<T>>::remove(&signer);
580
		Signing::<T>::remove(&signer);
581
582

		// Let's deposit an event to let the outside world know this happened.
583
		Self::deposit_event(Event::<T>::Claimed(dest, signer, balance_due));
584
585
586

		Ok(())
	}
587
588
}

589
590
591
/// Validate `attest` calls prior to execution. Needed to avoid a DoS attack since they are
/// otherwise free to place on chain.
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
Shawn Tabrizi's avatar
Shawn Tabrizi committed
592
593
pub struct PrevalidateAttests<T: Config + Send + Sync>(sp_std::marker::PhantomData<T>)
where
594
	<T as frame_system::Config>::Call: IsSubType<Call<T>>;
595

Shawn Tabrizi's avatar
Shawn Tabrizi committed
596
597
598
impl<T: Config + Send + Sync> Debug for PrevalidateAttests<T>
where
	<T as frame_system::Config>::Call: IsSubType<Call<T>>,
599
600
601
602
603
604
605
606
607
608
609
610
{
	#[cfg(feature = "std")]
	fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
		write!(f, "PrevalidateAttests")
	}

	#[cfg(not(feature = "std"))]
	fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
		Ok(())
	}
}

Shawn Tabrizi's avatar
Shawn Tabrizi committed
611
612
613
impl<T: Config + Send + Sync> PrevalidateAttests<T>
where
	<T as frame_system::Config>::Call: IsSubType<Call<T>>,
614
615
616
617
618
619
620
{
	/// Create new `SignedExtension` to check runtime version.
	pub fn new() -> Self {
		Self(sp_std::marker::PhantomData)
	}
}

Shawn Tabrizi's avatar
Shawn Tabrizi committed
621
622
623
impl<T: Config + Send + Sync> SignedExtension for PrevalidateAttests<T>
where
	<T as frame_system::Config>::Call: IsSubType<Call<T>>,
624
625
{
	type AccountId = T::AccountId;
626
	type Call = <T as frame_system::Config>::Call;
627
628
629
630
631
632
633
634
635
	type AdditionalSigned = ();
	type Pre = ();
	const IDENTIFIER: &'static str = "PrevalidateAttests";

	fn additional_signed(&self) -> Result<Self::AdditionalSigned, TransactionValidityError> {
		Ok(())
	}

	// <weight>
636
	// The weight of this logic is included in the `attest` dispatchable.
637
638
639
640
641
642
643
644
645
646
647
648
	// </weight>
	fn validate(
		&self,
		who: &Self::AccountId,
		call: &Self::Call,
		_info: &DispatchInfoOf<Self::Call>,
		_len: usize,
	) -> TransactionValidity {
		if let Some(local_call) = call.is_sub_type() {
			if let Call::attest(attested_statement) = local_call {
				let signer = Preclaims::<T>::get(who)
					.ok_or(InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()))?;
649
				if let Some(s) = Signing::<T>::get(signer) {
650
651
652
					let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into());
					ensure!(&attested_statement[..] == s.to_text(), e);
				}
653
			}
Gav Wood's avatar
Gav Wood committed
654
		}
655
		Ok(ValidTransaction::default())
Gav Wood's avatar
Gav Wood committed
656
657
658
	}
}

659
660
661
662
#[cfg(any(test, feature = "runtime-benchmarks"))]
mod secp_utils {
	use super::*;

663
664
	pub fn public(secret: &libsecp256k1::SecretKey) -> libsecp256k1::PublicKey {
		libsecp256k1::PublicKey::from_secret_key(secret)
665
	}
666
	pub fn eth(secret: &libsecp256k1::SecretKey) -> EthereumAddress {
667
668
669
670
		let mut res = EthereumAddress::default();
		res.0.copy_from_slice(&keccak_256(&public(secret).serialize()[1..65])[12..]);
		res
	}
Shawn Tabrizi's avatar
Shawn Tabrizi committed
671
672
673
674
675
676
677
678
679
	pub fn sig<T: Config>(
		secret: &libsecp256k1::SecretKey,
		what: &[u8],
		extra: &[u8],
	) -> EcdsaSignature {
		let msg = keccak_256(&<super::Pallet<T>>::ethereum_signable_message(
			&to_ascii_hex(what)[..],
			extra,
		));
680
		let (sig, recovery_id) = libsecp256k1::sign(&libsecp256k1::Message::parse(&msg), secret);
681
682
683
684
685
686
687
		let mut r = [0u8; 65];
		r[0..64].copy_from_slice(&sig.serialize()[..]);
		r[64] = recovery_id.serialize();
		EcdsaSignature(r)
	}
}

Gav Wood's avatar
Gav Wood committed
688
689
690
#[cfg(test)]
mod tests {
	use super::*;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
691
	use hex_literal::hex;
692
	use secp_utils::*;
Gav Wood's avatar
Gav Wood committed
693

694
	use parity_scale_codec::Encode;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
695
	use sp_core::H256;
Gav Wood's avatar
Gav Wood committed
696
	// The testing primitives are very useful for avoiding having to work with signatures
Black3HDF's avatar
Black3HDF committed
697
	// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
698
699
	use crate::claims;
	use claims::Call as ClaimsCall;
Gavin Wood's avatar
Gavin Wood committed
700
	use frame_support::{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
701
		assert_err, assert_noop, assert_ok,
702
		dispatch::DispatchError::BadOrigin,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
703
704
705
		ord_parameter_types, parameter_types,
		traits::{ExistenceRequirement, GenesisBuild},
		weights::{GetDispatchInfo, Pays},
Gavin Wood's avatar
Gavin Wood committed
706
	};
707
	use pallet_balances;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
708
709
710
711
712
	use sp_runtime::{
		testing::Header,
		traits::{BlakeTwo256, Identity, IdentityLookup},
		transaction_validity::TransactionLongevity,
	};
713
714
715
716
717
718
719
720
721
722

	type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
	type Block = frame_system::mocking::MockBlock<Test>;

	frame_support::construct_runtime!(
		pub enum Test where
			Block = Block,
			NodeBlock = Block,
			UncheckedExtrinsic = UncheckedExtrinsic,
		{
723
724
725
726
			System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
			Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
			Vesting: pallet_vesting::{Pallet, Call, Storage, Config<T>, Event<T>},
			Claims: claims::{Pallet, Call, Storage, Config<T>, Event<T>, ValidateUnsigned},
727
		}
728
729
	);

730
	parameter_types! {
731
		pub const BlockHashCount: u32 = 250;
732
	}
733
	impl frame_system::Config for Test {
734
		type BaseCallFilter = frame_support::traits::AllowAll;
735
736
737
		type BlockWeights = ();
		type BlockLength = ();
		type DbWeight = ();
Gav Wood's avatar
Gav Wood committed
738
		type Origin = Origin;
739
		type Call = Call;
Gav Wood's avatar
Gav Wood committed
740
741
742
743
744
745
746
		type Index = u64;
		type BlockNumber = u64;
		type Hash = H256;
		type Hashing = BlakeTwo256;
		type AccountId = u64;
		type Lookup = IdentityLookup<u64>;
		type Header = Header;
747
		type Event = Event;
748
		type BlockHashCount = BlockHashCount;
749
		type Version = ();
750
		type PalletInfo = PalletInfo;
751
		type AccountData = pallet_balances::AccountData<u64>;
Gavin Wood's avatar
Gavin Wood committed
752
		type OnNewAccount = ();
753
		type OnKilledAccount = ();
754
		type SystemWeightInfo = ();
755
		type SS58Prefix = ();
756
		type OnSetCode = ();
Gav Wood's avatar
Gav Wood committed
757
	}
Gavin Wood's avatar
Gavin Wood committed
758
759

	parameter_types! {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
760
		pub const ExistentialDeposit: u64 = 1;
Gavin Wood's avatar
Gavin Wood committed
761
762
	}

763
	impl pallet_balances::Config for Test {
Gav Wood's avatar
Gav Wood committed
764
		type Balance = u64;
765
		type Event = Event;
766
		type DustRemoval = ();
Gavin Wood's avatar
Gavin Wood committed
767
		type ExistentialDeposit = ExistentialDeposit;
768
		type AccountStore = System;
769
		type MaxLocks = ();
Gavin Wood's avatar
Gavin Wood committed
770
771
		type MaxReserves = ();
		type ReserveIdentifier = [u8; 8];
772
		type WeightInfo = ();
Gav Wood's avatar
Gav Wood committed
773
	}
774

775
776
777
778
	parameter_types! {
		pub const MinVestedTransfer: u64 = 0;
	}

779
	impl pallet_vesting::Config for Test {
780
		type Event = Event;
Gavin Wood's avatar
Gavin Wood committed
781
782
		type Currency = Balances;
		type BlockNumberToBalance = Identity;
783
		type MinVestedTransfer = MinVestedTransfer;
784
		type WeightInfo = ();
Gavin Wood's avatar
Gavin Wood committed
785
786
	}

Shawn Tabrizi's avatar
Shawn Tabrizi committed
787
	parameter_types! {
788
		pub Prefix: &'static [u8] = b"Pay RUSTs to the TEST account:";
789
	}
790
791
792
	ord_parameter_types! {
		pub const Six: u64 = 6;
	}
793

794
	impl Config for Test {
795
		type Event = Event;
Gavin Wood's avatar
Gavin Wood committed
796
		type VestingSchedule = Vesting;
797
		type Prefix = Prefix;
798
		type MoveClaimOrigin = frame_system::EnsureSignedBy<Six, u64>;
799
		type WeightInfo = TestWeightInfo;
Gav Wood's avatar
Gav Wood committed
800
801
	}

802
803
	fn alice() -> libsecp256k1::SecretKey {
		libsecp256k1::SecretKey::parse(&keccak_256(b"Alice")).unwrap()
Gav Wood's avatar
Gav Wood committed
804
	}
805
806
	fn bob() -> libsecp256k1::SecretKey {
		libsecp256k1::SecretKey::parse(&keccak_256(b"Bob")).unwrap()
Gav Wood's avatar
Gav Wood committed
807
	}
808
809
	fn dave() -> libsecp256k1::SecretKey {
		libsecp256k1::SecretKey::parse(&keccak_256(b"Dave")).unwrap()
810
	}
811
812
	fn eve() -> libsecp256k1::SecretKey {
		libsecp256k1::SecretKey::parse(&keccak_256(b"Eve")).unwrap()
813
	}
814
815
	fn frank() -> libsecp256k1::SecretKey {
		libsecp256k1::SecretKey::parse(&keccak_256(b"Frank")).unwrap()
816
	}
Gav Wood's avatar
Gav Wood committed
817
818
819

	// This function basically just builds a genesis storage key/value store according to
	// our desired mockup.
820
	pub fn new_test_ext() -> sp_io::TestExternalities {
821
		let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
Gav Wood's avatar
Gav Wood committed
822
		// We use default for brevity, but you can configure as desired if needed.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
823
824
825
826
		pallet_balances::GenesisConfig::<Test>::default()
			.assimilate_storage(&mut t)
			.unwrap();
		claims::GenesisConfig::<Test> {
827
828
			claims: vec![
				(eth(&alice()), 100, None, None),
Gavin Wood's avatar
Gavin Wood committed
829
830
				(eth(&dave()), 200, None, Some(StatementKind::Regular)),
				(eth(&eve()), 300, Some(42), Some(StatementKind::Saft)),
831
832
				(eth(&frank()), 400, Some(43), None),
			],
833
			vesting: vec![(eth(&alice()), (50, 10, 1))],
Shawn Tabrizi's avatar
Shawn Tabrizi committed
834
835
836
		}
		.assimilate_storage(&mut t)
		.unwrap();
Gav Wood's avatar
Gav Wood committed
837
838
839
		t.into()
	}

840
841
842
843
	fn total_claims() -> u64 {
		100 + 200 + 300 + 400
	}

Gav Wood's avatar
Gav Wood committed
844
845
	#[test]
	fn basic_setup_works() {
846
		new_test_ext().execute_with(|| {
847
			assert_eq!(Claims::total(), total_claims());
Gavin Wood's avatar
Gavin Wood committed
848
			assert_eq!(Claims::claims(&eth(&alice())), Some(100));
849
850
851
			assert_eq!(Claims::claims(&eth(&dave())), Some(200));
			assert_eq!(Claims::claims(&eth(&eve())), Some(300));
			assert_eq!(Claims::claims(&eth(&frank())), Some(400));
thiolliere's avatar
thiolliere committed
852
			assert_eq!(Claims::claims(&EthereumAddress::default()), None);
853
			assert_eq!(Claims::vesting(&eth(&alice())), Some((50, 10, 1)));
Gav Wood's avatar
Gav Wood committed
854
855
856
		});
	}

857
858
859
860
861
862
863
864
865
	#[test]
	fn serde_works() {
		let x = EthereumAddress(hex!["0123456789abcdef0123456789abcdef01234567"]);
		let y = serde_json::to_string(&x).unwrap();
		assert_eq!(y, "\"0x0123456789abcdef0123456789abcdef01234567\"");
		let z: EthereumAddress = serde_json::from_str(&y).unwrap();
		assert_eq!(x, z);
	}

Gav Wood's avatar
Gav Wood committed
866
867
	#[test]
	fn claiming_works() {
868
		new_test_ext().execute_with(|| {
869
			assert_eq!(Balances::free_balance(42), 0);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
870
871
872
873
874
			assert_ok!(Claims::claim(
				Origin::none(),
				42,
				sig::<Test>(&alice(), &42u64.encode(), &[][..])
			));
Gav Wood's avatar
Gav Wood committed
875
			assert_eq!(Balances::free_balance(&42), 100);
876
			assert_eq!(Vesting::vesting_balance(&42), Some(50));
877
878
879
880
			assert_eq!(Claims::total(), total_claims() - 100);
		});
	}

881
882
883
884
	#[test]
	fn basic_claim_moving_works() {
		new_test_ext().execute_with(|| {
			assert_eq!(Balances::free_balance(42), 0);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
885
886
887
888
			assert_noop!(
				Claims::move_claim(Origin::signed(1), eth(&alice()), eth(&bob()), None),
				BadOrigin
			);
889
			assert_ok!(Claims::move_claim(Origin::signed(6), eth(&alice()), eth(&bob()), None));
Shawn Tabrizi's avatar
Shawn Tabrizi committed
890
891
892
893
894
895
896
897
898
			assert_noop!(
				Claims::claim(Origin::none(), 42, sig::<Test>(&alice(), &42u64.encode(), &[][..])),
				Error::<Test>::SignerHasNoClaim
			);
			assert_ok!(Claims::claim(
				Origin::none(),
				42,
				sig::<Test>(&bob(), &42u64.encode(), &[][..])
			));
899
900
901
902
903
904
905
906
907
908
909
			assert_eq!(Balances::free_balance(&42), 100);
			assert_eq!(Vesting::vesting_balance(&42), Some(50));
			assert_eq!(Claims::total(), total_claims() - 100);
		});
	}

	#[test]
	fn claim_attest_moving_works() {
		new_test_ext().execute_with(|| {
			assert_ok!(Claims::move_claim(Origin::signed(6), eth(&dave()), eth(&bob()), None));
			let s = sig::<Test>(&bob(), &42u64.encode(), StatementKind::Regular.to_text());
Shawn Tabrizi's avatar
Shawn Tabrizi committed
910
911
912
913
914
915
			assert_ok!(Claims::claim_attest(
				Origin::none(),
				42,
				s,
				StatementKind::Regular.to_text().to_vec()
			));
916
917
918
919
920
921
922
923
924
925
926
927
928
			assert_eq!(Balances::free_balance(&42), 200);
		});
	}

	#[test]
	fn attest_moving_works() {
		new_test_ext().execute_with(|| {
			assert_ok!(Claims::move_claim(Origin::signed(6), eth(&eve()), eth(&bob()), Some(42)));
			assert_ok!(Claims::attest(Origin::signed(42), StatementKind::Saft.to_text().to_vec()));
			assert_eq!(Balances::free_balance(&42), 300);
		});
	}

929
930
931
	#[test]
	fn claiming_does_not_bypass_signing() {
		new_test_ext().execute_with(|| {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
932
933
934
935
936
			assert_ok!(Claims::claim(
				Origin::none(),
				42,
				sig::<Test>(&alice(), &42u64.encode(), &[][..])
			));
937
			assert_noop!(
938
				Claims::claim(Origin::none(), 42, sig::<Test>(&dave(), &42u64.encode(), &[][..])),
939
940
941
				Error::<Test>::InvalidStatement,
			);
			assert_noop!(
942
				Claims::claim(Origin::none(), 42, sig::<Test>(&eve(), &42u64.encode(), &[][..])),
943
944
				Error::<Test>::InvalidStatement,
			);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
945
946
947
948
949
			assert_ok!(Claims::claim(
				Origin::none(),
				42,
				sig::<Test>(&frank(), &42u64.encode(), &[][..])
			));
950
951
952
953
954
955
956
		});
	}

	#[test]
	fn attest_claiming_works() {
		new_test_ext().execute_with(|| {
			assert_eq!(Balances::free_balance(42), 0);
Gavin Wood's avatar
Gavin Wood committed
957
			let s = sig::<Test>(&dave(), &42u64.encode(), StatementKind::Saft.to_text());
Shawn Tabrizi's avatar
Shawn Tabrizi committed
958
959
960
961
962
963
			let r = Claims::claim_attest(
				Origin::none(),
				42,
				s.clone(),
				StatementKind::Saft.to_text().to_vec(),
			);
964
965
			assert_noop!(r, Error::<Test>::InvalidStatement);

Shawn Tabrizi's avatar
Shawn Tabrizi committed
966
967
968
969
970
971
			let r = Claims::claim_attest(
				Origin::none(),
				42,
				s,
				StatementKind::Regular.to_text().to_vec(),
			);
972
973
974
975
			assert_noop!(r, Error::<Test>::SignerHasNoClaim);
			// ^^^ we use ecdsa_recover, so an invalid signature just results in a random signer id
			// being recovered, which realistically will never have a claim.

Gavin Wood's avatar
Gavin Wood committed
976
			let s = sig::<Test>(&dave(), &42u64.encode(), StatementKind::Regular.to_text());
Shawn Tabrizi's avatar
Shawn Tabrizi committed
977
978
979
980
981
982
			assert_ok!(Claims::claim_attest(
				Origin::none(),
				42,
				s,
				StatementKind::Regular.to_text().to_vec()
			));
983
984
985
			assert_eq!(Balances::free_balance(&42), 200);
			assert_eq!(Claims::total(), total_claims() - 200);

Gavin Wood's avatar
Gavin Wood committed
986
			let s = sig::<Test>(&dave(), &42u64.encode(), StatementKind::Regular.to_text());
Shawn Tabrizi's avatar
Shawn Tabrizi committed
987
988
989
990
991
992
			let r = Claims::claim_attest(
				Origin::none(),
				42,
				s,
				StatementKind::Regular.to_text().to_vec(),
			);
993
994
995
996
997
998
999
1000
			assert_noop!(r, Error::<Test>::SignerHasNoClaim);
		});
	}

	#[test]
	fn attesting_works() {
		new_test_ext().execute_with(|| {
			assert_eq!(Balances::free_balance(42), 0);
For faster browsing, not all history is shown. View entire blame