diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 9e95bbd224fd..1e09cd6fe7d3 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, + 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, 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,40 @@ where // so the actual block number is `n`. .saturating_sub(1); let tip = 0; - let tx_ext: TxExtension = ( + let inner_tx: InnerUnsigned = ( pallet_transaction_payment::SetFeeAgent::::default(), frame_support::transaction_extensions::SignedOriginSignature::< 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::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::default(), inner_tx); + Some((call, (address, signature, tx_ext.into()))) } } @@ -1557,8 +1568,15 @@ pub type SignedBlock = generic::SignedBlock; 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, frame_system::CheckTxVersion, @@ -1569,6 +1587,144 @@ pub type TxExtension = ( pallet_transaction_payment::ChargeTransactionPayment, ); +impl 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(); + let (_signature_check, inner_extension) = extension; + inner_extension + .validate( + origin, + &self.0.function, + info, + len, + &mut Default::default(), + inner_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 (_signature_check, inner_extension) = 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() + } +} + +#[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(|| { @@ -1578,24 +1734,249 @@ fn free_transaction_extension_test() { }; use keyring::AccountKeyring; use sp_io::hashing::blake2_256; - use sp_runtime::{ - traits::{DispatchTransaction, TransactionExtensionBase}, + 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(); + + // 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); + TxExtension::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, 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), + ), + ); - // 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, + 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, + ); + + // 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); + TxExtension::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] + ) + }))); + + assert_eq!( + charlie_initial_nonce.saturating_add(1), + frame_system::Pallet::::account(&charlie_account).nonce + ); + }); +} + +#[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 = ( @@ -1609,13 +1990,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(), @@ -1626,10 +2010,21 @@ 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 = ( frame_system::CheckNonZeroSender::::new(), @@ -1684,26 +2079,109 @@ fn free_transaction_extension_test() { bob_account.clone(), ); - let mut tx_ext_encoded = tx_ext.encode(); + 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 tx_ext: TxExtension = TxExtension::decode(&mut &tx_ext_encoded[..]).unwrap(); + let bob_signed_tx_ext: TxExtension = TxExtension::decode(&mut &tx_ext_encoded[..]).unwrap(); - let _ = SignedPayload::new(call.clone(), tx_ext.clone()).unwrap(); + let info = DispatchInfo::default(); + let len = call.encoded_size(); - let _ = TxExtension::dispatch_transaction( - tx_ext, - RawOrigin::None.into(), - call, - &DispatchInfo::default(), - 0, - ) - .unwrap(); + 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)); - assert!(bob_balance > Balances::free_balance(bob_account)); + 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 + ); }); } @@ -1859,13 +2337,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 +2510,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/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/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) } }