From e508c32027e40a904bbb44c116ddf198fbc3e2e7 Mon Sep 17 00:00:00 2001 From: georgepisaltu Date: Mon, 25 Mar 2024 12:39:20 +0200 Subject: [PATCH 1/2] Changes to sponsored fee ext Signed-off-by: georgepisaltu --- polkadot/runtime/westend/src/lib.rs | 673 +++++++++++++++--- .../support/src/transaction_extensions.rs | 180 ++++- .../transaction-payment/src/benchmarking.rs | 30 +- .../frame/transaction-payment/src/lib.rs | 108 ++- .../runtime/src/generic/checked_extrinsic.rs | 8 +- .../primitives/runtime/src/generic/mod.rs | 2 +- .../src/generic/unchecked_extrinsic.rs | 12 +- .../dispatch_transaction.rs | 33 +- 8 files changed, 861 insertions(+), 185 deletions(-) diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 9e95bbd224fd..d7ebc9470633 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -29,21 +29,22 @@ use frame_election_provider_support::{bounds::ElectionBoundsBuilder, onchain, Se use frame_support::{ construct_runtime, derive_impl, genesis_builder_helper::{build_config, create_default_config}, + pallet_prelude::{InvalidTransaction, TransactionValidityError}, parameter_types, traits::{ fungible::HoldConsideration, ConstU32, Contains, EitherOf, EitherOfDiverse, EverythingBut, - InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, ProcessMessage, + ExtrinsicCall, InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, ProcessMessage, ProcessMessageError, WithdrawReasons, }, weights::{ConstantMultiplier, WeightMeter}, PalletId, }; -use frame_system::{EnsureRoot, EnsureSigned}; +use frame_system::{ChainContext, EnsureRoot, EnsureSigned}; use pallet_grandpa::{fg_primitives, AuthorityId as GrandpaId}; use pallet_identity::legacy::IdentityInfo; use pallet_session::historical as session_historical; use pallet_transaction_payment::{CurrencyAdapter, FeeDetails, RuntimeDispatchInfo}; -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use parity_scale_codec::{Decode, Encode, EncodeLike, Error as CodecError, Input, MaxEncodedLen}; use primitives::{ slashing, vstaging::{ApprovalVotingParams, NodeFeatures}, @@ -81,19 +82,22 @@ use runtime_parachains::{ scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; -use scale_info::TypeInfo; +use scale_info::{Type, TypeInfo}; use sp_core::{OpaqueMetadata, RuntimeDebug, H256}; use sp_runtime::{ create_runtime_str, curve::PiecewiseLinear, - generic, impl_opaque_keys, + generic::{self, ExtrinsicFormat, Preamble}, + impl_opaque_keys, traits::{ - BlakeTwo256, Block as BlockT, ConvertInto, Extrinsic as ExtrinsicT, IdentityLookup, - Keccak256, OpaqueKeys, SaturatedConversion, Verify, + BlakeTwo256, Block as BlockT, Checkable, ConvertInto, DispatchInfoOf, Dispatchable, + Extrinsic as ExtrinsicT, ExtrinsicMetadata, IdentityLookup, Keccak256, Lookup, OpaqueKeys, + PostDispatchInfoOf, SaturatedConversion, TransactionExtension, TransactionExtensionBase, + ValidateUnsigned, Verify, }, transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, BoundToRuntimeAppPublic, FixedU128, KeyTypeId, Perbill, Percent, Permill, - RuntimeAppPublic, + ApplyExtrinsicResult, ApplyExtrinsicResultWithInfo, BoundToRuntimeAppPublic, FixedU128, + KeyTypeId, Perbill, Percent, Permill, RuntimeAppPublic, }; use sp_staking::SessionIndex; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; @@ -822,33 +826,56 @@ where // so the actual block number is `n`. .saturating_sub(1); let tip = 0; - let tx_ext: TxExtension = ( - pallet_transaction_payment::SetFeeAgent::::default(), - frame_support::transaction_extensions::SignedOriginSignature::< + let inner_tx: ( + pallet_transaction_payment::SetFeeAgent< sp_runtime::MultiSignature, - >::default(), - frame_system::CheckNonZeroSender::::new(), - frame_system::CheckSpecVersion::::new(), - frame_system::CheckTxVersion::::new(), - frame_system::CheckGenesis::::new(), - frame_system::CheckMortality::::from(generic::Era::mortal( - period, - current_block, - )), - frame_system::CheckNonce::::from(nonce), - frame_system::CheckWeight::::new(), - pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + ( + frame_system::CheckNonce, + frame_support::transaction_extensions::CheckSignedPayload< + sp_runtime::MultiSignature, + ( + RuntimeCall, + BaseTxExtension, + ::Implicit, + ), + >, + ), + >, + BaseTxExtension, + ) = ( + pallet_transaction_payment::SetFeeAgent::default(), + ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckMortality::::from(generic::Era::mortal( + period, + current_block, + )), + frame_system::CheckNonce::::from(nonce), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + ), ) .into(); - let raw_payload = SignedPayload::new(call, tx_ext) + let tx_implicit = inner_tx + .implicit() .map_err(|e| { log::warn!("Unable to create signed payload: {:?}", e); }) .ok()?; - let signature = raw_payload.using_encoded(|payload| C::sign(payload, public))?; - let (call, tx_ext, _) = raw_payload.deconstruct(); - let address = ::Lookup::unlookup(account); - Some((call, (address, signature, tx_ext))) + let raw_payload = (call, inner_tx, tx_implicit); + let signature = raw_payload + .using_encoded(|payload| C::sign(&sp_io::hashing::blake2_256(payload)[..], public))?; + let (call, inner_tx, _) = raw_payload; + let address = ::Lookup::unlookup(account.clone()); + let tx_ext = frame_support::transaction_extensions::SignedOriginSignature::new_with_sign( + signature.clone(), + account, + inner_tx, + ); + Some((call, (address, signature, (tx_ext,)))) } } @@ -1555,10 +1582,7 @@ pub type Block = generic::Block; pub type SignedBlock = generic::SignedBlock; /// `BlockId` type as expected by this runtime. pub type BlockId = generic::BlockId; -/// The extension to the basic transaction logic. -pub type TxExtension = ( - pallet_transaction_payment::SetFeeAgent, - frame_support::transaction_extensions::SignedOriginSignature, +pub type BaseTxExtension = ( frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, @@ -1568,6 +1592,173 @@ pub type TxExtension = ( frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, ); +pub type InnerUnsigned = ( + pallet_transaction_payment::SetFeeAgent< + sp_runtime::MultiSignature, + ( + frame_system::CheckNonce, + frame_support::transaction_extensions::CheckSignedPayload< + sp_runtime::MultiSignature, + ( + RuntimeCall, + BaseTxExtension, + ::Implicit, + ), + >, + ), + >, + BaseTxExtension, +); +/// The extension to the basic transaction logic. +pub type TxExtension = ( + frame_support::transaction_extensions::SignedOriginSignature< + sp_runtime::MultiSignature, + ( + pallet_transaction_payment::SetFeeAgent< + sp_runtime::MultiSignature, + ( + frame_system::CheckNonce, + frame_support::transaction_extensions::CheckSignedPayload< + sp_runtime::MultiSignature, + ( + RuntimeCall, + BaseTxExtension, + ::Implicit, + ), + >, + ), + >, + BaseTxExtension, + ), + >, +); + +impl sp_runtime::traits::Applyable for CheckedExtrinsic { + type Call = RuntimeCall; + + fn validate>( + &self, + source: TransactionSource, + info: &DispatchInfoOf, + len: usize, + ) -> TransactionValidity { + match self.0.format { + ExtrinsicFormat::Bare => { + let inherent_validation = I::validate_unsigned(source, &self.0.function)?; + #[allow(deprecated)] + let legacy_validation = TxExtension::validate_bare_compat(&self.0.function, info, len)?; + Ok(legacy_validation.combine_with(inherent_validation)) + }, + ExtrinsicFormat::Signed(ref signer, ref extension) => { + let origin = Some(signer.clone()).into(); + extension + .0 + .extension + .validate( + origin, + &self.0.function, + info, + len, + &mut Default::default(), + extension.0.extension.implicit()?, + &self.0.function, + ) + .map(|x| x.0) + }, + ExtrinsicFormat::General(ref extension) => { + let mut context = pallet_transaction_payment::Context::default(); + extension + .validate( + None.into(), + &self.0.function, + info, + len, + &mut context, + extension.implicit()?, + &self.0.function, + ) + .map(|x| x.0) + }, + } + } + + fn apply>( + self, + info: &DispatchInfoOf, + len: usize, + ) -> crate::ApplyExtrinsicResultWithInfo> { + match self.0.format { + ExtrinsicFormat::Bare => { + I::pre_dispatch(&self.0.function)?; + // TODO: Remove below once `pre_dispatch_unsigned` is removed from `LegacyExtension` + // or `LegacyExtension` is removed. + #[allow(deprecated)] + TxExtension::validate_bare_compat(&self.0.function, info, len)?; + #[allow(deprecated)] + TxExtension::pre_dispatch_bare_compat(&self.0.function, info, len)?; + let res = self.0.function.dispatch(None.into()); + let post_info = res.unwrap_or_else(|err| err.post_info); + let pd_res = res.map(|_| ()).map_err(|e| e.error); + // TODO: Remove below once `pre_dispatch_unsigned` is removed from `LegacyExtension` + // or `LegacyExtension` is removed. + #[allow(deprecated)] + TxExtension::post_dispatch_bare_compat(info, &post_info, len, &pd_res)?; + Ok(res) + }, + ExtrinsicFormat::Signed(signer, extension) => { + // extension.dispatch_transaction(Some(signer).into(), self.function, info, len), + let inner_extension: InnerUnsigned = extension.0.extension; + let mut context = pallet_transaction_payment::Context::default(); + let (_, val, origin) = inner_extension + .validate( + Some(signer).into(), + &self.0.function, + info, + len, + &mut context, + inner_extension.implicit().unwrap(), + &self.0.function, + ) + .unwrap(); + let pre = + inner_extension.prepare(val, &origin, &self.0.function, info, len, &context)?; + let res = self.0.function.dispatch(origin); + let post_info = res.unwrap_or_else(|err| err.post_info); + let pd_res = res.map(|_| ()).map_err(|e| e.error); + InnerUnsigned::post_dispatch(pre, info, &post_info, len, &pd_res, &context)?; + Ok(res) + }, + ExtrinsicFormat::General(extension) => + // extension.dispatch_transaction(None.into(), self.function, info, len), + { + let mut context = pallet_transaction_payment::Context::default(); + let (_, val, origin) = extension + .validate( + None.into(), + &self.0.function, + &info, + len, + &mut context, + extension.implicit().unwrap(), + &self.0.function, + ) + .unwrap(); + let pre = extension.prepare(val, &origin, &self.0.function, info, len, &context)?; + let res = self.0.function.dispatch(origin); + let post_info = res.unwrap_or_else(|err| err.post_info); + let pd_res = res.map(|_| ()).map_err(|e| e.error); + TxExtension::post_dispatch(pre, info, &post_info, len, &pd_res, &context)?; + Ok(res) + }, + } + } +} + +impl frame_support::dispatch::GetDispatchInfo for CheckedExtrinsic { + fn get_dispatch_info(&self) -> frame_support::dispatch::DispatchInfo { + self.0.function.get_dispatch_info() + } +} #[test] fn free_transaction_extension_test() { @@ -1578,29 +1769,31 @@ fn free_transaction_extension_test() { }; use keyring::AccountKeyring; use sp_io::hashing::blake2_256; - use sp_runtime::{ - traits::{DispatchTransaction, TransactionExtensionBase}, - MultiSignature, - }; + use sp_runtime::{traits::TransactionExtensionBase, MultiSignature}; - // The part of `TxExtension` that has to be provided and signed by user who wants - // the transaciton fee to be sponsored by someone else. - type BaseTxExtension = ( - frame_system::CheckNonZeroSender, - frame_system::CheckSpecVersion, - frame_system::CheckTxVersion, - frame_system::CheckGenesis, - frame_system::CheckMortality, - frame_system::CheckNonce, - frame_system::CheckWeight, - pallet_transaction_payment::ChargeTransactionPayment, + type CheckSignedInnerTxExtension = + frame_support::transaction_extensions::CheckSignedPayload< + sp_runtime::MultiSignature, + ( + RuntimeCall, + BaseTxExtension, + ::Implicit, + ), + >; + + type InnerUnsignedTxExtension = ( + pallet_transaction_payment::SetFeeAgent< + sp_runtime::MultiSignature, + (frame_system::CheckNonce, CheckSignedInnerTxExtension), + >, + BaseTxExtension, ); - // The part of `TxExtension` that has to be provided and signed by the fee agent, - // the user who sponsors the transaction fee. - type SignedTxExtension = ( - frame_support::transaction_extensions::SignedOriginSignature, - BaseTxExtension, + type OuterSignedTxExtension = ( + frame_support::transaction_extensions::SignedOriginSignature< + sp_runtime::MultiSignature, + InnerUnsignedTxExtension, + >, ); frame_system::GenesisConfig::::default().build(); @@ -1609,13 +1802,16 @@ fn free_transaction_extension_test() { // Alice wants the transaction fee to be sponsored by Bob. let alice_keyring = AccountKeyring::Alice; let bob_keyring = AccountKeyring::Bob; + let charlie_keyring = AccountKeyring::Charlie; let alice_account = AccountId::from(alice_keyring.public()); let bob_account = AccountId::from(bob_keyring.public()); + let charlie_account = AccountId::from(charlie_keyring.public()); // Setup the initial balances. let alice_balance = 10 * ExistentialDeposit::get(); let bob_balance = 10 * ExistentialDeposit::get(); + let charlie_balance = 10 * ExistentialDeposit::get(); Balances::force_set_balance( RuntimeOrigin::root(), @@ -1627,11 +1823,23 @@ fn free_transaction_extension_test() { Balances::force_set_balance(RuntimeOrigin::root(), bob_account.clone().into(), bob_balance) .unwrap(); + Balances::force_set_balance( + RuntimeOrigin::root(), + charlie_account.clone().into(), + charlie_balance, + ) + .unwrap(); + // The call that Alice wants to be executed. let call = RuntimeCall::System(frame_system::Call::remark_with_event { remark: vec![1] }); + let alice_initial_nonce = frame_system::Pallet::::account(&alice_account).nonce; + let bob_initial_nonce = frame_system::Pallet::::account(&bob_account).nonce; + let charlie_initial_nonce = + frame_system::Pallet::::account(&charlie_account).nonce; + // Alice builds the transaction extension for the sponsored transaction. - let stmt_ext: BaseTxExtension = ( + let alice_base_ext: BaseTxExtension = ( frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), @@ -1650,60 +1858,168 @@ fn free_transaction_extension_test() { ) .into(); + let alice_base_ext_implicit = alice_base_ext.implicit().unwrap(); + // Alice signs the transaction extension and shares it. - let stmt_sign = MultiSignature::Sr25519( - (call.clone(), stmt_ext.clone(), stmt_ext.implicit().unwrap()) + let alice_base_sign = MultiSignature::Sr25519( + (call.clone(), alice_base_ext.clone(), alice_base_ext_implicit.clone()) .using_encoded(|e| alice_keyring.sign(&blake2_256(e))), ); - let statement = (call.clone(), stmt_ext, stmt_sign).encode(); - - type Statement = (RuntimeCall, BaseTxExtension, MultiSignature); - let (stmt_call, stmt_ext, stmt_sign) = Statement::decode(&mut &statement[..]).unwrap(); - // Bob constructs the transaction based on Alice statemnt. - let signed_tx_ext = frame_support::transaction_extensions::SignedOriginSignature::< - MultiSignature, - >::new_with_sign(stmt_sign, alice_account.clone()); + let alice_signed_tx_ext = CheckSignedInnerTxExtension::new_with_sign( + alice_base_sign, + alice_account.clone(), + (call.clone(), alice_base_ext.clone(), alice_base_ext_implicit.clone()), + ); - let mut signed_tx_ext = signed_tx_ext.encode(); - signed_tx_ext.append(&mut stmt_ext.encode()); - let signed_tx_ext: SignedTxExtension = - SignedTxExtension::decode(&mut &signed_tx_ext[..]).unwrap(); + let bob_unsigned_tx_ext = ( + pallet_transaction_payment::SetFeeAgent::new_with_agent( + bob_account.clone(), + ( + frame_system::CheckNonce::::from( + frame_system::Pallet::::account(&bob_account).nonce, + ), + alice_signed_tx_ext, + ), + ), + alice_base_ext, + ); - // Bob signs the transaction with Alice's part to poof he is willing to sponser the fee. - let signed_tx_sign = MultiSignature::Sr25519( - (stmt_call, signed_tx_ext.clone(), signed_tx_ext.implicit().unwrap()) + let bob_unsigned_tx_ext_implicit = bob_unsigned_tx_ext.implicit().unwrap(); + let bob_sign = MultiSignature::Sr25519( + (call.clone(), bob_unsigned_tx_ext.clone(), bob_unsigned_tx_ext_implicit) .using_encoded(|e| bob_keyring.sign(&blake2_256(e))), ); - let tx_ext = pallet_transaction_payment::SetFeeAgent::::new_with_agent( - signed_tx_sign, - bob_account.clone(), + let bob_signed_tx_ext: OuterSignedTxExtension = + (frame_support::transaction_extensions::SignedOriginSignature::new_with_sign( + bob_sign, + bob_account.clone(), + bob_unsigned_tx_ext, + ),); + + // Dispatch the transaction + { + let mut context = pallet_transaction_payment::Context::default(); + let info = DispatchInfo::default(); + let len = call.encoded_size(); + let (_, val, origin) = bob_signed_tx_ext + .validate( + RawOrigin::None.into(), + &call, + &info, + len, + &mut context, + bob_signed_tx_ext.implicit().unwrap(), + &call, + ) + .unwrap(); + let pre = bob_signed_tx_ext.prepare(val, &origin, &call, &info, len, &context).unwrap(); + let res = call.dispatch(origin); + let post_info = res.unwrap_or_else(|err| err.post_info); + let pd_res = res.map(|_| ()).map_err(|e| e.error); + OuterSignedTxExtension::post_dispatch(pre, &info, &post_info, len, &pd_res, &context) + .unwrap(); + } + + // Alice balance is unchanged, Bob paid the transaction fee. + assert_eq!(alice_balance, Balances::free_balance(alice_account.clone())); + assert!(bob_balance > Balances::free_balance(bob_account.clone())); + + assert!(System::events().iter().any(|ev| ev.event == + RuntimeEvent::System(frame_system::Event::Remarked { + sender: alice_account.clone(), + hash: + <::Hashing as sp_runtime::traits::Hash>::hash( + &[1u8] + ) + }))); + + assert_eq!( + alice_initial_nonce.saturating_add(1), + frame_system::Pallet::::account(&alice_account).nonce + ); + assert_eq!( + bob_initial_nonce.saturating_add(1), + frame_system::Pallet::::account(&bob_account).nonce ); - let mut tx_ext_encoded = tx_ext.encode(); - tx_ext_encoded.append(&mut signed_tx_ext.encode()); + // The call that Charlie wants to be executed. + let call = RuntimeCall::System(frame_system::Call::remark_with_event { remark: vec![3] }); + + let charlie_unsigned_tx_ext = ( + pallet_transaction_payment::SetFeeAgent::default(), + ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckMortality::::from(sp_runtime::generic::Era::immortal()), + frame_system::CheckNonce::::from( + frame_system::Pallet::::account(&charlie_account).nonce, + ), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(0), + ), + ); - // The final valid for submission transaction extension. - let tx_ext: TxExtension = TxExtension::decode(&mut &tx_ext_encoded[..]).unwrap(); + let charlie_unsigned_tx_ext_implicit = charlie_unsigned_tx_ext.implicit().unwrap(); + let charlie_sign = MultiSignature::Sr25519( + (call.clone(), charlie_unsigned_tx_ext.clone(), charlie_unsigned_tx_ext_implicit) + .using_encoded(|e| charlie_keyring.sign(&blake2_256(e))), + ); - let _ = SignedPayload::new(call.clone(), tx_ext.clone()).unwrap(); + let charlie_signed_tx_ext: OuterSignedTxExtension = + (frame_support::transaction_extensions::SignedOriginSignature::new_with_sign( + charlie_sign, + charlie_account.clone(), + charlie_unsigned_tx_ext, + ),); - let _ = TxExtension::dispatch_transaction( - tx_ext, - RawOrigin::None.into(), - call, - &DispatchInfo::default(), - 0, - ) - .unwrap(); + // Dispatch the transaction + { + let mut context = pallet_transaction_payment::Context::default(); + let info = DispatchInfo::default(); + let len = call.encoded_size(); + let (_, val, origin) = charlie_signed_tx_ext + .validate( + RawOrigin::None.into(), + &call, + &info, + len, + &mut context, + charlie_signed_tx_ext.implicit().unwrap(), + &call, + ) + .unwrap(); + let pre = charlie_signed_tx_ext + .prepare(val, &origin, &call, &info, len, &context) + .unwrap(); + let res = call.dispatch(origin); + let post_info = res.unwrap_or_else(|err| err.post_info); + let pd_res = res.map(|_| ()).map_err(|e| e.error); + OuterSignedTxExtension::post_dispatch(pre, &info, &post_info, len, &pd_res, &context) + .unwrap(); + } + + assert!(charlie_balance > Balances::free_balance(charlie_account.clone())); + + assert!(System::events().iter().any(|ev| ev.event == + RuntimeEvent::System(frame_system::Event::Remarked { + sender: charlie_account.clone(), + hash: + <::Hashing as sp_runtime::traits::Hash>::hash( + &[3u8] + ) + }))); - // Alice balance is unchanged, Bob paid the transaction fee. - assert_eq!(alice_balance, Balances::free_balance(alice_account)); - assert!(bob_balance > Balances::free_balance(bob_account)); + assert_eq!( + charlie_initial_nonce.saturating_add(1), + frame_system::Pallet::::account(&charlie_account).nonce + ); }); } @@ -1859,13 +2175,167 @@ pub mod migrations { } /// Unchecked extrinsic type as expected by this runtime. -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic< - Address, - RuntimeCall, - Signature, - TxExtension, - pallet_transaction_payment::Context, ->; +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct UncheckedExtrinsic( + pub generic::UncheckedExtrinsic< + Address, + RuntimeCall, + Signature, + TxExtension, + pallet_transaction_payment::Context, + >, +); + +impl frame_support::dispatch::GetDispatchInfo for UncheckedExtrinsic { + fn get_dispatch_info(&self) -> frame_support::dispatch::DispatchInfo { + self.0.function.get_dispatch_info() + } +} + +impl TypeInfo for UncheckedExtrinsic { + type Identity = generic::UncheckedExtrinsic< + Address, + RuntimeCall, + Signature, + TxExtension, + pallet_transaction_payment::Context, + >; + + fn type_info() -> Type { + generic::UncheckedExtrinsic::< + Address, + RuntimeCall, + Signature, + TxExtension, + pallet_transaction_payment::Context, + >::type_info() + } +} + +impl ExtrinsicMetadata for UncheckedExtrinsic { + const VERSION: u8 = 5; + type Extra = TxExtension; +} + +type UncheckedSignaturePayload = + sp_runtime::generic::UncheckedSignaturePayload; + +impl ExtrinsicT for UncheckedExtrinsic { + type Call = RuntimeCall; + + type SignaturePayload = UncheckedSignaturePayload; + + fn is_bare(&self) -> bool { + matches!(self.0.preamble, Preamble::Bare) + } + + fn is_signed(&self) -> Option { + Some(matches!(self.0.preamble, Preamble::Signed(..))) + } + + fn new(function: Self::Call, signed_data: Option) -> Option { + Some(if let Some((address, signature, extra)) = signed_data { + Self(generic::UncheckedExtrinsic::new_signed(function, address, signature, extra)) + } else { + Self(generic::UncheckedExtrinsic::new_bare(function)) + }) + } + + fn new_inherent(function: Self::Call) -> Self { + Self(generic::UncheckedExtrinsic::new_bare(function)) + } +} + +impl ExtrinsicCall for UncheckedExtrinsic { + fn call(&self) -> &RuntimeCall { + &self.0.function + } +} + +impl Encode for UncheckedExtrinsic { + fn using_encoded R>(&self, f: F) -> R { + self.0.using_encoded(f) + } +} + +impl EncodeLike for UncheckedExtrinsic {} + +impl Decode for UncheckedExtrinsic { + fn decode(input: &mut I) -> Result { + generic::UncheckedExtrinsic::< + Address, + RuntimeCall, + Signature, + TxExtension, + pallet_transaction_payment::Context, + >::decode(input) + .map(|inner| Self(inner)) + } +} + +impl serde::Serialize for UncheckedExtrinsic { + fn serialize(&self, seq: S) -> Result + where + S: serde::Serializer, + { + self.using_encoded(|bytes| seq.serialize_bytes(bytes)) + } +} + +impl<'a> serde::Deserialize<'a> for UncheckedExtrinsic { + fn deserialize(de: D) -> Result + where + D: serde::Deserializer<'a>, + { + let r = sp_core::bytes::deserialize(de)?; + Self::decode(&mut &r[..]).map_err(|_e| serde::de::Error::custom("Decode error")) + } +} + +impl Checkable> for UncheckedExtrinsic { + type Checked = CheckedExtrinsic; + + fn check( + self, + lookup: &ChainContext, + ) -> Result { + Ok(match self.0.preamble { + Preamble::Signed(signed, signature, tx_ext) => { + let signed = lookup.lookup(signed)?; + // The `Implicit` is "implicitly" included in the payload. + let raw_payload = SignedPayload::new(self.0.function, tx_ext)?; + if !raw_payload.using_encoded(|payload| signature.verify(payload, &signed)) { + return Err(InvalidTransaction::BadProof.into()) + } + let (function, tx_ext, _) = raw_payload.deconstruct(); + CheckedExtrinsic(generic::CheckedExtrinsic { + format: ExtrinsicFormat::Signed(signed, tx_ext), + function, + _phantom: Default::default(), + }) + }, + Preamble::General(tx_ext) => CheckedExtrinsic(generic::CheckedExtrinsic { + format: ExtrinsicFormat::General(tx_ext), + function: self.0.function, + _phantom: Default::default(), + }), + Preamble::Bare => CheckedExtrinsic(generic::CheckedExtrinsic { + format: ExtrinsicFormat::Bare, + function: self.0.function, + _phantom: Default::default(), + }), + }) + } + + #[cfg(feature = "try-runtime")] + fn unchecked_into_checked_i_know_what_i_am_doing( + self, + _lookup: &ChainContext, + ) -> Result { + todo!(); + } +} + /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< Runtime, @@ -1878,6 +2348,15 @@ pub type Executive = frame_executive::Executive< /// The payload being signed in transactions. pub type SignedPayload = generic::SignedPayload; +pub struct CheckedExtrinsic( + pub generic::CheckedExtrinsic< + AccountId, + RuntimeCall, + TxExtension, + pallet_transaction_payment::Context, + >, +); + #[cfg(feature = "runtime-benchmarks")] mod benches { frame_benchmarking::define_benchmarks!( diff --git a/substrate/frame/support/src/transaction_extensions.rs b/substrate/frame/support/src/transaction_extensions.rs index 2bd8f94a0b5b..050431be6840 100644 --- a/substrate/frame/support/src/transaction_extensions.rs +++ b/substrate/frame/support/src/transaction_extensions.rs @@ -94,67 +94,205 @@ where /// Transaction extension that sets the origin to the given account ID if the provided signature by /// that account is valid for all subsequent extensions. If signature is not provided, this -/// extension is no-op. +/// extension is no-op. Will run wrapped extension logic after the origin validation. // TODO better doc. #[derive( CloneNoBound, EqNoBound, PartialEqNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, )] #[codec(encode_bound())] #[codec(decode_bound())] -pub struct SignedOriginSignature +pub struct SignedOriginSignature where V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, + InnerTx: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, { - signature: Option<(V, ::AccountId)>, + pub signature: Option<(V, ::AccountId)>, + pub extension: InnerTx, } -impl Default for SignedOriginSignature +impl SignedOriginSignature where V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, + InnerTx: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, { - fn default() -> Self { - Self { signature: None } + pub fn new_with_sign( + signature: V, + account_id: ::AccountId, + extension: InnerTx, + ) -> Self { + Self { signature: Some((signature, account_id)), extension } } } -impl SignedOriginSignature +impl TransactionExtensionBase for SignedOriginSignature +where + V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, + ::AccountId: + Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, + InnerTx: Codec + + Debug + + Sync + + Send + + Clone + + Eq + + PartialEq + + StaticTypeInfo + + TransactionExtensionBase, +{ + const IDENTIFIER: &'static str = "SignedOriginSignature"; + type Implicit = (); +} + +impl + TransactionExtension for SignedOriginSignature where V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, + ::RuntimeOrigin: From::AccountId>>, + InnerTx: Codec + + Debug + + Sync + + Send + + Clone + + Eq + + PartialEq + + StaticTypeInfo + + TransactionExtension, +{ + type Val = InnerTx::Val; + type Pre = InnerTx::Pre; + + fn validate( + &self, + origin: ::RuntimeOrigin, + call: &Call, + info: &DispatchInfoOf, + len: usize, + context: &mut Context, + _self_implicit: (), + inherited_implication: &impl Encode, + ) -> Result< + (ValidTransaction, Self::Val, ::RuntimeOrigin), + TransactionValidityError, + > { + let (signature, account_id) = match &self.signature { + Some((s, a)) => (s, a.clone()), // TODO check if origin None + None => { + let implicit = self.extension.implicit()?; + return self.extension.validate( + origin, + call, + info, + len, + context, + implicit, + inherited_implication, + ) + }, + }; + + let implicit = self.extension.implicit()?; + let signed_payload = (call, &self.extension, &implicit); + if !signed_payload + .using_encoded(|payload| signature.verify(&blake2_256(payload)[..], &account_id)) + { + return Err(InvalidTransaction::BadProof.into()) + } + + let origin = Some(account_id).into(); + self.extension + .validate(origin, call, info, len, context, implicit, inherited_implication) + } + + fn prepare( + self, + val: Self::Val, + origin: &sp_runtime::traits::OriginOf, + call: &Call, + info: &DispatchInfoOf, + len: usize, + context: &Context, + ) -> Result { + self.extension.prepare(val, origin, call, info, len, context) + } + + fn post_dispatch( + pre: Self::Pre, + info: &DispatchInfoOf, + post_info: &sp_runtime::traits::PostDispatchInfoOf, + len: usize, + result: &sp_runtime::DispatchResult, + context: &Context, + ) -> Result<(), TransactionValidityError> { + InnerTx::post_dispatch(pre, info, post_info, len, result, context) + } +} + +/// Transaction extension that sets the origin to the given account ID if the provided signature by +/// that account is valid for the provided payload. +#[derive( + CloneNoBound, EqNoBound, PartialEqNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, +)] +#[codec(encode_bound())] +#[codec(decode_bound())] +pub struct CheckSignedPayload +where + V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, + ::AccountId: + Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, + P: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, +{ + pub signature: Option<(V, ::AccountId, P)>, +} + +impl CheckSignedPayload +where + V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, + ::AccountId: + Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, + P: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, { pub fn new_with_sign( signature: V, account_id: ::AccountId, + payload: P, ) -> Self { - Self { signature: Some((signature, account_id)) } + Self { signature: Some((signature, account_id, payload)) } + } + + pub fn new() -> Self { + Self { signature: None } } } -impl TransactionExtensionBase for SignedOriginSignature +impl TransactionExtensionBase for CheckSignedPayload where V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, + P: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, { - const IDENTIFIER: &'static str = "SignedOriginSignature"; + const IDENTIFIER: &'static str = "CheckSignedPayload"; type Implicit = (); } -impl TransactionExtension - for SignedOriginSignature +impl + TransactionExtension for CheckSignedPayload where V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, + P: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::RuntimeOrigin: From::AccountId>>, { type Val = (); type Pre = (); + impl_tx_ext_default!(Call; Context; prepare); fn validate( @@ -163,23 +301,23 @@ where _call: &Call, _info: &DispatchInfoOf, _len: usize, - _: &mut Context, - _: (), - inherited_implication: &impl Encode, + _context: &mut Context, + _self_implicit: (), + _inherited_implication: &impl Encode, ) -> Result< (ValidTransaction, Self::Val, ::RuntimeOrigin), TransactionValidityError, > { - let (signature, account_id) = match &self.signature { - Some((s, a)) => (s, a.clone()), // TODO check if origin None + let (signature, account_id, payload) = match &self.signature { + Some((s, a, p)) => (s, a.clone(), p), // TODO check if origin None None => return Ok((ValidTransaction::default(), (), origin)), }; - let msg = inherited_implication.using_encoded(blake2_256); - - if !signature.verify(&msg[..], &account_id) { - Err(InvalidTransaction::BadProof)? + if !payload.using_encoded(|payload| signature.verify(&blake2_256(payload)[..], &account_id)) + { + return Err(InvalidTransaction::BadProof.into()) } + let origin = Some(account_id).into(); Ok((ValidTransaction::default(), (), origin)) } diff --git a/substrate/frame/transaction-payment/src/benchmarking.rs b/substrate/frame/transaction-payment/src/benchmarking.rs index d63c5a7d746e..dfafa64b4170 100644 --- a/substrate/frame/transaction-payment/src/benchmarking.rs +++ b/substrate/frame/transaction-payment/src/benchmarking.rs @@ -22,7 +22,7 @@ use crate::Pallet; use frame_benchmarking::v2::*; use frame_support::dispatch::{DispatchInfo, PostDispatchInfo}; use frame_system::{EventRecord, RawOrigin}; -use sp_runtime::traits::{DispatchTransaction, Dispatchable}; +use sp_runtime::traits::Dispatchable; fn assert_last_event(generic_event: ::RuntimeEvent) { let events = frame_system::Pallet::::events(); @@ -60,15 +60,31 @@ mod benchmarks { actual_weight: Some(Weight::from_parts(10, 0)), pays_fee: Pays::Yes, }; + let mut context: Context = Context::default(); #[block] { - assert!(ext - .test_run(RawOrigin::Signed(caller.clone()).into(), &call, &info, 10, |_| Ok( - post_info - )) - .unwrap() - .is_ok()); + let (_, val, origin) = ext + .validate( + RawOrigin::Signed(caller.clone()).into(), + &call, + &info, + 10, + &mut context, + (), + &call, + ) + .unwrap(); + let pre = ext.prepare(val, &origin, &call, &info, 10, &context).unwrap(); + ChargeTransactionPayment::::post_dispatch( + pre, + &info, + &post_info, + 10, + &Ok(()), + &context, + ) + .unwrap(); } let actual_fee = Pallet::::compute_actual_fee(10, &info, &post_info, tip); diff --git a/substrate/frame/transaction-payment/src/lib.rs b/substrate/frame/transaction-payment/src/lib.rs index 70de8600260c..625a7a188e78 100644 --- a/substrate/frame/transaction-payment/src/lib.rs +++ b/substrate/frame/transaction-payment/src/lib.rs @@ -964,11 +964,7 @@ use codec::Codec; use core::fmt::Debug; use frame_support::{CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound}; use scale_info::StaticTypeInfo; -use sp_io::hashing::blake2_256; -use sp_runtime::{ - impl_tx_ext_default, - traits::{IdentifyAccount, Verify}, -}; +use sp_runtime::traits::{IdentifyAccount, Verify}; #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub enum FeeAgent { @@ -1003,67 +999,79 @@ impl Default for Context { )] #[codec(encode_bound())] #[codec(decode_bound())] -pub struct SetFeeAgent +pub struct SetFeeAgent where V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, + Tx: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, { - pub agent: Option<(V, ::AccountId)>, + pub agent: Option<::AccountId>, + pub rest: Option, } -impl Default for SetFeeAgent +impl Default for SetFeeAgent where V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, + Tx: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, { fn default() -> Self { - Self { agent: None } + Self { agent: None, rest: None } } } -impl SetFeeAgent +impl SetFeeAgent where V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, + Tx: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, { - pub fn new_with_agent( - signature: V, - account_id: ::AccountId, - ) -> Self { - Self { agent: Some((signature, account_id)) } + pub fn new_with_agent(account_id: ::AccountId, tx: Tx) -> Self { + Self { agent: Some(account_id), rest: Some(tx) } } } -impl TransactionExtensionBase for SetFeeAgent +impl TransactionExtensionBase for SetFeeAgent where V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, + Tx: TransactionExtensionBase + + Codec + + Debug + + Sync + + Send + + Clone + + Eq + + PartialEq + + StaticTypeInfo, { const IDENTIFIER: &'static str = "SetFeeAgent"; type Implicit = (); } -impl - TransactionExtension::AccountId>> for SetFeeAgent +impl + TransactionExtension::AccountId>> + for SetFeeAgent where + Call: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, + InnerTx: TransactionExtension::AccountId>>, { - type Val = (); - type Pre = (); - impl_tx_ext_default!(Call; Context<::AccountId>; prepare); + type Val = Option; + type Pre = Option; fn validate( &self, origin: ::RuntimeOrigin, - _call: &Call, - _info: &DispatchInfoOf, - _len: usize, + call: &Call, + info: &DispatchInfoOf, + len: usize, context: &mut Context<::AccountId>, _: (), inherited_implication: &impl Encode, @@ -1071,17 +1079,53 @@ where (ValidTransaction, Self::Val, ::RuntimeOrigin), TransactionValidityError, > { - let (signature, account_id) = match &self.agent { - None => return Ok((ValidTransaction::default(), (), origin)), - Some((s, a)) => (s, a.clone()), // TODO check if origin None + let account_id = match &self.agent { + None => return Ok((ValidTransaction::default(), None, origin)), + Some(a) => a.clone(), // TODO check if origin None }; - let msg = inherited_implication.using_encoded(blake2_256); + let rest = match &self.rest { + Some(inner) => inner, + None => return Ok((ValidTransaction::default(), None, origin)), + }; - if !signature.verify(&msg[..], &account_id) { - Err(InvalidTransaction::BadProof)? - } *context = Context { fee_agent: Some(account_id) }; - Ok((ValidTransaction::default(), (), origin)) + let rest_implicit = rest.implicit()?; + let (validity, val, origin) = + rest.validate(origin, call, info, len, context, rest_implicit, inherited_implication)?; + Ok((validity, Some(val), origin)) + } + + fn prepare( + self, + val: Self::Val, + origin: &::RuntimeOrigin, + call: &Call, + info: &DispatchInfoOf, + len: usize, + context: &Context<::AccountId>, + ) -> Result { + match self.rest { + Some(inner) => { + let val = val.unwrap(); + let pre = inner.prepare(val, origin, call, info, len, context)?; + Ok(Some(pre)) + }, + None => Ok(None), + } + } + + fn post_dispatch( + pre: Self::Pre, + info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, + len: usize, + result: &DispatchResult, + context: &Context<::AccountId>, + ) -> Result<(), TransactionValidityError> { + match pre { + Some(pre) => InnerTx::post_dispatch(pre, info, post_info, len, result, context), + None => Ok(()), + } } } diff --git a/substrate/primitives/runtime/src/generic/checked_extrinsic.rs b/substrate/primitives/runtime/src/generic/checked_extrinsic.rs index 6b4471a499a7..cd33b0419b3b 100644 --- a/substrate/primitives/runtime/src/generic/checked_extrinsic.rs +++ b/substrate/primitives/runtime/src/generic/checked_extrinsic.rs @@ -69,18 +69,16 @@ pub struct CheckedExtrinsic { pub function: Call, /// Phantom type for `Context`. - #[codec(skip)] pub _phantom: PhantomData, } -impl traits::Applyable - for CheckedExtrinsic +impl traits::Applyable + for CheckedExtrinsic where AccountId: Member + MaybeDisplay, Call: Member + Dispatchable + Encode, - Extension: TransactionExtension, + Extension: TransactionExtension, RuntimeOrigin: From>, - Context: Sync + Send + Default, { type Call = Call; diff --git a/substrate/primitives/runtime/src/generic/mod.rs b/substrate/primitives/runtime/src/generic/mod.rs index 5713c166b04c..2e6e1c2eea4e 100644 --- a/substrate/primitives/runtime/src/generic/mod.rs +++ b/substrate/primitives/runtime/src/generic/mod.rs @@ -33,5 +33,5 @@ pub use self::{ digest::{Digest, DigestItem, DigestItemRef, OpaqueDigestItemId}, era::{Era, Phase}, header::Header, - unchecked_extrinsic::{Preamble, SignedPayload, UncheckedExtrinsic}, + unchecked_extrinsic::{Preamble, SignedPayload, UncheckedExtrinsic, UncheckedSignaturePayload}, }; diff --git a/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs b/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs index aeb08dd66a82..62961545e309 100644 --- a/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs +++ b/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs @@ -44,7 +44,7 @@ use sp_std::{fmt, prelude::*}; const EXTRINSIC_FORMAT_VERSION: u8 = 5; /// The `SignaturePayload` of `UncheckedExtrinsic`. -type UncheckedSignaturePayload = (Address, Signature, Extension); +pub type UncheckedSignaturePayload = (Address, Signature, Extension); impl SignaturePayload for UncheckedSignaturePayload @@ -128,7 +128,6 @@ pub struct UncheckedExtrinsic /// The function that should be called. pub function: Call, /// Phantom type for `Context`. - #[codec(skip)] pub _phantom: PhantomData, } @@ -302,14 +301,19 @@ where CheckedExtrinsic { format: ExtrinsicFormat::Signed(signed, extra), function: self.function, + _phantom: Default::default(), } }, Preamble::General(extra) => CheckedExtrinsic { format: ExtrinsicFormat::General(extra), function: self.function, + _phantom: Default::default(), + }, + Preamble::Bare => CheckedExtrinsic { + format: ExtrinsicFormat::Bare, + function: self.function, + _phantom: Default::default(), }, - Preamble::Bare => - CheckedExtrinsic { format: ExtrinsicFormat::Bare, function: self.function }, }) } } diff --git a/substrate/primitives/runtime/src/traits/transaction_extension/dispatch_transaction.rs b/substrate/primitives/runtime/src/traits/transaction_extension/dispatch_transaction.rs index 3b04c2982b90..db35c8cfc5f4 100644 --- a/substrate/primitives/runtime/src/traits/transaction_extension/dispatch_transaction.rs +++ b/substrate/primitives/runtime/src/traits/transaction_extension/dispatch_transaction.rs @@ -22,7 +22,7 @@ use super::*; /// Single-function utility trait with a blanket impl over [TransactionExtension] in order to /// provide transaction dispatching functionality. We avoid implementing this directly on the trait /// since we never want it to be overriden by the trait implementation. -pub trait DispatchTransaction { +pub trait DispatchTransaction { /// The origin type of the transaction. type Origin; /// The info type. @@ -43,7 +43,7 @@ pub trait DispatchTransaction { call: &Call, info: &Self::Info, len: usize, - ) -> Result<(ValidTransaction, Self::Val, Self::Origin, Context), TransactionValidityError>; + ) -> Result<(ValidTransaction, Self::Val, Self::Origin), TransactionValidityError>; /// Validate and prepare a transaction, ready for dispatch. fn validate_and_prepare( self, @@ -51,7 +51,7 @@ pub trait DispatchTransaction { call: &Call, info: &Self::Info, len: usize, - ) -> Result<(Self::Pre, Self::Origin, Context), TransactionValidityError>; + ) -> Result<(Self::Pre, Self::Origin), TransactionValidityError>; /// Dispatch a transaction with the given base origin and call. fn dispatch_transaction( self, @@ -75,8 +75,8 @@ pub trait DispatchTransaction { ) -> Self::Result; } -impl, Call: Dispatchable + Encode> - DispatchTransaction for T +impl, Call: Dispatchable + Encode> DispatchTransaction + for T { type Origin = ::RuntimeOrigin; type Info = DispatchInfoOf; @@ -90,11 +90,8 @@ impl, Call: Dispatchabl call: &Call, info: &DispatchInfoOf, len: usize, - ) -> Result<(ValidTransaction, T::Val, Self::Origin, Context), TransactionValidityError> { - let mut context = Context::default(); - let (info, val, origin) = - self.validate(origin, call, info, len, &mut context, self.implicit()?, call)?; - return Ok((info, val, origin, context)); + ) -> Result<(ValidTransaction, T::Val, Self::Origin), TransactionValidityError> { + self.validate(origin, call, info, len, &mut (), self.implicit()?, call) } fn validate_and_prepare( self, @@ -102,10 +99,10 @@ impl, Call: Dispatchabl call: &Call, info: &DispatchInfoOf, len: usize, - ) -> Result<(T::Pre, Self::Origin, Context), TransactionValidityError> { - let (_, val, origin, context) = self.validate_only(origin, call, info, len)?; - let pre = self.prepare(val, &origin, &call, info, len, &context)?; - Ok((pre, origin, context)) + ) -> Result<(T::Pre, Self::Origin), TransactionValidityError> { + let (_, val, origin) = self.validate_only(origin, call, info, len)?; + let pre = self.prepare(val, &origin, &call, info, len, &())?; + Ok((pre, origin)) } fn dispatch_transaction( self, @@ -114,11 +111,11 @@ impl, Call: Dispatchabl info: &DispatchInfoOf, len: usize, ) -> Self::Result { - let (pre, origin, context) = self.validate_and_prepare(origin, &call, info, len)?; + let (pre, origin) = self.validate_and_prepare(origin, &call, info, len)?; let res = call.dispatch(origin); let post_info = res.unwrap_or_else(|err| err.post_info); let pd_res = res.map(|_| ()).map_err(|e| e.error); - T::post_dispatch(pre, info, &post_info, len, &pd_res, &context)?; + T::post_dispatch(pre, info, &post_info, len, &pd_res, &())?; Ok(res) } fn test_run( @@ -131,14 +128,14 @@ impl, Call: Dispatchabl Self::Origin, ) -> crate::DispatchResultWithInfo<::PostInfo>, ) -> Self::Result { - let (pre, origin, context) = self.validate_and_prepare(origin, &call, info, len)?; + let (pre, origin) = self.validate_and_prepare(origin, &call, info, len)?; let res = substitute(origin); let post_info = match res { Ok(info) => info, Err(err) => err.post_info, }; let pd_res = res.map(|_| ()).map_err(|e| e.error); - T::post_dispatch(pre, info, &post_info, len, &pd_res, &context)?; + T::post_dispatch(pre, info, &post_info, len, &pd_res, &())?; Ok(res) } } From 7b2d206ce4f04142c93d83fbb2bb27e5255b6bc4 Mon Sep 17 00:00:00 2001 From: georgepisaltu Date: Mon, 8 Apr 2024 18:41:25 +0300 Subject: [PATCH 2/2] Simplify extensions using inherited implication Signed-off-by: georgepisaltu --- polkadot/runtime/westend/src/lib.rs | 440 ++++++++++++------ .../support/src/transaction_extensions.rs | 180 +------ .../frame/transaction-payment/src/lib.rs | 108 ++--- 3 files changed, 354 insertions(+), 374 deletions(-) diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index d7ebc9470633..1e09cd6fe7d3 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -90,10 +90,10 @@ use sp_runtime::{ generic::{self, ExtrinsicFormat, Preamble}, impl_opaque_keys, traits::{ - BlakeTwo256, Block as BlockT, Checkable, ConvertInto, DispatchInfoOf, Dispatchable, - Extrinsic as ExtrinsicT, ExtrinsicMetadata, IdentityLookup, Keccak256, Lookup, OpaqueKeys, - PostDispatchInfoOf, SaturatedConversion, TransactionExtension, TransactionExtensionBase, - ValidateUnsigned, Verify, + Applyable, BlakeTwo256, Block as BlockT, Checkable, ConvertInto, DispatchInfoOf, + Dispatchable, Extrinsic as ExtrinsicT, ExtrinsicMetadata, IdentityLookup, Keccak256, + Lookup, OpaqueKeys, PostDispatchInfoOf, SaturatedConversion, TransactionExtension, + TransactionExtensionBase, ValidateUnsigned, Verify, }, transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity}, ApplyExtrinsicResult, ApplyExtrinsicResultWithInfo, BoundToRuntimeAppPublic, FixedU128, @@ -826,24 +826,11 @@ where // so the actual block number is `n`. .saturating_sub(1); let tip = 0; - let inner_tx: ( - pallet_transaction_payment::SetFeeAgent< + let inner_tx: InnerUnsigned = ( + pallet_transaction_payment::SetFeeAgent::::default(), + frame_support::transaction_extensions::SignedOriginSignature::< sp_runtime::MultiSignature, - ( - frame_system::CheckNonce, - frame_support::transaction_extensions::CheckSignedPayload< - sp_runtime::MultiSignature, - ( - RuntimeCall, - BaseTxExtension, - ::Implicit, - ), - >, - ), - >, - BaseTxExtension, - ) = ( - pallet_transaction_payment::SetFeeAgent::default(), + >::default(), ( frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), @@ -870,12 +857,9 @@ where .using_encoded(|payload| C::sign(&sp_io::hashing::blake2_256(payload)[..], public))?; let (call, inner_tx, _) = raw_payload; let address = ::Lookup::unlookup(account.clone()); - let tx_ext = frame_support::transaction_extensions::SignedOriginSignature::new_with_sign( - signature.clone(), - account, - inner_tx, - ); - Some((call, (address, signature, (tx_ext,)))) + let tx_ext = + (frame_support::transaction_extensions::SignedOriginSignature::default(), inner_tx); + Some((call, (address, signature, tx_ext.into()))) } } @@ -1582,6 +1566,16 @@ pub type Block = generic::Block; pub type SignedBlock = generic::SignedBlock; /// `BlockId` type as expected by this runtime. pub type BlockId = generic::BlockId; +/// The extension to the basic transaction logic. +pub type TxExtension = ( + frame_support::transaction_extensions::SignedOriginSignature, + InnerUnsigned, +); +pub type InnerUnsigned = ( + pallet_transaction_payment::SetFeeAgent, + frame_support::transaction_extensions::SignedOriginSignature, + BaseTxExtension, +); pub type BaseTxExtension = ( frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, @@ -1592,48 +1586,8 @@ pub type BaseTxExtension = ( frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, ); -pub type InnerUnsigned = ( - pallet_transaction_payment::SetFeeAgent< - sp_runtime::MultiSignature, - ( - frame_system::CheckNonce, - frame_support::transaction_extensions::CheckSignedPayload< - sp_runtime::MultiSignature, - ( - RuntimeCall, - BaseTxExtension, - ::Implicit, - ), - >, - ), - >, - BaseTxExtension, -); -/// The extension to the basic transaction logic. -pub type TxExtension = ( - frame_support::transaction_extensions::SignedOriginSignature< - sp_runtime::MultiSignature, - ( - pallet_transaction_payment::SetFeeAgent< - sp_runtime::MultiSignature, - ( - frame_system::CheckNonce, - frame_support::transaction_extensions::CheckSignedPayload< - sp_runtime::MultiSignature, - ( - RuntimeCall, - BaseTxExtension, - ::Implicit, - ), - >, - ), - >, - BaseTxExtension, - ), - >, -); -impl sp_runtime::traits::Applyable for CheckedExtrinsic { +impl Applyable for CheckedExtrinsic { type Call = RuntimeCall; fn validate>( @@ -1651,16 +1605,15 @@ impl sp_runtime::traits::Applyable for CheckedExtrinsic { }, ExtrinsicFormat::Signed(ref signer, ref extension) => { let origin = Some(signer.clone()).into(); - extension - .0 - .extension + let (_signature_check, inner_extension) = extension; + inner_extension .validate( origin, &self.0.function, info, len, &mut Default::default(), - extension.0.extension.implicit()?, + inner_extension.implicit()?, &self.0.function, ) .map(|x| x.0) @@ -1707,7 +1660,7 @@ impl sp_runtime::traits::Applyable for CheckedExtrinsic { }, ExtrinsicFormat::Signed(signer, extension) => { // extension.dispatch_transaction(Some(signer).into(), self.function, info, len), - let inner_extension: InnerUnsigned = extension.0.extension; + let (_signature_check, inner_extension) = extension; let mut context = pallet_transaction_payment::Context::default(); let (_, val, origin) = inner_extension .validate( @@ -1760,6 +1713,18 @@ impl frame_support::dispatch::GetDispatchInfo for CheckedExtrinsic { } } +#[cfg(test)] +struct DummyValidateUnsigned; + +#[cfg(test)] +impl ValidateUnsigned for DummyValidateUnsigned { + type Call = RuntimeCall; + + fn validate_unsigned(_source: TransactionSource, _call: &Self::Call) -> TransactionValidity { + Ok(sp_runtime::transaction_validity::ValidTransaction::default()) + } +} + #[test] fn free_transaction_extension_test() { sp_io::TestExternalities::default().execute_with(|| { @@ -1771,31 +1736,13 @@ fn free_transaction_extension_test() { use sp_io::hashing::blake2_256; use sp_runtime::{traits::TransactionExtensionBase, MultiSignature}; - type CheckSignedInnerTxExtension = - frame_support::transaction_extensions::CheckSignedPayload< - sp_runtime::MultiSignature, - ( - RuntimeCall, - BaseTxExtension, - ::Implicit, - ), - >; - - type InnerUnsignedTxExtension = ( - pallet_transaction_payment::SetFeeAgent< - sp_runtime::MultiSignature, - (frame_system::CheckNonce, CheckSignedInnerTxExtension), - >, + // The part of `TxExtension` that has to be provided and signed by the fee agent, + // the user who sponsors the transaction fee. + type SignedTxExtension = ( + frame_support::transaction_extensions::SignedOriginSignature, BaseTxExtension, ); - type OuterSignedTxExtension = ( - frame_support::transaction_extensions::SignedOriginSignature< - sp_runtime::MultiSignature, - InnerUnsignedTxExtension, - >, - ); - frame_system::GenesisConfig::::default().build(); System::set_block_number(1); @@ -1822,7 +1769,6 @@ fn free_transaction_extension_test() { Balances::force_set_balance(RuntimeOrigin::root(), bob_account.clone().into(), bob_balance) .unwrap(); - Balances::force_set_balance( RuntimeOrigin::root(), charlie_account.clone().into(), @@ -1839,7 +1785,7 @@ fn free_transaction_extension_test() { frame_system::Pallet::::account(&charlie_account).nonce; // Alice builds the transaction extension for the sponsored transaction. - let alice_base_ext: BaseTxExtension = ( + let stmt_ext: BaseTxExtension = ( frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), @@ -1858,48 +1804,50 @@ fn free_transaction_extension_test() { ) .into(); - let alice_base_ext_implicit = alice_base_ext.implicit().unwrap(); - // Alice signs the transaction extension and shares it. - let alice_base_sign = MultiSignature::Sr25519( - (call.clone(), alice_base_ext.clone(), alice_base_ext_implicit.clone()) + let stmt_sign = MultiSignature::Sr25519( + (call.clone(), stmt_ext.clone(), stmt_ext.implicit().unwrap()) .using_encoded(|e| alice_keyring.sign(&blake2_256(e))), ); + let statement = (call.clone(), stmt_ext, stmt_sign).encode(); + + type Statement = (RuntimeCall, BaseTxExtension, MultiSignature); + let (stmt_call, stmt_ext, stmt_sign) = Statement::decode(&mut &statement[..]).unwrap(); + // Bob constructs the transaction based on Alice statemnt. - let alice_signed_tx_ext = CheckSignedInnerTxExtension::new_with_sign( - alice_base_sign, - alice_account.clone(), - (call.clone(), alice_base_ext.clone(), alice_base_ext_implicit.clone()), - ); + let signed_tx_ext = frame_support::transaction_extensions::SignedOriginSignature::< + MultiSignature, + >::new_with_sign(stmt_sign, alice_account.clone()); - let bob_unsigned_tx_ext = ( - pallet_transaction_payment::SetFeeAgent::new_with_agent( - bob_account.clone(), - ( - frame_system::CheckNonce::::from( - frame_system::Pallet::::account(&bob_account).nonce, - ), - alice_signed_tx_ext, - ), - ), - alice_base_ext, - ); + let mut signed_tx_ext = signed_tx_ext.encode(); + signed_tx_ext.append(&mut stmt_ext.encode()); + let signed_tx_ext: SignedTxExtension = + SignedTxExtension::decode(&mut &signed_tx_ext[..]).unwrap(); - let bob_unsigned_tx_ext_implicit = bob_unsigned_tx_ext.implicit().unwrap(); - let bob_sign = MultiSignature::Sr25519( - (call.clone(), bob_unsigned_tx_ext.clone(), bob_unsigned_tx_ext_implicit) + // Bob signs the transaction with Alice's part to poof he is willing to sponser the fee. + let signed_tx_sign = MultiSignature::Sr25519( + (stmt_call, signed_tx_ext.clone(), signed_tx_ext.implicit().unwrap()) .using_encoded(|e| bob_keyring.sign(&blake2_256(e))), ); - let bob_signed_tx_ext: OuterSignedTxExtension = - (frame_support::transaction_extensions::SignedOriginSignature::new_with_sign( - bob_sign, - bob_account.clone(), - bob_unsigned_tx_ext, - ),); + let tx_ext = pallet_transaction_payment::SetFeeAgent::::new_with_agent( + signed_tx_sign, + bob_account.clone(), + ); + + let empty_signature_ext = frame_support::transaction_extensions::SignedOriginSignature::< + MultiSignature, + >::default(); + + let mut tx_ext_encoded = empty_signature_ext.encode(); + tx_ext_encoded.append(&mut tx_ext.encode()); + tx_ext_encoded.append(&mut signed_tx_ext.encode()); + + // The final valid for submission transaction extension. + let bob_signed_tx_ext: TxExtension = TxExtension::decode(&mut &tx_ext_encoded[..]).unwrap(); // Dispatch the transaction { @@ -1921,8 +1869,7 @@ fn free_transaction_extension_test() { let res = call.dispatch(origin); let post_info = res.unwrap_or_else(|err| err.post_info); let pd_res = res.map(|_| ()).map_err(|e| e.error); - OuterSignedTxExtension::post_dispatch(pre, &info, &post_info, len, &pd_res, &context) - .unwrap(); + TxExtension::post_dispatch(pre, &info, &post_info, len, &pd_res, &context).unwrap(); } // Alice balance is unchanged, Bob paid the transaction fee. @@ -1942,16 +1889,15 @@ fn free_transaction_extension_test() { alice_initial_nonce.saturating_add(1), frame_system::Pallet::::account(&alice_account).nonce ); - assert_eq!( - bob_initial_nonce.saturating_add(1), - frame_system::Pallet::::account(&bob_account).nonce - ); + assert_eq!(bob_initial_nonce, frame_system::Pallet::::account(&bob_account).nonce); // The call that Charlie wants to be executed. let call = RuntimeCall::System(frame_system::Call::remark_with_event { remark: vec![3] }); let charlie_unsigned_tx_ext = ( pallet_transaction_payment::SetFeeAgent::default(), + frame_support::transaction_extensions::SignedOriginSignature::::default( + ), ( frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), @@ -1972,12 +1918,13 @@ fn free_transaction_extension_test() { .using_encoded(|e| charlie_keyring.sign(&blake2_256(e))), ); - let charlie_signed_tx_ext: OuterSignedTxExtension = - (frame_support::transaction_extensions::SignedOriginSignature::new_with_sign( + let charlie_signed_tx_ext: TxExtension = ( + frame_support::transaction_extensions::SignedOriginSignature::new_with_sign( charlie_sign, charlie_account.clone(), - charlie_unsigned_tx_ext, - ),); + ), + charlie_unsigned_tx_ext, + ); // Dispatch the transaction { @@ -2001,8 +1948,7 @@ fn free_transaction_extension_test() { let res = call.dispatch(origin); let post_info = res.unwrap_or_else(|err| err.post_info); let pd_res = res.map(|_| ()).map_err(|e| e.error); - OuterSignedTxExtension::post_dispatch(pre, &info, &post_info, len, &pd_res, &context) - .unwrap(); + TxExtension::post_dispatch(pre, &info, &post_info, len, &pd_res, &context).unwrap(); } assert!(charlie_balance > Balances::free_balance(charlie_account.clone())); @@ -2023,6 +1969,222 @@ fn free_transaction_extension_test() { }); } +#[test] +fn applied_free_transaction_extension_test() { + sp_io::TestExternalities::default().execute_with(|| { + use frame_support::{dispatch::DispatchInfo, traits::BuildGenesisConfig}; + use keyring::AccountKeyring; + use sp_io::hashing::blake2_256; + use sp_runtime::{traits::TransactionExtensionBase, MultiSignature}; + + // The part of `TxExtension` that has to be provided and signed by the fee agent, + // the user who sponsors the transaction fee. + type SignedTxExtension = ( + frame_support::transaction_extensions::SignedOriginSignature, + BaseTxExtension, + ); + + frame_system::GenesisConfig::::default().build(); + System::set_block_number(1); + + // Alice wants the transaction fee to be sponsored by Bob. + let alice_keyring = AccountKeyring::Alice; + let bob_keyring = AccountKeyring::Bob; + let charlie_keyring = AccountKeyring::Charlie; + + let alice_account = AccountId::from(alice_keyring.public()); + let bob_account = AccountId::from(bob_keyring.public()); + let charlie_account = AccountId::from(charlie_keyring.public()); + + // Setup the initial balances. + let alice_balance = 10 * ExistentialDeposit::get(); + let bob_balance = 10 * ExistentialDeposit::get(); + let charlie_balance = 10 * ExistentialDeposit::get(); + + Balances::force_set_balance( + RuntimeOrigin::root(), + alice_account.clone().into(), + alice_balance, + ) + .unwrap(); + + Balances::force_set_balance(RuntimeOrigin::root(), bob_account.clone().into(), bob_balance) + .unwrap(); + Balances::force_set_balance( + RuntimeOrigin::root(), + charlie_account.clone().into(), + charlie_balance, + ) + .unwrap(); + + // The call that Alice wants to be executed. + let call = RuntimeCall::System(frame_system::Call::remark_with_event { remark: vec![1] }); + + let alice_initial_nonce = frame_system::Pallet::::account(&alice_account).nonce; + let bob_initial_nonce = frame_system::Pallet::::account(&bob_account).nonce; + let charlie_initial_nonce = + frame_system::Pallet::::account(&charlie_account).nonce; + + // Alice builds the transaction extension for the sponsored transaction. + let stmt_ext: BaseTxExtension = ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckMortality::::from(sp_runtime::generic::Era::immortal()), + frame_system::CheckNonce::::from( + frame_system::Pallet::::account(&alice_account).nonce, + ), + frame_system::CheckWeight::::new(), + // In case if any agent can sponsor the transaction fee. + // pallet_transaction_payment::ChargeTransactionPayment::::with_any_agent(), + // Only Bob can sponsor the transaction fee. + pallet_transaction_payment::ChargeTransactionPayment::::with_agent( + bob_account.clone(), + ), + ) + .into(); + + // Alice signs the transaction extension and shares it. + + let stmt_sign = MultiSignature::Sr25519( + (call.clone(), stmt_ext.clone(), stmt_ext.implicit().unwrap()) + .using_encoded(|e| alice_keyring.sign(&blake2_256(e))), + ); + + let statement = (call.clone(), stmt_ext, stmt_sign).encode(); + + type Statement = (RuntimeCall, BaseTxExtension, MultiSignature); + let (stmt_call, stmt_ext, stmt_sign) = Statement::decode(&mut &statement[..]).unwrap(); + + // Bob constructs the transaction based on Alice statemnt. + + let signed_tx_ext = frame_support::transaction_extensions::SignedOriginSignature::< + MultiSignature, + >::new_with_sign(stmt_sign, alice_account.clone()); + + let mut signed_tx_ext = signed_tx_ext.encode(); + signed_tx_ext.append(&mut stmt_ext.encode()); + let signed_tx_ext: SignedTxExtension = + SignedTxExtension::decode(&mut &signed_tx_ext[..]).unwrap(); + + // Bob signs the transaction with Alice's part to poof he is willing to sponser the fee. + let signed_tx_sign = MultiSignature::Sr25519( + (stmt_call, signed_tx_ext.clone(), signed_tx_ext.implicit().unwrap()) + .using_encoded(|e| bob_keyring.sign(&blake2_256(e))), + ); + + let tx_ext = pallet_transaction_payment::SetFeeAgent::::new_with_agent( + signed_tx_sign, + bob_account.clone(), + ); + + let empty_signature_ext = frame_support::transaction_extensions::SignedOriginSignature::< + MultiSignature, + >::default(); + + let mut tx_ext_encoded = empty_signature_ext.encode(); + tx_ext_encoded.append(&mut tx_ext.encode()); + tx_ext_encoded.append(&mut signed_tx_ext.encode()); + + // The final valid for submission transaction extension. + let bob_signed_tx_ext: TxExtension = TxExtension::decode(&mut &tx_ext_encoded[..]).unwrap(); + + let info = DispatchInfo::default(); + let len = call.encoded_size(); + + let xt = UncheckedExtrinsic(generic::UncheckedExtrinsic::< + Address, + RuntimeCall, + Signature, + TxExtension, + pallet_transaction_payment::Context, + >::new_transaction(call.clone(), bob_signed_tx_ext.clone())); + let checked_xt = xt.check(&Default::default()).unwrap(); + assert!(checked_xt.apply::(&info, len).is_ok()); + + // Alice balance is unchanged, Bob paid the transaction fee. + assert_eq!(alice_balance, Balances::free_balance(alice_account.clone())); + assert!(bob_balance > Balances::free_balance(bob_account.clone())); + + assert!(System::events().iter().any(|ev| ev.event == + RuntimeEvent::System(frame_system::Event::Remarked { + sender: alice_account.clone(), + hash: + <::Hashing as sp_runtime::traits::Hash>::hash( + &[1u8] + ) + }))); + + assert_eq!( + alice_initial_nonce.saturating_add(1), + frame_system::Pallet::::account(&alice_account).nonce + ); + assert_eq!(bob_initial_nonce, frame_system::Pallet::::account(&bob_account).nonce); + + // The call that Charlie wants to be executed. + let call = RuntimeCall::System(frame_system::Call::remark_with_event { remark: vec![3] }); + + let charlie_unsigned_tx_ext = ( + pallet_transaction_payment::SetFeeAgent::default(), + frame_support::transaction_extensions::SignedOriginSignature::::default( + ), + ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckMortality::::from(sp_runtime::generic::Era::immortal()), + frame_system::CheckNonce::::from( + frame_system::Pallet::::account(&charlie_account).nonce, + ), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(0), + ), + ); + + let charlie_unsigned_tx_ext_implicit = charlie_unsigned_tx_ext.implicit().unwrap(); + let charlie_sign = MultiSignature::Sr25519( + (call.clone(), charlie_unsigned_tx_ext.clone(), charlie_unsigned_tx_ext_implicit) + .using_encoded(|e| charlie_keyring.sign(&blake2_256(e))), + ); + + let charlie_signed_tx_ext: TxExtension = ( + frame_support::transaction_extensions::SignedOriginSignature::new_with_sign( + charlie_sign, + charlie_account.clone(), + ), + charlie_unsigned_tx_ext, + ); + + let xt = UncheckedExtrinsic(generic::UncheckedExtrinsic::< + Address, + RuntimeCall, + Signature, + TxExtension, + pallet_transaction_payment::Context, + >::new_transaction(call.clone(), charlie_signed_tx_ext.clone())); + let checked_xt = xt.check(&Default::default()).unwrap(); + assert!(checked_xt.apply::(&info, len).is_ok()); + + assert!(charlie_balance > Balances::free_balance(charlie_account.clone())); + + assert!(System::events().iter().any(|ev| ev.event == + RuntimeEvent::System(frame_system::Event::Remarked { + sender: charlie_account.clone(), + hash: + <::Hashing as sp_runtime::traits::Hash>::hash( + &[3u8] + ) + }))); + + assert_eq!( + charlie_initial_nonce.saturating_add(1), + frame_system::Pallet::::account(&charlie_account).nonce + ); + }); +} + pub struct NominationPoolsMigrationV4OldPallet; impl Get for NominationPoolsMigrationV4OldPallet { fn get() -> Perbill { diff --git a/substrate/frame/support/src/transaction_extensions.rs b/substrate/frame/support/src/transaction_extensions.rs index 050431be6840..2bd8f94a0b5b 100644 --- a/substrate/frame/support/src/transaction_extensions.rs +++ b/substrate/frame/support/src/transaction_extensions.rs @@ -94,205 +94,67 @@ where /// Transaction extension that sets the origin to the given account ID if the provided signature by /// that account is valid for all subsequent extensions. If signature is not provided, this -/// extension is no-op. Will run wrapped extension logic after the origin validation. +/// extension is no-op. // TODO better doc. #[derive( CloneNoBound, EqNoBound, PartialEqNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, )] #[codec(encode_bound())] #[codec(decode_bound())] -pub struct SignedOriginSignature +pub struct SignedOriginSignature where V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, - InnerTx: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, { - pub signature: Option<(V, ::AccountId)>, - pub extension: InnerTx, + signature: Option<(V, ::AccountId)>, } -impl SignedOriginSignature +impl Default for SignedOriginSignature where V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, - InnerTx: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, { - pub fn new_with_sign( - signature: V, - account_id: ::AccountId, - extension: InnerTx, - ) -> Self { - Self { signature: Some((signature, account_id)), extension } - } -} - -impl TransactionExtensionBase for SignedOriginSignature -where - V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, - ::AccountId: - Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, - InnerTx: Codec - + Debug - + Sync - + Send - + Clone - + Eq - + PartialEq - + StaticTypeInfo - + TransactionExtensionBase, -{ - const IDENTIFIER: &'static str = "SignedOriginSignature"; - type Implicit = (); -} - -impl - TransactionExtension for SignedOriginSignature -where - V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, - ::AccountId: - Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, - ::RuntimeOrigin: From::AccountId>>, - InnerTx: Codec - + Debug - + Sync - + Send - + Clone - + Eq - + PartialEq - + StaticTypeInfo - + TransactionExtension, -{ - type Val = InnerTx::Val; - type Pre = InnerTx::Pre; - - fn validate( - &self, - origin: ::RuntimeOrigin, - call: &Call, - info: &DispatchInfoOf, - len: usize, - context: &mut Context, - _self_implicit: (), - inherited_implication: &impl Encode, - ) -> Result< - (ValidTransaction, Self::Val, ::RuntimeOrigin), - TransactionValidityError, - > { - let (signature, account_id) = match &self.signature { - Some((s, a)) => (s, a.clone()), // TODO check if origin None - None => { - let implicit = self.extension.implicit()?; - return self.extension.validate( - origin, - call, - info, - len, - context, - implicit, - inherited_implication, - ) - }, - }; - - let implicit = self.extension.implicit()?; - let signed_payload = (call, &self.extension, &implicit); - if !signed_payload - .using_encoded(|payload| signature.verify(&blake2_256(payload)[..], &account_id)) - { - return Err(InvalidTransaction::BadProof.into()) - } - - let origin = Some(account_id).into(); - self.extension - .validate(origin, call, info, len, context, implicit, inherited_implication) - } - - fn prepare( - self, - val: Self::Val, - origin: &sp_runtime::traits::OriginOf, - call: &Call, - info: &DispatchInfoOf, - len: usize, - context: &Context, - ) -> Result { - self.extension.prepare(val, origin, call, info, len, context) - } - - fn post_dispatch( - pre: Self::Pre, - info: &DispatchInfoOf, - post_info: &sp_runtime::traits::PostDispatchInfoOf, - len: usize, - result: &sp_runtime::DispatchResult, - context: &Context, - ) -> Result<(), TransactionValidityError> { - InnerTx::post_dispatch(pre, info, post_info, len, result, context) + fn default() -> Self { + Self { signature: None } } } -/// Transaction extension that sets the origin to the given account ID if the provided signature by -/// that account is valid for the provided payload. -#[derive( - CloneNoBound, EqNoBound, PartialEqNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, -)] -#[codec(encode_bound())] -#[codec(decode_bound())] -pub struct CheckSignedPayload +impl SignedOriginSignature where V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, - P: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, -{ - pub signature: Option<(V, ::AccountId, P)>, -} - -impl CheckSignedPayload -where - V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, - ::AccountId: - Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, - P: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, { pub fn new_with_sign( signature: V, account_id: ::AccountId, - payload: P, ) -> Self { - Self { signature: Some((signature, account_id, payload)) } - } - - pub fn new() -> Self { - Self { signature: None } + Self { signature: Some((signature, account_id)) } } } -impl TransactionExtensionBase for CheckSignedPayload +impl TransactionExtensionBase for SignedOriginSignature where V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, - P: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, { - const IDENTIFIER: &'static str = "CheckSignedPayload"; + const IDENTIFIER: &'static str = "SignedOriginSignature"; type Implicit = (); } -impl - TransactionExtension for CheckSignedPayload +impl TransactionExtension + for SignedOriginSignature where V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, - P: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::RuntimeOrigin: From::AccountId>>, { type Val = (); type Pre = (); - impl_tx_ext_default!(Call; Context; prepare); fn validate( @@ -301,23 +163,23 @@ where _call: &Call, _info: &DispatchInfoOf, _len: usize, - _context: &mut Context, - _self_implicit: (), - _inherited_implication: &impl Encode, + _: &mut Context, + _: (), + inherited_implication: &impl Encode, ) -> Result< (ValidTransaction, Self::Val, ::RuntimeOrigin), TransactionValidityError, > { - let (signature, account_id, payload) = match &self.signature { - Some((s, a, p)) => (s, a.clone(), p), // TODO check if origin None + let (signature, account_id) = match &self.signature { + Some((s, a)) => (s, a.clone()), // TODO check if origin None None => return Ok((ValidTransaction::default(), (), origin)), }; - if !payload.using_encoded(|payload| signature.verify(&blake2_256(payload)[..], &account_id)) - { - return Err(InvalidTransaction::BadProof.into()) - } + let msg = inherited_implication.using_encoded(blake2_256); + if !signature.verify(&msg[..], &account_id) { + Err(InvalidTransaction::BadProof)? + } let origin = Some(account_id).into(); Ok((ValidTransaction::default(), (), origin)) } diff --git a/substrate/frame/transaction-payment/src/lib.rs b/substrate/frame/transaction-payment/src/lib.rs index 625a7a188e78..70de8600260c 100644 --- a/substrate/frame/transaction-payment/src/lib.rs +++ b/substrate/frame/transaction-payment/src/lib.rs @@ -964,7 +964,11 @@ use codec::Codec; use core::fmt::Debug; use frame_support::{CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound}; use scale_info::StaticTypeInfo; -use sp_runtime::traits::{IdentifyAccount, Verify}; +use sp_io::hashing::blake2_256; +use sp_runtime::{ + impl_tx_ext_default, + traits::{IdentifyAccount, Verify}, +}; #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub enum FeeAgent { @@ -999,79 +1003,67 @@ impl Default for Context { )] #[codec(encode_bound())] #[codec(decode_bound())] -pub struct SetFeeAgent +pub struct SetFeeAgent where V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, - Tx: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, { - pub agent: Option<::AccountId>, - pub rest: Option, + pub agent: Option<(V, ::AccountId)>, } -impl Default for SetFeeAgent +impl Default for SetFeeAgent where V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, - Tx: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, { fn default() -> Self { - Self { agent: None, rest: None } + Self { agent: None } } } -impl SetFeeAgent +impl SetFeeAgent where V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, - Tx: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, { - pub fn new_with_agent(account_id: ::AccountId, tx: Tx) -> Self { - Self { agent: Some(account_id), rest: Some(tx) } + pub fn new_with_agent( + signature: V, + account_id: ::AccountId, + ) -> Self { + Self { agent: Some((signature, account_id)) } } } -impl TransactionExtensionBase for SetFeeAgent +impl TransactionExtensionBase for SetFeeAgent where V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, - Tx: TransactionExtensionBase - + Codec - + Debug - + Sync - + Send - + Clone - + Eq - + PartialEq - + StaticTypeInfo, { const IDENTIFIER: &'static str = "SetFeeAgent"; type Implicit = (); } -impl - TransactionExtension::AccountId>> - for SetFeeAgent +impl + TransactionExtension::AccountId>> for SetFeeAgent where - Call: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, ::AccountId: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo, - InnerTx: TransactionExtension::AccountId>>, { - type Val = Option; - type Pre = Option; + type Val = (); + type Pre = (); + impl_tx_ext_default!(Call; Context<::AccountId>; prepare); fn validate( &self, origin: ::RuntimeOrigin, - call: &Call, - info: &DispatchInfoOf, - len: usize, + _call: &Call, + _info: &DispatchInfoOf, + _len: usize, context: &mut Context<::AccountId>, _: (), inherited_implication: &impl Encode, @@ -1079,53 +1071,17 @@ where (ValidTransaction, Self::Val, ::RuntimeOrigin), TransactionValidityError, > { - let account_id = match &self.agent { - None => return Ok((ValidTransaction::default(), None, origin)), - Some(a) => a.clone(), // TODO check if origin None + let (signature, account_id) = match &self.agent { + None => return Ok((ValidTransaction::default(), (), origin)), + Some((s, a)) => (s, a.clone()), // TODO check if origin None }; - let rest = match &self.rest { - Some(inner) => inner, - None => return Ok((ValidTransaction::default(), None, origin)), - }; + let msg = inherited_implication.using_encoded(blake2_256); - *context = Context { fee_agent: Some(account_id) }; - let rest_implicit = rest.implicit()?; - let (validity, val, origin) = - rest.validate(origin, call, info, len, context, rest_implicit, inherited_implication)?; - Ok((validity, Some(val), origin)) - } - - fn prepare( - self, - val: Self::Val, - origin: &::RuntimeOrigin, - call: &Call, - info: &DispatchInfoOf, - len: usize, - context: &Context<::AccountId>, - ) -> Result { - match self.rest { - Some(inner) => { - let val = val.unwrap(); - let pre = inner.prepare(val, origin, call, info, len, context)?; - Ok(Some(pre)) - }, - None => Ok(None), - } - } - - fn post_dispatch( - pre: Self::Pre, - info: &DispatchInfoOf, - post_info: &PostDispatchInfoOf, - len: usize, - result: &DispatchResult, - context: &Context<::AccountId>, - ) -> Result<(), TransactionValidityError> { - match pre { - Some(pre) => InnerTx::post_dispatch(pre, info, post_info, len, result, context), - None => Ok(()), + if !signature.verify(&msg[..], &account_id) { + Err(InvalidTransaction::BadProof)? } + *context = Context { fee_agent: Some(account_id) }; + Ok((ValidTransaction::default(), (), origin)) } }