claims.rs 23.5 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
17
18

// 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/>.

//! Module to process claims from Ethereum addresses.

19
use sp_std::prelude::*;
20
use sp_io::{hashing::keccak_256, crypto::secp256k1_ecdsa_recover};
21
22
use frame_support::{decl_event, decl_storage, decl_module, decl_error};
use frame_support::weights::SimpleDispatchInfo;
Gavin Wood's avatar
Gavin Wood committed
23
use frame_support::traits::{Currency, Get, VestingSchedule};
Gavin Wood's avatar
Gavin Wood committed
24
use system::{ensure_root, ensure_none};
25
use codec::{Encode, Decode};
Gav Wood's avatar
Gav Wood committed
26
#[cfg(feature = "std")]
27
use serde::{self, Serialize, Deserialize, Serializer, Deserializer};
28
29
30
#[cfg(feature = "std")]
use sp_runtime::traits::Zero;
use sp_runtime::traits::CheckedSub;
31
use sp_runtime::{
32
	RuntimeDebug, transaction_validity::{
Gavin Wood's avatar
Gavin Wood committed
33
34
		TransactionLongevity, TransactionValidity, ValidTransaction, InvalidTransaction
	},
35
};
Gavin Wood's avatar
Gavin Wood committed
36
use primitives::ValidityError;
37
38
use system;

Gavin Wood's avatar
Gavin Wood committed
39
40
type CurrencyOf<T> = <<T as Trait>::VestingSchedule as VestingSchedule<<T as system::Trait>::AccountId>>::Currency;
type BalanceOf<T> = <CurrencyOf<T> as Currency<<T as system::Trait>::AccountId>>::Balance;
Gav Wood's avatar
Gav Wood committed
41
42

/// Configuration trait.
43
pub trait Trait: system::Trait {
Gav Wood's avatar
Gav Wood committed
44
45
	/// The overarching event type.
	type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
Gavin Wood's avatar
Gavin Wood committed
46
	type VestingSchedule: VestingSchedule<Self::AccountId, Moment=Self::BlockNumber>;
47
	type Prefix: Get<&'static [u8]>;
Gav Wood's avatar
Gav Wood committed
48
49
}

50
51
52
/// 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
53
#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, Default, RuntimeDebug)]
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
pub struct EthereumAddress([u8; 20]);

#[cfg(feature = "std")]
impl Serialize for EthereumAddress {
	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
		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 {
	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
		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 {
			Err(serde::de::Error::custom("Bad length of Ethereum address (should be 42 including '0x')"))?;
		}
		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)
	}
}
80

81
82
#[derive(Encode, Decode, Clone)]
pub struct EcdsaSignature(pub [u8; 65]);
83

84
85
86
impl PartialEq for EcdsaSignature {
	fn eq(&self, other: &Self) -> bool {
		&self.0[..] == &other.0[..]
87
88
	}
}
Gav Wood's avatar
Gav Wood committed
89

90
91
impl sp_std::fmt::Debug for EcdsaSignature {
	fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result {
92
93
		write!(f, "EcdsaSignature({:?})", &self.0[..])
	}
94
95
}

Gav Wood's avatar
Gav Wood committed
96
97
decl_event!(
	pub enum Event<T> where
98
99
		Balance = BalanceOf<T>,
		AccountId = <T as system::Trait>::AccountId
Gav Wood's avatar
Gav Wood committed
100
101
	{
		/// Someone claimed some DOTs.
102
		Claimed(AccountId, EthereumAddress, Balance),
Gav Wood's avatar
Gav Wood committed
103
104
105
	}
);

106
107
108
109
110
111
decl_error! {
	pub enum Error for Module<T: Trait> {
		/// Invalid Ethereum signature.
		InvalidEthereumSignature,
		/// Ethereum address has no claim.
		SignerHasNoClaim,
Gavin Wood's avatar
Gavin Wood committed
112
113
114
115
116
		/// The destination is already vesting and cannot be the target of a further claim.
		DestinationVesting,
		/// There's not enough in the pot to pay out some unvested amount. Generally implies a logic
		/// error.
		PotUnderflow,
117
118
119
	}
}

