Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions polkadot/runtime/common/src/assigned_slots/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,28 @@ mod tests {
}
}

impl<C> frame_system::offchain::CreateTransaction<C> for Test
where
RuntimeCall: From<C>,
{
type Extension = ();
fn create_transaction(
call: <Self as frame_system::offchain::CreateTransactionBase<C>>::RuntimeCall,
extension: Self::Extension,
) -> Self::Extrinsic {
UncheckedExtrinsic::new_transaction(call, extension)
}
}

impl<C> frame_system::offchain::CreateAuthorizedTransaction<C> for Test
where
RuntimeCall: From<C>,
{
fn create_extension() -> Self::Extension {
()
}
}

#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
Expand Down
23 changes: 11 additions & 12 deletions polkadot/runtime/common/src/claims/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ use frame_support::{
};
use frame_system::RawOrigin;
use secp_utils::*;
use sp_runtime::{
traits::{DispatchTransaction, ValidateUnsigned},
DispatchResult,
};
use sp_runtime::{traits::DispatchTransaction, DispatchResult};

const SEED: u32 = 0;

Expand Down Expand Up @@ -74,7 +71,7 @@ fn create_claim_attest<T: Config>(input: u32) -> DispatchResult {
mod benchmarks {
use super::*;

// Benchmark `claim` including `validate_unsigned` logic.
// Benchmark `claim` including `authorize` logic.
#[benchmark]
fn claim() -> Result<(), BenchmarkError> {
let c = MAX_CLAIMS;
Expand All @@ -95,17 +92,18 @@ mod benchmarks {
None,
)?;
assert_eq!(Claims::<T>::get(eth_address), Some(VALUE.into()));
let source = sp_runtime::transaction_validity::TransactionSource::External;
let call_enc =
Call::<T>::claim { dest: account.clone(), ethereum_signature: signature.clone() }
.encode();

#[block]
{
use frame_support::pallet_prelude::Authorize;
let call = <Call<T> as Decode>::decode(&mut &*call_enc)
.expect("call is encoded above, encoding must be correct");
super::Pallet::<T>::validate_unsigned(source, &call)
.map_err(|e| -> &'static str { e.into() })?;
call.authorize(TransactionSource::External)
.expect("Call give some authorization")
.expect("Authorization is valid");
call.dispatch_bypass_filter(RawOrigin::None.into())?;
}

Expand All @@ -132,7 +130,7 @@ mod benchmarks {
Ok(())
}

// Benchmark `claim_attest` including `validate_unsigned` logic.
// Benchmark `claim_attest` including `authorize` logic.
#[benchmark]
fn claim_attest() -> Result<(), BenchmarkError> {
let c = MAX_CLAIMS;
Expand Down Expand Up @@ -162,14 +160,15 @@ mod benchmarks {
statement: StatementKind::Regular.to_text().to_vec(),
}
.encode();
let source = sp_runtime::transaction_validity::TransactionSource::External;

#[block]
{
use frame_support::pallet_prelude::Authorize;
let call = <Call<T> as Decode>::decode(&mut &*call_enc)
.expect("call is encoded above, encoding must be correct");
super::Pallet::<T>::validate_unsigned(source, &call)
.map_err(|e| -> &'static str { e.into() })?;
call.authorize(TransactionSource::External)
.expect("Call give some authorization")
.expect("Authorization is valid");
call.dispatch_bypass_filter(RawOrigin::None.into())?;
}

Expand Down
17 changes: 11 additions & 6 deletions polkadot/runtime/common/src/claims/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,18 @@ use secp_utils::*;
use crate::claims;
use frame_support::{derive_impl, ord_parameter_types, parameter_types, traits::WithdrawReasons};
use pallet_balances;
use sp_runtime::{traits::Identity, BuildStorage};
use sp_runtime::{testing::UintAuthorityId, traits::Identity, BuildStorage};

type Block = frame_system::mocking::MockBlock<Test>;
pub type TransactionExtension = frame_system::AuthorizeCall<Test>;

pub type Header = sp_runtime::generic::Header<u64, sp_runtime::traits::BlakeTwo256>;
pub type Block = sp_runtime::generic::Block<Header, UncheckedExtrinsic>;
pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic<
u64,
RuntimeCall,
UintAuthorityId,
TransactionExtension,
>;

frame_support::construct_runtime!(
pub enum Test
Expand All @@ -41,12 +50,8 @@ frame_support::construct_runtime!(

#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type Block = Block;
type RuntimeEvent = RuntimeEvent;
type AccountData = pallet_balances::AccountData<u64>;
type MaxConsumers = frame_support::traits::ConstU32<16>;
}

#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
Expand Down
83 changes: 60 additions & 23 deletions polkadot/runtime/common/src/claims/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,13 +307,14 @@ pub mod pallet {
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}

#[pallet::call]
#[pallet::call(weight = T::WeightInfo)]
impl<T: Config> Pallet<T> {
/// Make a claim to collect your DOTs.
///
/// The dispatch origin for this call must be _None_.
/// The dispatch origin for this call must be _None_ or _Authorized_.
/// It can be dispatched with a general transaction or an unsigned transaction.
///
/// Unsigned Validation:
/// Validation/Authorization:
/// A call to claim is deemed valid if the signature provided matches
/// the expected signed message of:
///
Expand All @@ -334,13 +335,24 @@ pub mod pallet {
/// Total Complexity: O(1)
/// </weight>
#[pallet::call_index(0)]
#[pallet::authorize(|_source, dest, ethereum_sig|
Pallet::<T>::validate_claim(dest, ethereum_sig, None).map(|v| (v, Weight::zero()))
)]
#[pallet::weight(T::WeightInfo::claim())]
// Because the weight of both "validate unsigned" and "authorize" logic needs to be added
// to the weight of the extrinsic, and because "validate unsigned" weight must be included
// in the call weight, the "authorize weight is also included in the call weight.
// Thus we set it to zero here.
#[pallet::weight_of_authorize(Weight::zero())]
pub fn claim(
origin: OriginFor<T>,
dest: T::AccountId,
ethereum_signature: EcdsaSignature,
) -> DispatchResult {
ensure_none(origin)?;
let is_authorized = ensure_authorized(origin.clone()).is_ok();
let is_none = ensure_none(origin).is_ok();

ensure!(is_authorized || is_none, DispatchError::BadOrigin);

let data = dest.using_encoded(to_ascii_hex);
let signer = Self::eth_recover(&ethereum_signature, &data, &[][..])
Expand Down Expand Up @@ -390,9 +402,10 @@ pub mod pallet {

/// Make a claim to collect your DOTs by signing a statement.
///
/// The dispatch origin for this call must be _None_.
/// The dispatch origin for this call must be _None_ or _Authorized_.
/// It can be dispatched with a general transaction or an unsigned transaction.
///
/// Unsigned Validation:
/// Validation/Authorization:
/// A call to `claim_attest` is deemed valid if the signature provided matches
/// the expected signed message of:
///
Expand All @@ -416,14 +429,25 @@ pub mod pallet {
/// Total Complexity: O(1)
/// </weight>
#[pallet::call_index(2)]
#[pallet::authorize(|_source, dest, ethereum_sig, stmt|
Pallet::<T>::validate_claim(dest, ethereum_sig, Some(stmt)).map(|v| (v, Weight::zero()))
)]
#[pallet::weight(T::WeightInfo::claim_attest())]
// Because the weight of both "validate unsigned" and "authorize" logic needs to be added
// to the weight of the extrinsic, and because "validate unsigned" weight must be included
// in the call weight, the "authorize weight is also included in the call weight.
// Thus we set it to zero here.
#[pallet::weight_of_authorize(Weight::zero())]
pub fn claim_attest(
origin: OriginFor<T>,
dest: T::AccountId,
ethereum_signature: EcdsaSignature,
statement: Vec<u8>,
) -> DispatchResult {
ensure_none(origin)?;
let is_authorized = ensure_authorized(origin.clone()).is_ok();
let is_none = ensure_none(origin).is_ok();

ensure!(is_authorized || is_none, DispatchError::BadOrigin);

let data = dest.using_encoded(to_ascii_hex);
let signer = Self::eth_recover(&ethereum_signature, &data, &statement)
Expand Down Expand Up @@ -500,27 +524,36 @@ pub mod pallet {
type Call = Call<T>;

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

let (maybe_signer, maybe_statement) = match call {
match call {
// <weight>
// The weight of this logic is included in the `claim` dispatchable.
// </weight>
Call::claim { dest: account, ethereum_signature } => {
let data = account.using_encoded(to_ascii_hex);
(Self::eth_recover(&ethereum_signature, &data, &[][..]), None)
},
Call::claim { dest: account, ethereum_signature } =>
Self::validate_claim(account, ethereum_signature, None),
// <weight>
// The weight of this logic is included in the `claim_attest` dispatchable.
// </weight>
Call::claim_attest { dest: account, ethereum_signature, statement } => {
let data = account.using_encoded(to_ascii_hex);
(
Self::eth_recover(&ethereum_signature, &data, &statement),
Some(statement.as_slice()),
)
},
Call::claim_attest { dest: account, ethereum_signature, statement } =>
Self::validate_claim(account, ethereum_signature, Some(statement)),
_ => return Err(InvalidTransaction::Call.into()),
}
}
}

impl<T: Config> Pallet<T> {
fn validate_claim(
account: &T::AccountId,
ethereum_signature: &EcdsaSignature,
maybe_statement: Option<&Vec<u8>>,
) -> TransactionValidity {
const PRIORITY: u64 = 100;

let maybe_signer = if let Some(statement) = &maybe_statement {
let data = account.using_encoded(to_ascii_hex);
Self::eth_recover(&ethereum_signature, &data, statement)
} else {
let data = account.using_encoded(to_ascii_hex);
Self::eth_recover(&ethereum_signature, &data, &[][..])
};

let signer = maybe_signer.ok_or(InvalidTransaction::Custom(
Expand All @@ -533,7 +566,7 @@ pub mod pallet {
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),
Some(s) => ensure!(Some(s.to_text()) == maybe_statement.map(|s| s.as_slice()), e),
}

Ok(ValidTransaction {
Expand Down Expand Up @@ -578,7 +611,11 @@ impl<T: Config> Pallet<T> {

// 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], extra: &[u8]) -> Option<EthereumAddress> {
pub(crate) fn eth_recover(
s: &EcdsaSignature,
what: &[u8],
extra: &[u8],
) -> Option<EthereumAddress> {
let msg = keccak_256(&Self::ethereum_signable_message(what, extra));
let mut res = EthereumAddress::default();
res.0
Expand Down
Loading
Loading