diff --git a/polkadot/runtime/src/claims.rs b/polkadot/runtime/src/claims.rs index 555ca6dccd9a757ed886e49b585fa530caeb0858..e1a6404a16332916d972bf4fccf707cb45e4187d 100644 --- a/polkadot/runtime/src/claims.rs +++ b/polkadot/runtime/src/claims.rs @@ -20,10 +20,12 @@ use rstd::prelude::*; use sr_io::{keccak_256, secp256k1_ecdsa_recover}; use srml_support::{StorageValue, StorageMap}; use srml_support::traits::Currency; -use system::ensure_signed; +use system::ensure_none; use codec::Encode; #[cfg(feature = "std")] use sr_primitives::traits::Zero; +use sr_primitives::traits::ValidateUnsigned; +use sr_primitives::transaction_validity::{TransactionLongevity, TransactionValidity}; use system; type BalanceOf<T> = <<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance; @@ -118,13 +120,11 @@ decl_module! { fn deposit_event<T>() = default; /// Make a claim. - fn claim(origin, ethereum_signature: EcdsaSignature) { - // This is a public call, so we ensure that the origin is some signed account. - let sender = ensure_signed(origin)?; + fn claim(origin, dest: T::AccountId, ethereum_signature: EcdsaSignature) { + ensure_none(origin)?; - let signer = sender.using_encoded(|data| - eth_recover(ðereum_signature, data) - ).ok_or("Invalid Ethereum signature")?; + let signer = dest.using_encoded(|data| eth_recover(ðereum_signature, data)) + .ok_or("Invalid Ethereum signature")?; let balance_due = <Claims<T>>::take(&signer) .ok_or("Ethereum address has no claim")?; @@ -135,10 +135,46 @@ decl_module! { *t -= balance_due }); - T::Currency::deposit_creating(&sender, balance_due); + T::Currency::deposit_creating(&dest, balance_due); // Let's deposit an event to let the outside world know this happened. - Self::deposit_event(RawEvent::Claimed(sender, signer, balance_due)); + Self::deposit_event(RawEvent::Claimed(dest, signer, balance_due)); + } + } +} + +impl<T: Trait> ValidateUnsigned for Module<T> { + type Call = Call<T>; + + fn validate_unsigned(call: &Self::Call) -> TransactionValidity { + // Note errors > 0 are from ApplyError + const INVALID_ETHEREUM_SIGNATURE: i8 = -10; + const SIGNER_HAS_NO_CLAIM: i8 = -20; + const INVALID_CALL: i8 = -30; + + const PRIORITY: u64 = 100; + + match call { + Call::claim(account, ethereum_signature) => { + let signer = account.using_encoded(|data| eth_recover(ðereum_signature, data)); + let signer = if let Some(signer) = signer { + signer + } else { + return TransactionValidity::Invalid(INVALID_ETHEREUM_SIGNATURE); + }; + + if !<Claims<T>>::exists(&signer) { + return TransactionValidity::Invalid(SIGNER_HAS_NO_CLAIM); + } + + TransactionValidity::Valid { + priority: PRIORITY, + requires: vec![], + provides: vec![], + longevity: TransactionLongevity::max_value(), + } + } + _ => TransactionValidity::Invalid(INVALID_CALL) } } } @@ -249,17 +285,26 @@ mod tests { fn claiming_works() { with_externalities(&mut new_test_ext(), || { assert_eq!(Balances::free_balance(&42), 0); - assert_ok!(Claims::claim(Origin::signed(42), alice_sig(&42u64.encode()))); + assert_ok!(Claims::claim(Origin::NONE, 42, alice_sig(&42u64.encode()))); assert_eq!(Balances::free_balance(&42), 100); }); } + #[test] + fn origin_signed_claiming_fail() { + with_externalities(&mut new_test_ext(), || { + assert_eq!(Balances::free_balance(&42), 0); + assert_err!(Claims::claim(Origin::signed(42), 42, alice_sig(&42u64.encode())), + "bad origin: expected to be no origin"); + }); + } + #[test] fn double_claiming_doesnt_work() { with_externalities(&mut new_test_ext(), || { assert_eq!(Balances::free_balance(&42), 0); - assert_ok!(Claims::claim(Origin::signed(42), alice_sig(&42u64.encode()))); - assert_noop!(Claims::claim(Origin::signed(42), alice_sig(&42u64.encode())), "Ethereum address has no claim"); + assert_ok!(Claims::claim(Origin::NONE, 42, alice_sig(&42u64.encode()))); + assert_noop!(Claims::claim(Origin::NONE, 42, alice_sig(&42u64.encode())), "Ethereum address has no claim"); }); } @@ -267,7 +312,7 @@ mod tests { fn non_sender_sig_doesnt_work() { with_externalities(&mut new_test_ext(), || { assert_eq!(Balances::free_balance(&42), 0); - assert_noop!(Claims::claim(Origin::signed(42), alice_sig(&69u64.encode())), "Ethereum address has no claim"); + assert_noop!(Claims::claim(Origin::NONE, 42, alice_sig(&69u64.encode())), "Ethereum address has no claim"); }); } @@ -275,7 +320,7 @@ mod tests { fn non_claimant_doesnt_work() { with_externalities(&mut new_test_ext(), || { assert_eq!(Balances::free_balance(&42), 0); - assert_noop!(Claims::claim(Origin::signed(42), bob_sig(&69u64.encode())), "Ethereum address has no claim"); + assert_noop!(Claims::claim(Origin::NONE, 42, bob_sig(&69u64.encode())), "Ethereum address has no claim"); }); } @@ -287,4 +332,31 @@ mod tests { let signer = eth_recover(&sig, &who).unwrap(); assert_eq!(signer, hex!["DF67EC7EAe23D2459694685257b6FC59d1BAA1FE"]); } + + #[test] + fn validate_unsigned_works() { + with_externalities(&mut new_test_ext(), || { + assert_eq!( + <Module<Test>>::validate_unsigned(&Call::claim(1, alice_sig(&1u64.encode()))), + TransactionValidity::Valid { + priority: 100, + requires: vec![], + provides: vec![], + longevity: TransactionLongevity::max_value(), + } + ); + assert_eq!( + <Module<Test>>::validate_unsigned(&Call::claim(0, EcdsaSignature::from_blob(&[0; 65]))), + TransactionValidity::Invalid(-10) + ); + assert_eq!( + <Module<Test>>::validate_unsigned(&Call::claim(1, bob_sig(&1u64.encode()))), + TransactionValidity::Invalid(-20) + ); + assert_eq!( + <Module<Test>>::validate_unsigned(&Call::claim(0, bob_sig(&1u64.encode()))), + TransactionValidity::Invalid(-20) + ); + }); + } }