Gav Wood's avatar
Gav Wood committed
120
121
122
123
124
decl_storage! {
	// A macro for the Storage trait, and its implementation, for this module.
	// This allows for type-safe usage of the Substrate storage database, so you can
	// keep things around between blocks.
	trait Store for Module<T: Trait> as Claims {
125
		Claims get(fn claims) build(|config: &GenesisConfig<T>| {
Gav Wood's avatar
Gav Wood committed
126
			config.claims.iter().map(|(a, b)| (a.clone(), b.clone())).collect::<Vec<_>>()
127
		}): map hasher(identity) EthereumAddress => Option<BalanceOf<T>>;
128
		Total get(fn total) build(|config: &GenesisConfig<T>| {
129
130
			config.claims.iter().fold(Zero::zero(), |acc: BalanceOf<T>, &(_, n)| acc + n)
		}): BalanceOf<T>;
131
132
133
134
		/// 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.
135
		Vesting get(fn vesting) config():
136
			map hasher(identity) EthereumAddress
Gavin Wood's avatar
Gavin Wood committed
137
			=> Option<(BalanceOf<T>, BalanceOf<T>, T::BlockNumber)>;
Gav Wood's avatar
Gav Wood committed
138
139
	}
	add_extra_genesis {
140
		config(claims): Vec<(EthereumAddress, BalanceOf<T>)>;
Gav Wood's avatar
Gav Wood committed
141
142
143
144
145
	}
}

decl_module! {
	pub struct Module<T: Trait> for enum Call where origin: T::Origin {
146
147
		type Error = Error<T>;

148
149
150
		/// The Prefix that is used in signed Ethereum messages for this network
		const Prefix: &[u8] = T::Prefix::get();

Gav Wood's avatar
Gav Wood committed
151
		/// Deposit one of this module's events by using the default implementation.
thiolliere's avatar
thiolliere committed
152
		fn deposit_event() = default;
Gav Wood's avatar
Gav Wood committed
153

154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
		/// 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.
		/// - One `eth_recover` operation which involves a keccak hash and a
		///   ecdsa recover.
		/// - Three storage reads to check if a claim exists for the user, to
		///   get the current pot size, to see if there exists a vesting schedule.
		/// - Up to one storage write for adding a new vesting schedule.
		/// - One `deposit_creating` Currency call.
		/// - One storage write to update the total.
		/// - Two storage removals for vesting and claims information.
		/// - One deposit event.
		///
		/// Total Complexity: O(1)
		/// </weight>
186
		#[weight = SimpleDispatchInfo::FixedNormal(1_000_000)]
187
188
		fn claim(origin, dest: T::AccountId, ethereum_signature: EcdsaSignature) {
			ensure_none(origin)?;
189

190
191
			let data = dest.using_encoded(to_ascii_hex);
			let signer = Self::eth_recover(&ethereum_signature, &data)
192
				.ok_or(Error::<T>::InvalidEthereumSignature)?;
193

194
			let balance_due = <Claims<T>>::get(&signer)
195
				.ok_or(Error::<T>::SignerHasNoClaim)?;
196

197
			let new_total = Self::total().checked_sub(&balance_due).ok_or(Error::<T>::PotUnderflow)?;
Gavin Wood's avatar
Gavin Wood committed
198

199
			// Check if this claim should have a vesting schedule.
200
201
202
203
204
			if let Some(vs) = <Vesting<T>>::get(&signer) {
				// If this fails, destination account already has a vesting schedule
				// applied to it, and this claim should not be processed.
				T::VestingSchedule::add_vesting_schedule(&dest, vs.0, vs.1, vs.2)
					.map_err(|_| Error::<T>::DestinationVesting)?;
205
206
			}

207
208
			CurrencyOf::<T>::deposit_creating(&dest, balance_due);
			<Total<T>>::put(new_total);
209
210
211
			<Claims<T>>::remove(&signer);
			<Vesting<T>>::remove(&signer);

Gav Wood's avatar
Gav Wood committed
212
			// Let's deposit an event to let the outside world know this happened.
213
214
			Self::deposit_event(RawEvent::Claimed(dest, signer, balance_due));
		}
Gavin Wood's avatar
Gavin Wood committed
215

216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
		/// 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.
		/// - One storage mutate to increase the total claims available.
		/// - One storage write to add a new claim.
		/// - Up to one storage write to add a new vesting schedule.
		///
		/// Total Complexity: O(1)
		/// </weight>
Gavin Wood's avatar
Gavin Wood committed
233
		#[weight = SimpleDispatchInfo::FixedNormal(30_000)]
234
235
236
237
238
		fn mint_claim(origin,
			who: EthereumAddress,
			value: BalanceOf<T>,
			vesting_schedule: Option<(BalanceOf<T>, BalanceOf<T>, T::BlockNumber)>,
		) {
Gavin Wood's avatar
Gavin Wood committed
239
240
241
242
			ensure_root(origin)?;

			<Total<T>>::mutate(|t| *t += value);
			<Claims<T>>::insert(who, value);
243
244
245
			if let Some(vs) = vesting_schedule {
				<Vesting<T>>::insert(who, vs);
			}
Gavin Wood's avatar
Gavin Wood committed
246
		}
247
248
249
	}
}

