diff --git a/prdoc/pr_6571.prdoc b/prdoc/pr_6571.prdoc new file mode 100644 index 000000000000..477aaa0d97d2 --- /dev/null +++ b/prdoc/pr_6571.prdoc @@ -0,0 +1,11 @@ +title: 'Introduce `TransactionExtensionPipeline` to use instead of tuple for pipeline with more than 12 elements' +doc: +- audience: Runtime Dev + description: |- + Introduce a new type `TransactionExtensionPipeline` that has 32 generics that default to `()`. + This type accepts up to 32 transaction extensions. It can be used in place of tuple which is + limited to 12 transaction extensions. + +crates: +- name: sp-runtime + bump: minor diff --git a/substrate/frame/support/src/dispatch.rs b/substrate/frame/support/src/dispatch.rs index 483a3dce77f6..362965e5ed45 100644 --- a/substrate/frame/support/src/dispatch.rs +++ b/substrate/frame/support/src/dispatch.rs @@ -1580,3 +1580,275 @@ mod extension_weight_tests { }); } } + +// Same tests as extension_weight_tests but using a pipeline instead of a tuple. +#[cfg(test)] +// Do not complain about unused `dispatch` and `dispatch_aux`. +#[allow(dead_code)] +mod extension_weight_tests_with_pipeline { + use crate::assert_ok; + + use super::*; + use sp_core::parameter_types; + use sp_runtime::{ + generic::{self, ExtrinsicFormat}, + traits::{ + Applyable, BlakeTwo256, DispatchTransaction, TransactionExtension, + TransactionExtensionPipeline, + }, + }; + use sp_weights::RuntimeDbWeight; + use test_extensions::{ActualWeightIs, FreeIfUnder, HalfCostIf}; + + use super::weight_tests::frame_system; + use frame_support::construct_runtime; + + pub type TxExtension = TransactionExtensionPipeline; + pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + pub type Header = generic::Header; + pub type Block = generic::Block; + pub type AccountId = u64; + pub type Balance = u32; + pub type BlockNumber = u32; + + construct_runtime!( + pub enum ExtRuntime { + System: frame_system, + } + ); + + impl frame_system::Config for ExtRuntime { + type Block = Block; + type AccountId = AccountId; + type Balance = Balance; + type BaseCallFilter = crate::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeTask = RuntimeTask; + type DbWeight = DbWeight; + type PalletInfo = PalletInfo; + } + + parameter_types! { + pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 100, + write: 1000, + }; + } + + pub struct ExtBuilder {} + + impl Default for ExtBuilder { + fn default() -> Self { + Self {} + } + } + + impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let mut ext = sp_io::TestExternalities::new(Default::default()); + ext.execute_with(|| {}); + ext + } + + pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + self.build().execute_with(|| { + test(); + }) + } + } + + #[test] + fn no_post_dispatch_with_no_refund() { + ExtBuilder::default().build_and_execute(|| { + let call = RuntimeCall::System(frame_system::Call::::f99 {}); + let ext: TxExtension = (HalfCostIf(false), FreeIfUnder(1500), ActualWeightIs(0)).into(); + let uxt = UncheckedExtrinsic::new_signed(call.clone(), 0, (), ext.clone()); + assert_eq!(uxt.extension_weight(), Weight::from_parts(600, 0)); + + let mut info = call.get_dispatch_info(); + assert_eq!(info.total_weight(), Weight::from_parts(1000, 0)); + info.extension_weight = ext.weight(&call); + let (pre, _) = ext.validate_and_prepare(Some(0).into(), &call, &info, 0, 0).unwrap(); + let res = call.dispatch(Some(0).into()); + let mut post_info = res.unwrap(); + assert!(post_info.actual_weight.is_none()); + assert_ok!(>::post_dispatch( + pre, + &info, + &mut post_info, + 0, + &Ok(()), + )); + assert!(post_info.actual_weight.is_none()); + }); + } + + #[test] + fn no_post_dispatch_refunds_when_dispatched() { + ExtBuilder::default().build_and_execute(|| { + let call = RuntimeCall::System(frame_system::Call::::f99 {}); + let ext: TxExtension = (HalfCostIf(true), FreeIfUnder(100), ActualWeightIs(0)).into(); + let uxt = UncheckedExtrinsic::new_signed(call.clone(), 0, (), ext.clone()); + assert_eq!(uxt.extension_weight(), Weight::from_parts(600, 0)); + + let mut info = call.get_dispatch_info(); + assert_eq!(info.total_weight(), Weight::from_parts(1000, 0)); + info.extension_weight = ext.weight(&call); + let post_info = + ext.dispatch_transaction(Some(0).into(), call, &info, 0, 0).unwrap().unwrap(); + // 1000 call weight + 50 + 200 + 0 + assert_eq!(post_info.actual_weight, Some(Weight::from_parts(1250, 0))); + }); + } + + #[test] + fn post_dispatch_with_refunds() { + ExtBuilder::default().build_and_execute(|| { + let call = RuntimeCall::System(frame_system::Call::::f100 {}); + // First testcase + let ext: TxExtension = (HalfCostIf(false), FreeIfUnder(2000), ActualWeightIs(0)).into(); + let uxt = UncheckedExtrinsic::new_signed(call.clone(), 0, (), ext.clone()); + assert_eq!(uxt.extension_weight(), Weight::from_parts(600, 0)); + + let mut info = call.get_dispatch_info(); + assert_eq!(info.call_weight, Weight::from_parts(1000, 0)); + info.extension_weight = ext.weight(&call); + assert_eq!(info.total_weight(), Weight::from_parts(1600, 0)); + let (pre, _) = ext.validate_and_prepare(Some(0).into(), &call, &info, 0, 0).unwrap(); + let res = call.clone().dispatch(Some(0).into()); + let mut post_info = res.unwrap(); + // 500 actual call weight + assert_eq!(post_info.actual_weight, Some(Weight::from_parts(500, 0))); + // add the 600 worst case extension weight + post_info.set_extension_weight(&info); + // extension weight should be refunded + assert_ok!(>::post_dispatch( + pre, + &info, + &mut post_info, + 0, + &Ok(()), + )); + // 500 actual call weight + 100 + 0 + 0 + assert_eq!(post_info.actual_weight, Some(Weight::from_parts(600, 0))); + + // Second testcase + let ext: TxExtension = + (HalfCostIf(false), FreeIfUnder(1100), ActualWeightIs(200)).into(); + let (pre, _) = ext.validate_and_prepare(Some(0).into(), &call, &info, 0, 0).unwrap(); + let res = call.clone().dispatch(Some(0).into()); + let mut post_info = res.unwrap(); + // 500 actual call weight + assert_eq!(post_info.actual_weight, Some(Weight::from_parts(500, 0))); + // add the 600 worst case extension weight + post_info.set_extension_weight(&info); + // extension weight should be refunded + assert_ok!(>::post_dispatch( + pre, + &info, + &mut post_info, + 0, + &Ok(()), + )); + // 500 actual call weight + 100 + 200 + 200 + assert_eq!(post_info.actual_weight, Some(Weight::from_parts(1000, 0))); + + // Third testcase + let ext: TxExtension = + (HalfCostIf(true), FreeIfUnder(1060), ActualWeightIs(200)).into(); + let (pre, _) = ext.validate_and_prepare(Some(0).into(), &call, &info, 0, 0).unwrap(); + let res = call.clone().dispatch(Some(0).into()); + let mut post_info = res.unwrap(); + // 500 actual call weight + assert_eq!(post_info.actual_weight, Some(Weight::from_parts(500, 0))); + // add the 600 worst case extension weight + post_info.set_extension_weight(&info); + // extension weight should be refunded + assert_ok!(>::post_dispatch( + pre, + &info, + &mut post_info, + 0, + &Ok(()), + )); + // 500 actual call weight + 50 + 0 + 200 + assert_eq!(post_info.actual_weight, Some(Weight::from_parts(750, 0))); + + // Fourth testcase + let ext: TxExtension = + (HalfCostIf(false), FreeIfUnder(100), ActualWeightIs(300)).into(); + let (pre, _) = ext.validate_and_prepare(Some(0).into(), &call, &info, 0, 0).unwrap(); + let res = call.clone().dispatch(Some(0).into()); + let mut post_info = res.unwrap(); + // 500 actual call weight + assert_eq!(post_info.actual_weight, Some(Weight::from_parts(500, 0))); + // add the 600 worst case extension weight + post_info.set_extension_weight(&info); + // extension weight should be refunded + assert_ok!(>::post_dispatch( + pre, + &info, + &mut post_info, + 0, + &Ok(()), + )); + // 500 actual call weight + 100 + 200 + 300 + assert_eq!(post_info.actual_weight, Some(Weight::from_parts(1100, 0))); + }); + } + + #[test] + fn checked_extrinsic_apply() { + ExtBuilder::default().build_and_execute(|| { + let call = RuntimeCall::System(frame_system::Call::::f100 {}); + // First testcase + let ext: TxExtension = (HalfCostIf(false), FreeIfUnder(2000), ActualWeightIs(0)).into(); + let xt = CheckedExtrinsic { + format: ExtrinsicFormat::Signed(0, ext.clone()), + function: call.clone(), + }; + assert_eq!(xt.extension_weight(), Weight::from_parts(600, 0)); + let mut info = call.get_dispatch_info(); + assert_eq!(info.call_weight, Weight::from_parts(1000, 0)); + info.extension_weight = ext.weight(&call); + assert_eq!(info.total_weight(), Weight::from_parts(1600, 0)); + let post_info = xt.apply::(&info, 0).unwrap().unwrap(); + // 500 actual call weight + 100 + 0 + 0 + assert_eq!(post_info.actual_weight, Some(Weight::from_parts(600, 0))); + + // Second testcase + let ext: TxExtension = + (HalfCostIf(false), FreeIfUnder(1100), ActualWeightIs(200)).into(); + let xt = CheckedExtrinsic { + format: ExtrinsicFormat::Signed(0, ext), + function: call.clone(), + }; + let post_info = xt.apply::(&info, 0).unwrap().unwrap(); + // 500 actual call weight + 100 + 200 + 200 + assert_eq!(post_info.actual_weight, Some(Weight::from_parts(1000, 0))); + + // Third testcase + let ext: TxExtension = + (HalfCostIf(true), FreeIfUnder(1060), ActualWeightIs(200)).into(); + let xt = CheckedExtrinsic { + format: ExtrinsicFormat::Signed(0, ext), + function: call.clone(), + }; + let post_info = xt.apply::(&info, 0).unwrap().unwrap(); + // 500 actual call weight + 50 + 0 + 200 + assert_eq!(post_info.actual_weight, Some(Weight::from_parts(750, 0))); + + // Fourth testcase + let ext: TxExtension = + (HalfCostIf(false), FreeIfUnder(100), ActualWeightIs(300)).into(); + let xt = CheckedExtrinsic { + format: ExtrinsicFormat::Signed(0, ext), + function: call.clone(), + }; + let post_info = xt.apply::(&info, 0).unwrap().unwrap(); + // 500 actual call weight + 100 + 200 + 300 + assert_eq!(post_info.actual_weight, Some(Weight::from_parts(1100, 0))); + }); + } +} diff --git a/substrate/primitives/runtime/src/traits/mod.rs b/substrate/primitives/runtime/src/traits/mod.rs index cfcc3e5a354d..d86adc745a6c 100644 --- a/substrate/primitives/runtime/src/traits/mod.rs +++ b/substrate/primitives/runtime/src/traits/mod.rs @@ -55,7 +55,8 @@ use std::str::FromStr; pub mod transaction_extension; pub use transaction_extension::{ - DispatchTransaction, TransactionExtension, TransactionExtensionMetadata, ValidateResult, + DispatchTransaction, TransactionExtension, TransactionExtensionMetadata, + TransactionExtensionPipeline, TransactionExtensionPipelineImplicit, ValidateResult, }; /// A lazy value. diff --git a/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs b/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs index f8c5dc6a724e..100c8c18c3dd 100644 --- a/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs +++ b/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs @@ -39,9 +39,13 @@ use super::{ mod as_transaction_extension; mod dispatch_transaction; +mod transaction_extension_pipeline; #[allow(deprecated)] pub use as_transaction_extension::AsTransactionExtension; pub use dispatch_transaction::DispatchTransaction; +pub use transaction_extension_pipeline::{ + NoTxExt, TransactionExtensionPipeline, TransactionExtensionPipelineImplicit, +}; /// Shortcut for the result value of the `validate` function. pub type ValidateResult = @@ -605,6 +609,7 @@ impl TransactionExtension for Tuple { impl TransactionExtension for () { const IDENTIFIER: &'static str = "UnitTransactionExtension"; type Implicit = (); + #[inline] fn implicit(&self) -> sp_std::result::Result { Ok(()) } diff --git a/substrate/primitives/runtime/src/traits/transaction_extension/transaction_extension_pipeline.rs b/substrate/primitives/runtime/src/traits/transaction_extension/transaction_extension_pipeline.rs new file mode 100644 index 000000000000..c69fddf62071 --- /dev/null +++ b/substrate/primitives/runtime/src/traits/transaction_extension/transaction_extension_pipeline.rs @@ -0,0 +1,862 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The transaction extension pipeline struct, allowing to define a pipeline with many elements. + +use crate::{ + scale_info::TypeInfo, + traits::{ + transaction_extension::{ + TransactionExtension, TransactionExtensionMetadata, ValidateResult, + }, + DispatchInfoOf, DispatchOriginOf, Dispatchable, PostDispatchInfoOf, + }, + transaction_validity::{ + TransactionSource, TransactionValidity, TransactionValidityError, ValidTransaction, + }, + DispatchResult, +}; +use alloc::vec::Vec; +use codec::{Decode, Encode}; +use core::fmt::Debug; +use sp_weights::Weight; +use tuplex::PushBack; + +/// A no-op implementation of [`TransactionExtension`]. +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)] +pub struct NoTxExt; + +impl TransactionExtension for NoTxExt { + const IDENTIFIER: &'static str = "NoTxExt"; + type Implicit = (); + #[inline] + fn implicit(&self) -> sp_std::result::Result { + Ok(()) + } + type Val = (); + type Pre = (); + #[inline] + fn weight(&self, _call: &Call) -> Weight { + Weight::zero() + } + #[inline] + fn validate( + &self, + origin: ::RuntimeOrigin, + _call: &Call, + _info: &DispatchInfoOf, + _len: usize, + _self_implicit: Self::Implicit, + _inherited_implication: &impl Encode, + _source: TransactionSource, + ) -> Result< + (ValidTransaction, (), ::RuntimeOrigin), + TransactionValidityError, + > { + Ok((ValidTransaction::default(), (), origin)) + } + #[inline] + fn prepare( + self, + _val: (), + _origin: &::RuntimeOrigin, + _call: &Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> Result<(), TransactionValidityError> { + Ok(()) + } + #[inline] + fn metadata() -> Vec { + alloc::vec![] + } + #[inline] + fn post_dispatch( + _pre: Self::Pre, + _info: &DispatchInfoOf, + _post_info: &mut PostDispatchInfoOf, + _len: usize, + _result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + Ok(()) + } + #[inline] + fn bare_validate( + _call: &Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> TransactionValidity { + Ok(ValidTransaction::default()) + } + #[inline] + fn bare_post_dispatch( + _info: &DispatchInfoOf, + _post_info: &mut PostDispatchInfoOf, + _len: usize, + _result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + Ok(()) + } + #[inline] + fn post_dispatch_details( + _pre: Self::Pre, + _info: &DispatchInfoOf, + _post_info: &PostDispatchInfoOf, + _len: usize, + _result: &DispatchResult, + ) -> Result { + Ok(Weight::zero()) + } + #[inline] + fn bare_validate_and_prepare( + _call: &Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> Result<(), TransactionValidityError> { + Ok(()) + } +} + +macro_rules! declare_pipeline { + ($( $num:tt: $generic:ident, { $( $basket_0:tt )* }, { $( $basket_1:tt )* }, { $( $basket_2:tt )* }, )*) => { + /// A pipeline of transaction extensions. Same as a tuple of transaction extensions, but + /// support up to 32 elements. + // NOTE: To extend beyond 32 elements we need to get rid of `push_back` usage. + #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)] + pub struct TransactionExtensionPipeline< + $( $generic = NoTxExt, )* + >( + $( pub $generic, )* + ); + + paste::paste! { + $( + impl< $( [< E $basket_0 >], )* > + From<( $( [< E $basket_0 >], )* )> + for TransactionExtensionPipeline< $( [< E $basket_0 >], )* > + { + fn from(e: ($( [< E $basket_0 >], )*)) -> Self { + TransactionExtensionPipeline( + $( e.$basket_0, )* + $( { + #[allow(clippy::no_effect)] + $basket_1; + NoTxExt + }, )* + $( { + #[allow(clippy::no_effect)] + $basket_2; + NoTxExt + }, )* + ) + } + } + )* + } + + /// Implicit type for `TransactionExtensionPipeline`. + #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)] + pub struct TransactionExtensionPipelineImplicit< + $( $generic = (), )* + >( + $( pub $generic, )* + ); + + paste::paste! { + $( + impl< $( [< E $basket_0 >], )* > + From<( $( [< E $basket_0 >], )* )> + for TransactionExtensionPipelineImplicit< $( [< E $basket_0 >], )* > + { + fn from(e: ($( [< E $basket_0 >], )*)) -> Self { + TransactionExtensionPipelineImplicit( + $( e.$basket_0, )* + $( { + #[allow(clippy::no_effect)] + $basket_1; + () + }, )* + $( { + #[allow(clippy::no_effect)] + $basket_2; + () + }, )* + ) + } + } + )* + } + + impl< + Call: Dispatchable, + $( $generic: TransactionExtension, )* + > TransactionExtension + for TransactionExtensionPipeline< + $( $generic, )* + > + { + const IDENTIFIER: &'static str = "TransactionExtensionPipeline"; + type Implicit = TransactionExtensionPipelineImplicit< + $( <$generic as TransactionExtension>::Implicit, )* + >; + fn implicit(&self) -> Result { + Ok(TransactionExtensionPipelineImplicit( + $( self.$num.implicit()?, )* + )) + } + fn metadata() -> Vec { + let mut ids = Vec::new(); + $( ids.extend($generic::metadata()); )* + ids + } + type Val = ( $( <$generic as TransactionExtension>::Val, )* ); + type Pre = ( $( <$generic as TransactionExtension>::Pre, )* ); + fn weight(&self, call: &Call) -> Weight { + Weight::zero() + $( .saturating_add(self.$num.weight(call)) )* + } + fn validate( + &self, + origin: DispatchOriginOf, + call: &Call, + info: &DispatchInfoOf, + len: usize, + self_implicit: Self::Implicit, + inherited_implication: &impl Encode, + source: TransactionSource, + ) -> ValidateResult { + let valid = ValidTransaction::default(); + let val = (); + let explicit_implications = ( + $( &self.$num, )* + ); + let implicit_implications = self_implicit; + + $( + // Implication of this pipeline element not relevant for later items, so we pop it. + let item = explicit_implications.$num; + let item_implicit = implicit_implications.$num; + let (item_valid, item_val, origin) = { + let implications = ( + // The first is the implications born of the fact we return the mutated + // origin. + inherited_implication, + // This is the explicitly made implication born of the fact the new origin is + // passed into the next items in this pipeline-tuple. + ( + ( $( explicit_implications.$basket_1, )* ), + ( $( explicit_implications.$basket_2, )* ), + ), + // This is the implicitly made implication born of the fact the new origin is + // passed into the next items in this pipeline-tuple. + ( + ( $( &implicit_implications.$basket_1, )* ), + ( $( &implicit_implications.$basket_2, )* ), + ), + ); + $generic::validate(item, origin, call, info, len, item_implicit, &implications, source)? + }; + let valid = valid.combine_with(item_valid); + let val = val.push_back(item_val); + )* + + Ok((valid, val, origin)) + } + fn prepare( + self, + val: Self::Val, + origin: &DispatchOriginOf, + call: &Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + Ok(( + $( self.$num.prepare(val.$num, origin, call, info, len)?, )* + )) + } + fn post_dispatch_details( + pre: Self::Pre, + info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, + len: usize, + result: &DispatchResult, + ) -> Result { + let mut total_unspent_weight = Weight::zero(); + + $( + let unspent_weight = $generic::post_dispatch_details(pre.$num, info, post_info, len, result)?; + total_unspent_weight = total_unspent_weight.saturating_add(unspent_weight); + )* + + Ok(total_unspent_weight) + + } + fn post_dispatch( + pre: Self::Pre, + info: &DispatchInfoOf, + post_info: &mut PostDispatchInfoOf, + len: usize, + result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + $( + $generic::post_dispatch(pre.$num, info, post_info, len, result)?; + )* + Ok(()) + } + fn bare_validate(call: &Call, info: &DispatchInfoOf, len: usize) -> TransactionValidity { + let valid = ValidTransaction::default(); + $( + let item_valid = $generic::bare_validate(call, info, len)?; + let valid = valid.combine_with(item_valid); + )* + Ok(valid) + } + + fn bare_validate_and_prepare( + call: &Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result<(), TransactionValidityError> { + $( $generic::bare_validate_and_prepare(call, info, len)?; )* + Ok(()) + } + + fn bare_post_dispatch( + info: &DispatchInfoOf, + post_info: &mut PostDispatchInfoOf, + len: usize, + result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + $( $generic::bare_post_dispatch(info, post_info, len, result)?; )* + Ok(()) + } + } + }; +} + +declare_pipeline!( + 0: TransactionExtension0, { 0 }, { 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 }, { 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 1: TransactionExtension1, { 0 1 }, { 2 3 4 5 6 7 8 9 10 11 12 13 14 15 }, { 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 2: TransactionExtension2, { 0 1 2 }, { 3 4 5 6 7 8 9 10 11 12 13 14 15 }, { 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 3: TransactionExtension3, { 0 1 2 3 }, { 4 5 6 7 8 9 10 11 12 13 14 15 }, { 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 4: TransactionExtension4, { 0 1 2 3 4 }, { 5 6 7 8 9 10 11 12 13 14 15 }, { 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 5: TransactionExtension5, { 0 1 2 3 4 5 }, { 6 7 8 9 10 11 12 13 14 15 }, { 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 6: TransactionExtension6, { 0 1 2 3 4 5 6 }, { 7 8 9 10 11 12 13 14 15 }, { 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 7: TransactionExtension7, { 0 1 2 3 4 5 6 7 }, { 8 9 10 11 12 13 14 15 }, { 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 8: TransactionExtension8, { 0 1 2 3 4 5 6 7 8 }, { 9 10 11 12 13 14 15 }, { 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 9: TransactionExtension9, { 0 1 2 3 4 5 6 7 8 9 }, { 10 11 12 13 14 15 }, { 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 10: TransactionExtension10, { 0 1 2 3 4 5 6 7 8 9 10 }, { 11 12 13 14 15 }, { 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 11: TransactionExtension11, { 0 1 2 3 4 5 6 7 8 9 10 11 }, { 12 13 14 15 }, { 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 12: TransactionExtension12, { 0 1 2 3 4 5 6 7 8 9 10 11 12 }, { 13 14 15 }, { 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 13: TransactionExtension13, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 }, { 14 15 }, { 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 14: TransactionExtension14, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 }, { 15 }, { 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 15: TransactionExtension15, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 }, { }, { 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 16: TransactionExtension16, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 }, { }, { 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 17: TransactionExtension17, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 }, { }, { 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 18: TransactionExtension18, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 }, { }, { 19 20 21 22 23 24 25 26 27 28 29 30 31 }, + 19: TransactionExtension19, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 }, { }, { 20 21 22 23 24 25 26 27 28 29 30 31 }, + 20: TransactionExtension20, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 }, { }, { 21 22 23 24 25 26 27 28 29 30 31 }, + 21: TransactionExtension21, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 }, { }, { 22 23 24 25 26 27 28 29 30 31 }, + 22: TransactionExtension22, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 }, { }, { 23 24 25 26 27 28 29 30 31 }, + 23: TransactionExtension23, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 }, { }, { 24 25 26 27 28 29 30 31 }, + 24: TransactionExtension24, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 }, { }, { 25 26 27 28 29 30 31 }, + 25: TransactionExtension25, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 }, { }, { 26 27 28 29 30 31 }, + 26: TransactionExtension26, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 }, { }, { 27 28 29 30 31 }, + 27: TransactionExtension27, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 }, { }, { 28 29 30 31 }, + 28: TransactionExtension28, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 }, { }, { 29 30 31 }, + 29: TransactionExtension29, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 }, { }, { 30 31 }, + 30: TransactionExtension30, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 }, { }, { 31 }, + 31: TransactionExtension31, { 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }, { }, { }, +); + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + traits::{ExtensionPostDispatchWeightHandler, Printable, RefundWeight}, + transaction_validity::InvalidTransaction, + }; + use std::cell::RefCell; + + struct MockCall; + + #[derive(Eq, PartialEq, Clone, Copy, Encode, Decode)] + struct MockPostInfo(sp_weights::Weight); + + impl Printable for MockPostInfo { + fn print(&self) { + self.0.print(); + } + } + + impl ExtensionPostDispatchWeightHandler<()> for MockPostInfo { + fn set_extension_weight(&mut self, _info: &()) { + unimplemented!(); + } + } + + impl RefundWeight for MockPostInfo { + fn refund(&mut self, weight: sp_weights::Weight) { + self.0 = self.0.saturating_sub(weight); + } + } + + impl Dispatchable for MockCall { + type RuntimeOrigin = (); + type Config = (); + type Info = (); + type PostInfo = MockPostInfo; + fn dispatch( + self, + _origin: Self::RuntimeOrigin, + ) -> crate::DispatchResultWithInfo { + panic!("This implementation should not be used for actual dispatch."); + } + } + + #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)] + struct TransactionExtensionN< + const WEIGHT: u64, + const POST_DISPATCH_WEIGHT: u64, + const VAL: u32, + const PRE: u32, + const IMPLICIT: u32, + const BARE_VALIDATE: bool, + const BARE_POST_DISPATCH: bool, + >(u32); + + impl< + const WEIGHT: u64, + const POST_DISPATCH_WEIGHT: u64, + const VAL: u32, + const PRE: u32, + const IMPLICIT: u32, + const BARE_VALIDATE: bool, + const BARE_POST_DISPATCH: bool, + > + TransactionExtensionN< + WEIGHT, + POST_DISPATCH_WEIGHT, + VAL, + PRE, + IMPLICIT, + BARE_VALIDATE, + BARE_POST_DISPATCH, + > + { + fn new(explicit: u32) -> Self { + Self(explicit) + } + } + + impl< + const WEIGHT: u64, + const POST_DISPATCH_WEIGHT: u64, + const VAL: u32, + const PRE: u32, + const IMPLICIT: u32, + const BARE_VALIDATE: bool, + const BARE_POST_DISPATCH: bool, + > TransactionExtension + for TransactionExtensionN< + WEIGHT, + POST_DISPATCH_WEIGHT, + VAL, + PRE, + IMPLICIT, + BARE_VALIDATE, + BARE_POST_DISPATCH, + > + { + const IDENTIFIER: &'static str = "TransactionExtensionN"; + type Implicit = u32; + fn implicit(&self) -> Result { + Ok(IMPLICIT) + } + type Val = u32; + type Pre = u32; + fn weight(&self, _call: &MockCall) -> Weight { + WEIGHT.into() + } + fn validate( + &self, + origin: (), + _call: &MockCall, + _info: &(), + _len: usize, + self_implicit: Self::Implicit, + _inherited_implication: &impl Encode, + _source: TransactionSource, + ) -> ValidateResult { + assert_eq!(self_implicit, IMPLICIT); + Ok((ValidTransaction::default(), VAL, origin)) + } + fn prepare( + self, + val: Self::Val, + _origin: &(), + _call: &MockCall, + _info: &(), + _len: usize, + ) -> Result { + assert_eq!(val, VAL); + Ok(PRE) + } + fn post_dispatch_details( + _pre: Self::Pre, + _info: &(), + _post_info: &MockPostInfo, + _len: usize, + _result: &DispatchResult, + ) -> Result { + Ok(POST_DISPATCH_WEIGHT.into()) + } + fn bare_validate(_call: &MockCall, _info: &(), _len: usize) -> TransactionValidity { + if BARE_VALIDATE { + Ok(ValidTransaction::default()) + } else { + Err(InvalidTransaction::Custom(0).into()) + } + } + fn bare_validate_and_prepare( + _call: &MockCall, + _info: &(), + _len: usize, + ) -> Result<(), TransactionValidityError> { + if BARE_VALIDATE { + Ok(()) + } else { + Err(InvalidTransaction::Custom(0).into()) + } + } + fn bare_post_dispatch( + _info: &(), + post_info: &mut MockPostInfo, + _len: usize, + _result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + if BARE_POST_DISPATCH { + post_info.refund(POST_DISPATCH_WEIGHT.into()); + Ok(()) + } else { + Err(InvalidTransaction::Custom(0).into()) + } + } + } + + #[test] + fn test_bare() { + type T1 = TransactionExtensionN<0, 1, 0, 0, 0, true, true>; + type T1Bis = TransactionExtensionN<0, 2, 0, 0, 0, true, true>; + type T2 = TransactionExtensionN<0, 0, 0, 0, 0, false, false>; + + type P1 = TransactionExtensionPipeline; + P1::bare_validate_and_prepare(&MockCall, &(), 0).expect("success"); + P1::bare_validate(&MockCall, &(), 0).expect("success"); + let mut post_info = MockPostInfo(100.into()); + P1::bare_post_dispatch(&(), &mut post_info, 0, &Ok(())).expect("success"); + assert_eq!(post_info.0, (100 - 1 - 2 - 1).into()); + + type P2 = TransactionExtensionPipeline; + assert_eq!( + P2::bare_validate_and_prepare(&MockCall, &(), 0).unwrap_err(), + InvalidTransaction::Custom(0).into() + ); + assert_eq!( + P2::bare_validate(&MockCall, &(), 0).unwrap_err(), + InvalidTransaction::Custom(0).into() + ); + let mut post_info = MockPostInfo(100.into()); + assert_eq!( + P2::bare_post_dispatch(&(), &mut post_info, 0, &Ok(())).unwrap_err(), + InvalidTransaction::Custom(0).into() + ); + assert_eq!(post_info.0, (100 - 1 - 2).into()); + } + + const A_WEIGHT: u64 = 3; + const A_POST_DISPATCH_WEIGHT: u64 = 1; + const A_VAL: u32 = 4; + const A_PRE: u32 = 5; + const A_IMPLICIT: u32 = 6; + const A_EXPLICIT: u32 = 7; + type TransactionExtensionA = TransactionExtensionN< + A_WEIGHT, + A_POST_DISPATCH_WEIGHT, + A_VAL, + A_PRE, + A_IMPLICIT, + true, + true, + >; + + const B_WEIGHT: u64 = 5; + const B_POST_DISPATCH_WEIGHT: u64 = 2; + const B_VAL: u32 = 6; + const B_PRE: u32 = 7; + const B_IMPLICIT: u32 = 8; + const B_EXPLICIT: u32 = 9; + type TransactionExtensionB = TransactionExtensionN< + B_WEIGHT, + B_POST_DISPATCH_WEIGHT, + B_VAL, + B_PRE, + B_IMPLICIT, + true, + true, + >; + + thread_local! { + pub static INHERITED_IMPLICATION: RefCell> = RefCell::new(vec![]); + } + + #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)] + struct TransactionExtensionCheck; + impl TransactionExtension for TransactionExtensionCheck { + const IDENTIFIER: &'static str = "TransactionExtensionCheck"; + type Implicit = (); + fn implicit(&self) -> Result { + Ok(()) + } + type Val = u32; + type Pre = u32; + fn weight(&self, _call: &MockCall) -> Weight { + Weight::zero() + } + fn validate( + &self, + origin: (), + _call: &MockCall, + _info: &(), + _len: usize, + _self_implicit: Self::Implicit, + inherited_implication: &impl Encode, + _source: TransactionSource, + ) -> ValidateResult { + INHERITED_IMPLICATION.with_borrow(|i| assert_eq!(*i, inherited_implication.encode())); + Ok((ValidTransaction::default(), 0, origin)) + } + fn prepare( + self, + _val: Self::Val, + _origin: &(), + _call: &MockCall, + _info: &(), + _len: usize, + ) -> Result { + Ok(0) + } + fn post_dispatch_details( + _pre: Self::Pre, + _info: &(), + _post_info: &MockPostInfo, + _len: usize, + _result: &DispatchResult, + ) -> Result { + Ok(Weight::zero()) + } + } + + #[test] + fn inherited_implications_at_the_end() { + let t1 = TransactionExtensionPipeline::from(( + TransactionExtensionA::new(A_EXPLICIT), + TransactionExtensionB::new(B_EXPLICIT), + TransactionExtensionCheck, + )); + + t1.validate( + (), + &MockCall, + &(), + 0, + TransactionExtensionPipelineImplicit::from((A_IMPLICIT, B_IMPLICIT, ())), + &(), + TransactionSource::Local, + ) + .unwrap(); + } + + #[test] + fn inherited_implications_in_the_middle_1() { + INHERITED_IMPLICATION.with_borrow_mut(|i| { + *i = (B_EXPLICIT, B_IMPLICIT).encode(); + }); + + let t1 = TransactionExtensionPipeline::from(( + TransactionExtensionA::new(A_EXPLICIT), + TransactionExtensionCheck, + TransactionExtensionB::new(B_EXPLICIT), + )); + + t1.validate( + (), + &MockCall, + &(), + 0, + TransactionExtensionPipelineImplicit::from((A_IMPLICIT, (), B_IMPLICIT)), + &(), + TransactionSource::Local, + ) + .unwrap(); + } + + #[test] + fn inherited_implications_in_the_middle_2() { + INHERITED_IMPLICATION.with_borrow_mut(|i| { + *i = (B_EXPLICIT, A_EXPLICIT, B_IMPLICIT, A_IMPLICIT).encode(); + }); + + let t2 = TransactionExtensionPipeline::from(( + TransactionExtensionA::new(A_EXPLICIT), + TransactionExtensionCheck, + TransactionExtensionB::new(B_EXPLICIT), + TransactionExtensionA::new(A_EXPLICIT), + )); + + t2.validate( + (), + &MockCall, + &(), + 0, + TransactionExtensionPipelineImplicit::from((A_IMPLICIT, (), B_IMPLICIT, A_IMPLICIT)), + &(), + TransactionSource::Local, + ) + .unwrap(); + } + + #[test] + fn inherited_implications_in_the_middle_3() { + INHERITED_IMPLICATION.with_borrow_mut(|i| { + *i = ( + (B_EXPLICIT, A_EXPLICIT, B_EXPLICIT, A_EXPLICIT), + (B_EXPLICIT, A_EXPLICIT, B_EXPLICIT, A_EXPLICIT), + (B_EXPLICIT, A_EXPLICIT, B_EXPLICIT, A_EXPLICIT), + (B_EXPLICIT, A_EXPLICIT, B_EXPLICIT, A_EXPLICIT), + (B_EXPLICIT, B_EXPLICIT, B_EXPLICIT, B_EXPLICIT), + (B_IMPLICIT, A_IMPLICIT, B_IMPLICIT, A_IMPLICIT), + (B_IMPLICIT, A_IMPLICIT, B_IMPLICIT, A_IMPLICIT), + (B_IMPLICIT, A_IMPLICIT, B_IMPLICIT, A_IMPLICIT), + (B_IMPLICIT, A_IMPLICIT, B_IMPLICIT, A_IMPLICIT), + (B_IMPLICIT, B_IMPLICIT, B_IMPLICIT, B_IMPLICIT), + ) + .encode(); + }); + + let t3 = TransactionExtensionPipeline::from(( + TransactionExtensionA::new(A_EXPLICIT), + TransactionExtensionCheck, + TransactionExtensionB::new(B_EXPLICIT), + TransactionExtensionA::new(A_EXPLICIT), + TransactionExtensionB::new(B_EXPLICIT), + TransactionExtensionA::new(A_EXPLICIT), + TransactionExtensionB::new(B_EXPLICIT), + TransactionExtensionA::new(A_EXPLICIT), + TransactionExtensionB::new(B_EXPLICIT), + TransactionExtensionA::new(A_EXPLICIT), + TransactionExtensionB::new(B_EXPLICIT), + TransactionExtensionA::new(A_EXPLICIT), + TransactionExtensionB::new(B_EXPLICIT), + TransactionExtensionA::new(A_EXPLICIT), + TransactionExtensionB::new(B_EXPLICIT), + TransactionExtensionA::new(A_EXPLICIT), + TransactionExtensionB::new(B_EXPLICIT), + TransactionExtensionA::new(A_EXPLICIT), + TransactionExtensionB::new(B_EXPLICIT), + TransactionExtensionB::new(B_EXPLICIT), + TransactionExtensionB::new(B_EXPLICIT), + TransactionExtensionB::new(B_EXPLICIT), + )); + + t3.validate( + (), + &MockCall, + &(), + 0, + TransactionExtensionPipelineImplicit::from(( + A_IMPLICIT, + (), + B_IMPLICIT, + A_IMPLICIT, + B_IMPLICIT, + A_IMPLICIT, + B_IMPLICIT, + A_IMPLICIT, + B_IMPLICIT, + A_IMPLICIT, + B_IMPLICIT, + A_IMPLICIT, + B_IMPLICIT, + A_IMPLICIT, + B_IMPLICIT, + A_IMPLICIT, + B_IMPLICIT, + A_IMPLICIT, + B_IMPLICIT, + B_IMPLICIT, + B_IMPLICIT, + B_IMPLICIT, + )), + &(), + TransactionSource::Local, + ) + .unwrap(); + } + + #[test] + fn general_tx_test() { + type Pipeline = TransactionExtensionPipeline; + let p = Pipeline::from(( + TransactionExtensionA::new(A_EXPLICIT), + TransactionExtensionB::new(B_EXPLICIT), + )); + + let weight = p.weight(&MockCall); + assert_eq!(weight, (A_WEIGHT + B_WEIGHT).into()); + + let implicit = p.implicit().unwrap(); + assert_eq!(implicit, (A_IMPLICIT, B_IMPLICIT).into()); + + let val = p + .validate( + (), + &MockCall, + &(), + 0, + TransactionExtensionPipelineImplicit::from((A_IMPLICIT, B_IMPLICIT)), + &(), + TransactionSource::Local, + ) + .unwrap(); + assert_eq!(val.1 .0, A_VAL); + assert_eq!(val.1 .1, B_VAL); + + let pre = p.prepare(val.1, &(), &MockCall, &(), 0).unwrap(); + assert_eq!(pre.0, A_PRE); + assert_eq!(pre.1, B_PRE); + + let details = + Pipeline::post_dispatch_details(pre, &(), &MockPostInfo(100.into()), 0, &Ok(())) + .unwrap(); + assert_eq!(details, (A_POST_DISPATCH_WEIGHT + B_POST_DISPATCH_WEIGHT).into()); + + let mut post_info = MockPostInfo(100.into()); + Pipeline::post_dispatch(pre, &(), &mut post_info, 0, &Ok(())).unwrap(); + assert_eq!(post_info.0, (100 - A_POST_DISPATCH_WEIGHT - B_POST_DISPATCH_WEIGHT).into()); + } +}