250
251
252
253
254
255
256
257
258
259
260
/// 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
}

261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
impl<T: Trait> Module<T> {
	// Constructs the message that Ethereum RPC's `personal_sign` and `eth_sign` would sign.
	fn ethereum_signable_message(what: &[u8]) -> Vec<u8> {
		let prefix = T::Prefix::get();
		let mut l = prefix.len() + what.len();
		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);
		v
	}

	// Attempts to recover the Ethereum address from a message signature signed by using
	// the Ethereum RPC's `personal_sign` and `eth_sign`.
	fn eth_recover(s: &EcdsaSignature, what: &[u8]) -> Option<EthereumAddress> {
		let msg = keccak_256(&Self::ethereum_signable_message(what));
		let mut res = EthereumAddress::default();
283
		res.0.copy_from_slice(&keccak_256(&secp256k1_ecdsa_recover(&s.0, &msg).ok()?[..])[12..]);
284
285
286
287
		Some(res)
	}
}

288
impl<T: Trait> sp_runtime::traits::ValidateUnsigned for Module<T> {
289
290
291
292
293
294
295
	type Call = Call<T>;

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

		match call {
			Call::claim(account, ethereum_signature) => {
296
297
298
299
				let data = account.using_encoded(to_ascii_hex);
				let maybe_signer = Self::eth_recover(&ethereum_signature, &data);
				let signer = if let Some(s) = maybe_signer {
					s
300
				} else {
Gavin Wood's avatar
Gavin Wood committed
301
302
303
					return InvalidTransaction::Custom(
						ValidityError::InvalidEthereumSignature.into(),
					).into();
304
305
				};

306
				if !<Claims<T>>::contains_key(&signer) {
Gavin Wood's avatar
Gavin Wood committed
307
308
309
					return Err(InvalidTransaction::Custom(
						ValidityError::SignerHasNoClaim.into(),
					).into());
310
311
				}

Gavin Wood's avatar
Gavin Wood committed
312
				Ok(ValidTransaction {
313
314
					priority: PRIORITY,
					requires: vec![],
315
					provides: vec![("claims", signer).encode()],
316
					longevity: TransactionLongevity::max_value(),
317
					propagate: true,
318
				})
319
			}
Gavin Wood's avatar
Gavin Wood committed
320
			_ => Err(InvalidTransaction::Call.into()),
Gav Wood's avatar
Gav Wood committed
321
322
323
324
		}
	}
}

325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
#[cfg(any(test, feature = "runtime-benchmarks"))]
mod secp_utils {
	use super::*;
	use secp256k1;

	pub fn public(secret: &secp256k1::SecretKey) -> secp256k1::PublicKey {
		secp256k1::PublicKey::from_secret_key(secret)
	}
	pub fn eth(secret: &secp256k1::SecretKey) -> EthereumAddress {
		let mut res = EthereumAddress::default();
		res.0.copy_from_slice(&keccak_256(&public(secret).serialize()[1..65])[12..]);
		res
	}
	pub fn sig<T: Trait>(secret: &secp256k1::SecretKey, what: &[u8]) -> EcdsaSignature {
		let msg = keccak_256(&<super::Module<T>>::ethereum_signable_message(&to_ascii_hex(what)[..]));
		let (sig, recovery_id) = secp256k1::sign(&secp256k1::Message::parse(&msg), secret);
		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
348
349
350
#[cfg(test)]
mod tests {
	use secp256k1;
351
	use hex_literal::hex;
Gav Wood's avatar
Gav Wood committed
352
	use super::*;
353
	use secp_utils::*;
Gav Wood's avatar
Gav Wood committed
354

355
	use sp_core::H256;
356
	use codec::Encode;
Gav Wood's avatar
Gav Wood committed
357
	// The testing primitives are very useful for avoiding having to work with signatures
Black3HDF's avatar
Black3HDF committed
358
	// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required.
Gavin Wood's avatar
Gavin Wood committed
359
360
361
362
	use sp_runtime::{Perbill, traits::{BlakeTwo256, IdentityLookup, Identity}, testing::Header};
	use frame_support::{
		impl_outer_origin, assert_ok, assert_err, assert_noop, parameter_types
	};
Gav Wood's avatar
Gav Wood committed
363
364
365
366
367
368
369
370
371
372
	use balances;

	impl_outer_origin! {
		pub enum Origin for Test {}
	}
	// For testing the module, we construct most of a mock runtime. This means
	// first constructing a configuration type (`Test`) which `impl`s each of the
	// configuration traits of modules we want to use.
	#[derive(Clone, Eq, PartialEq)]
	pub struct Test;
373
	parameter_types! {
374
		pub const BlockHashCount: u32 = 250;
375
376
377
		pub const MaximumBlockWeight: u32 = 4 * 1024 * 1024;
		pub const MaximumBlockLength: u32 = 4 * 1024 * 1024;
		pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75);
378
	}
Gav Wood's avatar
Gav Wood committed
379
380
	impl system::Trait for Test {
		type Origin = Origin;
381
		type Call = ();
Gav Wood's avatar
Gav Wood committed
382
383
384
385
386
387
388
389
		type Index = u64;
		type BlockNumber = u64;
		type Hash = H256;
		type Hashing = BlakeTwo256;
		type AccountId = u64;
		type Lookup = IdentityLookup<u64>;
		type Header = Header;
		type Event = ();
390
		type BlockHashCount = BlockHashCount;
391
392
		type MaximumBlockWeight = MaximumBlockWeight;
		type MaximumBlockLength = MaximumBlockLength;
Gavin Wood's avatar
Gavin Wood committed
393
		type AvailableBlockRatio = AvailableBlockRatio;
394
		type Version = ();
395
		type ModuleToIndex = ();
396
		type AccountData = balances::AccountData<u64>;
Gavin Wood's avatar
Gavin Wood committed
397
		type OnNewAccount = ();
398
		type OnKilledAccount = Balances;
Gav Wood's avatar
Gav Wood committed
399
	}
Gavin Wood's avatar
Gavin Wood committed
400
401

	parameter_types! {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
402
		pub const ExistentialDeposit: u64 = 1;
Gavin Wood's avatar
Gavin Wood committed
403
		pub const CreationFee: u64 = 0;
404
		pub const MinVestedTransfer: u64 = 0;
Gavin Wood's avatar
Gavin Wood committed
405
406
	}

Gav Wood's avatar
Gav Wood committed
407
408
	impl balances::Trait for Test {
		type Balance = u64;
Gavin Wood's avatar
Gavin Wood committed
409
		type Event = ();
410
		type DustRemoval = ();
Gavin Wood's avatar
Gavin Wood committed
411
		type ExistentialDeposit = ExistentialDeposit;
412
		type AccountStore = System;
Gav Wood's avatar
Gav Wood committed
413
	}
414

Gavin Wood's avatar
Gavin Wood committed
415
416
417
418
	impl vesting::Trait for Test {
		type Event = ();
		type Currency = Balances;
		type BlockNumberToBalance = Identity;
419
		type MinVestedTransfer = MinVestedTransfer;
Gavin Wood's avatar
Gavin Wood committed
420
421
	}

422
	parameter_types!{
423
		pub const Prefix: &'static [u8] = b"Pay RUSTs to the TEST account:";
424
425
	}

Gav Wood's avatar
Gav Wood committed
426
427
	impl Trait for Test {
		type Event = ();
Gavin Wood's avatar
Gavin Wood committed
428
		type VestingSchedule = Vesting;
429
		type Prefix = Prefix;
Gav Wood's avatar
Gav Wood committed
430
	}
431
	type System = system::Module<Test>;
Gav Wood's avatar
Gav Wood committed
432
	type Balances = balances::Module<Test>;
Gavin Wood's avatar
Gavin Wood committed
433
	type Vesting = vesting::Module<Test>;
Gav Wood's avatar
Gav Wood committed
434
435
	type Claims = Module<Test>;

Gavin Wood's avatar
Gavin Wood committed
436
	fn alice() -> secp256k1::SecretKey {
437
		secp256k1::SecretKey::parse(&keccak_256(b"Alice")).unwrap()
Gav Wood's avatar
Gav Wood committed
438
	}
Gavin Wood's avatar
Gavin Wood committed
439
	fn bob() -> secp256k1::SecretKey {
440
		secp256k1::SecretKey::parse(&keccak_256(b"Bob")).unwrap()
Gav Wood's avatar
Gav Wood committed
441
442
443
444
	}

	// This function basically just builds a genesis storage key/value store according to
	// our desired mockup.
445
	fn new_test_ext() -> sp_io::TestExternalities {
446
		let mut t = system::GenesisConfig::default().build_storage::<Test>().unwrap();
Gav Wood's avatar
Gav Wood committed
447
		// We use default for brevity, but you can configure as desired if needed.
448
449
		balances::GenesisConfig::<Test>::default().assimilate_storage(&mut t).unwrap();
		GenesisConfig::<Test>{
Gavin Wood's avatar
Gavin Wood committed
450
			claims: vec![(eth(&alice()), 100)],
451
			vesting: vec![(eth(&alice()), (50, 10, 1))],
452
		}.assimilate_storage(&mut t).unwrap();
Gav Wood's avatar
Gav Wood committed
453
454
455
456
457
		t.into()
	}

	#[test]
	fn basic_setup_works() {
458
		new_test_ext().execute_with(|| {
Gav Wood's avatar
Gav Wood committed
459
			assert_eq!(Claims::total(), 100);
Gavin Wood's avatar
Gavin Wood committed
460
			assert_eq!(Claims::claims(&eth(&alice())), Some(100));
thiolliere's avatar
thiolliere committed
461
			assert_eq!(Claims::claims(&EthereumAddress::default()), None);
462
			assert_eq!(Claims::vesting(&eth(&alice())), Some((50, 10, 1)));
Gav Wood's avatar
Gav Wood committed
463
464
465
		});
	}

466
467
468
469
470
471
472
473
474
	#[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
475
476
	#[test]
	fn claiming_works() {
477
		new_test_ext().execute_with(|| {
478
			assert_eq!(Balances::free_balance(42), 0);
479
			assert_ok!(Claims::claim(Origin::NONE, 42, sig::<Test>(&alice(), &42u64.encode())));
Gav Wood's avatar
Gav Wood committed
480
			assert_eq!(Balances::free_balance(&42), 100);
481
			assert_eq!(Vesting::vesting_balance(&42), Some(50));
482
			assert_eq!(Claims::total(), 0);
Gav Wood's avatar
Gav Wood committed
483
484
485
		});
	}

Gavin Wood's avatar
Gavin Wood committed
486
487
488
	#[test]
	fn add_claim_works() {
		new_test_ext().execute_with(|| {
489
490
			assert_noop!(
				Claims::mint_claim(Origin::signed(42), eth(&bob()), 200, None),
491
				sp_runtime::traits::BadOrigin,
492
			);
493
			assert_eq!(Balances::free_balance(42), 0);
494
			assert_noop!(
495
				Claims::claim(Origin::NONE, 69, sig::<Test>(&bob(), &69u64.encode())),
496
				Error::<Test>::SignerHasNoClaim,
497
498
			);
			assert_ok!(Claims::mint_claim(Origin::ROOT, eth(&bob()), 200, None));
499
			assert_eq!(Claims::total(), 300);
500
			assert_ok!(Claims::claim(Origin::NONE, 69, sig::<Test>(&bob(), &69u64.encode())));
501
			assert_eq!(Balances::free_balance(&69), 200);
502
			assert_eq!(Vesting::vesting_balance(&69), None);
503
			assert_eq!(Claims::total(), 100);
504
505
506
507
508
509
510
511
		});
	}

	#[test]
	fn add_claim_with_vesting_works() {
		new_test_ext().execute_with(|| {
			assert_noop!(
				Claims::mint_claim(Origin::signed(42), eth(&bob()), 200, Some((50, 10, 1))),
512
				sp_runtime::traits::BadOrigin,
513
			);
Gavin Wood's avatar
Gavin Wood committed
514
			assert_eq!(Balances::free_balance(42), 0);
Gavin Wood's avatar
Gavin Wood committed
515
			assert_noop!(
516
				Claims::claim(Origin::NONE, 69, sig::<Test>(&bob(), &69u64.encode())),
517
				Error::<Test>::SignerHasNoClaim
Gavin Wood's avatar
Gavin Wood committed
518
			);
519
			assert_ok!(Claims::mint_claim(Origin::ROOT, eth(&bob()), 200, Some((50, 10, 1))));
520
			assert_ok!(Claims::claim(Origin::NONE, 69, sig::<Test>(&bob(), &69u64.encode())));
521
			assert_eq!(Balances::free_balance(&69), 200);
522
			assert_eq!(Vesting::vesting_balance(&69), Some(50));
Gavin Wood's avatar
Gavin Wood committed
523
524
525
		});
	}

526
527
	#[test]
	fn origin_signed_claiming_fail() {
528
		new_test_ext().execute_with(|| {
Gavin Wood's avatar
Gavin Wood committed
529
			assert_eq!(Balances::free_balance(42), 0);
Gavin Wood's avatar
Gavin Wood committed
530
			assert_err!(
531
				Claims::claim(Origin::signed(42), 42, sig::<Test>(&alice(), &42u64.encode())),
532
				sp_runtime::traits::BadOrigin,
Gavin Wood's avatar
Gavin Wood committed
533
			);
534
535
536
		});
	}

Gav Wood's avatar
Gav Wood committed
537
538
	#[test]
	fn double_claiming_doesnt_work() {
539
		new_test_ext().execute_with(|| {
Gavin Wood's avatar
Gavin Wood committed
540
			assert_eq!(Balances::free_balance(42), 0);
541
			assert_ok!(Claims::claim(Origin::NONE, 42, sig::<Test>(&alice(), &42u64.encode())));
542
			assert_noop!(
543
				Claims::claim(Origin::NONE, 42, sig::<Test>(&alice(), &42u64.encode())),
544
545
				Error::<Test>::SignerHasNoClaim
			);
Gav Wood's avatar
Gav Wood committed
546
547
548
		});
	}

549
550
551
552
553
554
555
556
557
558
559
560
561
562
	#[test]
	fn claiming_while_vested_doesnt_work() {
		new_test_ext().execute_with(|| {
			assert_eq!(Claims::total(), 100);
			// A user is already vested
			assert_ok!(<Test as Trait>::VestingSchedule::add_vesting_schedule(&69, 1000, 100, 10));
			CurrencyOf::<Test>::make_free_balance_be(&69, 1000);
			assert_eq!(Balances::free_balance(69), 1000);
			assert_ok!(Claims::mint_claim(Origin::ROOT, eth(&bob()), 200, Some((50, 10, 1))));
			// New total
			assert_eq!(Claims::total(), 300);

			// They should not be able to claim
			assert_noop!(
563
				Claims::claim(Origin::NONE, 69, sig::<Test>(&bob(), &69u64.encode())),
564
565
566
567
568
				Error::<Test>::DestinationVesting
			);
			// Everything should be unchanged
			assert_eq!(Claims::total(), 300);
			assert_eq!(Balances::free_balance(69), 1000);
569
			assert_eq!(Vesting::vesting_balance(&69), Some(1000));
570
571
572
		});
	}

Gav Wood's avatar
Gav Wood committed
573
574
	#[test]
	fn non_sender_sig_doesnt_work() {
575
		new_test_ext().execute_with(|| {
Gavin Wood's avatar
Gavin Wood committed
576
			assert_eq!(Balances::free_balance(42), 0);
577
			assert_noop!(
578
				Claims::claim(Origin::NONE, 42, sig::<Test>(&alice(), &69u64.encode())),
579
580
				Error::<Test>::SignerHasNoClaim
			);
Gav Wood's avatar
Gav Wood committed
581
582
583
584
585
		});
	}

	#[test]
	fn non_claimant_doesnt_work() {
586
		new_test_ext().execute_with(|| {
Gavin Wood's avatar
Gavin Wood committed
587
			assert_eq!(Balances::free_balance(42), 0);
588
			assert_noop!(
589
				Claims::claim(Origin::NONE, 42, sig::<Test>(&bob(), &69u64.encode())),
590
591
				Error::<Test>::SignerHasNoClaim
			);
Gav Wood's avatar
Gav Wood committed
592
593
594
595
596
		});
	}

	#[test]
	fn real_eth_sig_works() {
597
		new_test_ext().execute_with(|| {
598
599
			// "Pay RUSTs to the TEST account:2a00000000000000"
			let sig = hex!["444023e89b67e67c0562ed0305d252a5dd12b2af5ac51d6d3cb69a0b486bc4b3191401802dc29d26d586221f7256cd3329fe82174bdf659baea149a40e1c495d1c"];
600
			let sig = EcdsaSignature(sig);
601
			let who = 42u64.using_encoded(to_ascii_hex);
602
			let signer = Claims::eth_recover(&sig, &who).unwrap();
603
			assert_eq!(signer.0, hex!["6d31165d5d932d571f3b44695653b46dcc327e84"]);
604
		});
Gav Wood's avatar
Gav Wood committed
605
	}
606
607
608

	#[test]
	fn validate_unsigned_works() {
609
		use sp_runtime::traits::ValidateUnsigned;
Kian Paimani's avatar
Kian Paimani committed
610

611
		new_test_ext().execute_with(|| {
612
			assert_eq!(
613
				<Module<Test>>::validate_unsigned(&Call::claim(1, sig::<Test>(&alice(), &1u64.encode()))),
Gavin Wood's avatar
Gavin Wood committed
614
				Ok(ValidTransaction {
615
616
					priority: 100,
					requires: vec![],
Gavin Wood's avatar
Gavin Wood committed
617
					provides: vec![("claims", eth(&alice())).encode()],
618
					longevity: TransactionLongevity::max_value(),
619
					propagate: true,
620
				})
621
622
			);
			assert_eq!(
623
				<Module<Test>>::validate_unsigned(&Call::claim(0, EcdsaSignature([0; 65]))),
Gavin Wood's avatar
Gavin Wood committed
624
				InvalidTransaction::Custom(ValidityError::InvalidEthereumSignature.into()).into(),
625
626
			);
			assert_eq!(
627
				<Module<Test>>::validate_unsigned(&Call::claim(1, sig::<Test>(&bob(), &1u64.encode()))),
Gavin Wood's avatar
Gavin Wood committed
628
				InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(),
629
630
			);
			assert_eq!(
631
				<Module<Test>>::validate_unsigned(&Call::claim(0, sig::<Test>(&bob(), &1u64.encode()))),
Gavin Wood's avatar
Gavin Wood committed
632
				InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(),
633
634
635
			);
		});
	}
Gav Wood's avatar
Gav Wood committed
636
}
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking {
	use super::*;
	use secp_utils::*;
	use system::RawOrigin;
	use frame_benchmarking::{benchmarks, account};
	use sp_runtime::DispatchResult;
	use sp_runtime::traits::ValidateUnsigned;
	use crate::claims::Call;

	const SEED: u32 = 0;

	const MAX_CLAIMS: u32 = 10_000;
	const VALUE: u32 = 1_000_000;

	fn create_claim<T: Trait>(input: u32) -> DispatchResult {
		let secret_key = secp256k1::SecretKey::parse(&keccak_256(&input.encode())).unwrap();
		let eth_address = eth(&secret_key);
		let vesting = Some((100_000.into(), 1_000.into(), 100.into()));
		super::Module::<T>::mint_claim(RawOrigin::Root.into(), eth_address, VALUE.into(), vesting)?;
		Ok(())
	}

	benchmarks! {
		_ {
			// Create claims in storage.
			let c in 0 .. MAX_CLAIMS => create_claim::<T>(c)?;
		}

		// Benchmark `claim` for different users.
		claim {
			let u in 0 .. 1000;
			let secret_key = secp256k1::SecretKey::parse(&keccak_256(&u.encode())).unwrap();
			let eth_address = eth(&secret_key);
			let account: T::AccountId = account("user", u, SEED);
			let vesting = Some((100_000.into(), 1_000.into(), 100.into()));
			let signature = sig::<T>(&secret_key, &account.encode());
			super::Module::<T>::mint_claim(RawOrigin::Root.into(), eth_address, VALUE.into(), vesting)?;
		}: _(RawOrigin::None, account, signature)

		// Benchmark `mint_claim` when there already exists `c` claims in storage.
		mint_claim {
			let c in ...;
			let account = account("user", c, SEED);
			let vesting = Some((100_000.into(), 1_000.into(), 100.into()));
		}: _(RawOrigin::Root, account, VALUE.into(), vesting)

685
		// Benchmark the time it takes to execute `validate_unsigned`
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
		validate_unsigned {
			let c in ...;
			// Crate signature
			let secret_key = secp256k1::SecretKey::parse(&keccak_256(&c.encode())).unwrap();
			let account: T::AccountId = account("user", c, SEED);
			let signature = sig::<T>(&secret_key, &account.encode());
			let call = Call::<T>::claim(account, signature);
		}: {
			super::Module::<T>::validate_unsigned(&call)?
		}

		// Benchmark the time it takes to do `repeat` number of keccak256 hashes
		keccak256 {
			let i in 0 .. 10_000;
			let bytes = (i).encode();
		}: {
			for index in 0 .. i {
				let _hash = keccak_256(&bytes);
			}
		}

707
		// Benchmark the time it takes to do `repeat` number of `eth_recover`
708
709
710
711
712
713
714
715
716
717
718
719
720
721
		eth_recover {
			let i in 0 .. 1_000;
			// Crate signature
			let secret_key = secp256k1::SecretKey::parse(&keccak_256(&i.encode())).unwrap();
			let account: T::AccountId = account("user", i, SEED);
			let signature = sig::<T>(&secret_key, &account.encode());
			let data = account.using_encoded(to_ascii_hex);
		}: {
			for _ in 0 .. i {
				let _maybe_signer = super::Module::<T>::eth_recover(&signature, &data);
			}
		}
	}
}