Skip to main content

sp_runtime/traits/vers_tx_ext/
variant.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Implementation of versioned transaction extension pipeline that aggregate a version 0 and
19//! other versions.
20
21use crate::{
22	generic::ExtensionVersion,
23	traits::{
24		AsTransactionAuthorizedOrigin, DecodeWithVersion, DecodeWithVersionWithMemTracking,
25		DispatchInfoOf, DispatchTransaction, Dispatchable, Pipeline, PipelineAtVers,
26		PipelineMetadataBuilder, PipelineVersion, PostDispatchInfoOf, TransactionExtension,
27	},
28	transaction_validity::TransactionSource,
29};
30use alloc::vec::Vec;
31use codec::{Decode, Encode};
32use scale_info::TypeInfo;
33use sp_weights::Weight;
34
35/// Version 0 of the transaction extension version.
36const EXTENSION_V0_VERSION: ExtensionVersion = 0;
37
38/// A versioned transaction extension pipeline defined with 2 variants: one for the version 0 and
39/// one for other versions.
40///
41/// The generic `ExtensionOtherVersions` should not re-define a transaction extension pipeline for
42/// the version 0, it will be ignored. The transaction extension pipeline for the version 0 is
43/// defined by the generic `ExtensionV0`.
44#[derive(PartialEq, Eq, Clone, Debug, TypeInfo)]
45pub enum ExtensionVariant<ExtensionV0, ExtensionOtherVersions> {
46	/// A transaction extension pipeline for the version 0.
47	V0(ExtensionV0),
48	/// A transaction extension pipeline for other versions.
49	Other(ExtensionOtherVersions),
50}
51
52impl<ExtensionV0, ExtensionOtherVersions: PipelineVersion> PipelineVersion
53	for ExtensionVariant<ExtensionV0, ExtensionOtherVersions>
54{
55	fn version(&self) -> u8 {
56		match self {
57			ExtensionVariant::V0(_) => EXTENSION_V0_VERSION,
58			ExtensionVariant::Other(ext) => ext.version(),
59		}
60	}
61}
62
63impl<ExtensionV0: Encode, ExtensionOtherVersions: Encode> Encode
64	for ExtensionVariant<ExtensionV0, ExtensionOtherVersions>
65{
66	fn encode(&self) -> Vec<u8> {
67		match self {
68			ExtensionVariant::V0(ext) => ext.encode(),
69			ExtensionVariant::Other(ext) => ext.encode(),
70		}
71	}
72	fn size_hint(&self) -> usize {
73		match self {
74			ExtensionVariant::V0(ext) => ext.size_hint(),
75			ExtensionVariant::Other(ext) => ext.size_hint(),
76		}
77	}
78	fn encode_to<T: codec::Output + ?Sized>(&self, dest: &mut T) {
79		match self {
80			ExtensionVariant::V0(ext) => ext.encode_to(dest),
81			ExtensionVariant::Other(ext) => ext.encode_to(dest),
82		}
83	}
84	fn encoded_size(&self) -> usize {
85		match self {
86			ExtensionVariant::V0(ext) => ext.encoded_size(),
87			ExtensionVariant::Other(ext) => ext.encoded_size(),
88		}
89	}
90	fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
91		match self {
92			ExtensionVariant::V0(ext) => ext.using_encoded(f),
93			ExtensionVariant::Other(ext) => ext.using_encoded(f),
94		}
95	}
96}
97
98impl<ExtensionV0: Decode, ExtensionOtherVersions: DecodeWithVersion> DecodeWithVersion
99	for ExtensionVariant<ExtensionV0, ExtensionOtherVersions>
100{
101	fn decode_with_version<I: codec::Input>(
102		extension_version: u8,
103		input: &mut I,
104	) -> Result<Self, codec::Error> {
105		match extension_version {
106			EXTENSION_V0_VERSION => Ok(ExtensionVariant::V0(Decode::decode(input)?)),
107			_ => Ok(ExtensionVariant::Other(DecodeWithVersion::decode_with_version(
108				extension_version,
109				input,
110			)?)),
111		}
112	}
113}
114
115impl<ExtensionV0: Decode, ExtensionOtherVersions: DecodeWithVersionWithMemTracking>
116	DecodeWithVersionWithMemTracking for ExtensionVariant<ExtensionV0, ExtensionOtherVersions>
117{
118}
119
120impl<
121		Call: Dispatchable + Encode,
122		ExtensionV0: TransactionExtension<Call>,
123		ExtensionOtherVersions: Pipeline<Call>,
124	> Pipeline<Call> for ExtensionVariant<ExtensionV0, ExtensionOtherVersions>
125where
126	<Call as Dispatchable>::RuntimeOrigin: AsTransactionAuthorizedOrigin,
127{
128	fn build_metadata(builder: &mut PipelineMetadataBuilder) {
129		PipelineAtVers::<EXTENSION_V0_VERSION, ExtensionV0>::build_metadata(builder);
130		ExtensionOtherVersions::build_metadata(builder);
131	}
132	fn validate_only(
133		&self,
134		origin: super::DispatchOriginOf<Call>,
135		call: &Call,
136		info: &DispatchInfoOf<Call>,
137		len: usize,
138		source: TransactionSource,
139	) -> Result<
140		crate::transaction_validity::ValidTransaction,
141		crate::transaction_validity::TransactionValidityError,
142	> {
143		match self {
144			ExtensionVariant::V0(ext) => ext
145				.validate_only(origin, call, info, len, source, EXTENSION_V0_VERSION)
146				.map(|x| x.0),
147			ExtensionVariant::Other(ext) => ext.validate_only(origin, call, info, len, source),
148		}
149	}
150	fn dispatch_transaction(
151		self,
152		origin: super::DispatchOriginOf<Call>,
153		call: Call,
154		info: &DispatchInfoOf<Call>,
155		len: usize,
156	) -> crate::ApplyExtrinsicResultWithInfo<PostDispatchInfoOf<Call>> {
157		match self {
158			ExtensionVariant::V0(ext) => {
159				ext.dispatch_transaction(origin, call, info, len, EXTENSION_V0_VERSION)
160			},
161			ExtensionVariant::Other(ext) => ext.dispatch_transaction(origin, call, info, len),
162		}
163	}
164	fn weight(&self, call: &Call) -> Weight {
165		match self {
166			ExtensionVariant::V0(ext) => ext.weight(call),
167			ExtensionVariant::Other(ext) => ext.weight(call),
168		}
169	}
170}
171
172#[cfg(test)]
173mod tests {
174	use super::*;
175	use crate::{
176		traits::{
177			AsTransactionAuthorizedOrigin, DispatchInfoOf, Dispatchable, Implication,
178			PipelineAtVers, TransactionExtension, TransactionSource, ValidateResult,
179		},
180		transaction_validity::{InvalidTransaction, TransactionValidityError, ValidTransaction},
181		DispatchError,
182	};
183	use codec::{Decode, DecodeWithMemTracking, Encode};
184	use core::fmt::Debug;
185	use sp_weights::Weight;
186
187	// --------------------------------------------------------------------
188	// 1. Mock call and "origin" type
189	// --------------------------------------------------------------------
190
191	#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq)]
192	pub struct MockCall(pub u64);
193	#[derive(Debug)]
194	pub struct MockOrigin;
195
196	impl Dispatchable for MockCall {
197		type RuntimeOrigin = MockOrigin;
198		type Config = ();
199		type Info = ();
200		type PostInfo = ();
201
202		fn dispatch(
203			self,
204			_origin: Self::RuntimeOrigin,
205		) -> crate::DispatchResultWithInfo<Self::PostInfo> {
206			if self.0 == 0 {
207				return Err(DispatchError::Other("call is 0").into());
208			}
209			Ok(Default::default())
210		}
211	}
212
213	// We'll implement the AsTransactionAuthorizedOrigin for Option<u64>:
214	impl AsTransactionAuthorizedOrigin for MockOrigin {
215		fn is_transaction_authorized(&self) -> bool {
216			true
217		}
218	}
219
220	// --------------------------------------------------------------------
221	// 2. Mock Extension used as "ExtensionV0"
222	// --------------------------------------------------------------------
223
224	/// A trivial extension type for "old-school version 0".
225	#[derive(Clone, Debug, Encode, Decode, DecodeWithMemTracking, PartialEq, Eq, TypeInfo)]
226	pub struct ExtV0 {
227		pub success_token: bool,
228		pub w: u64,
229	}
230
231	impl TransactionExtension<MockCall> for ExtV0 {
232		const IDENTIFIER: &'static str = "OldSchoolV0";
233		type Implicit = ();
234		fn implicit(&self) -> Result<Self::Implicit, TransactionValidityError> {
235			Ok(())
236		}
237		type Val = ();
238		type Pre = ();
239
240		fn weight(&self, _call: &MockCall) -> Weight {
241			Weight::from_parts(self.w, 0)
242		}
243
244		fn validate(
245			&self,
246			origin: MockOrigin,
247			_call: &MockCall,
248			_info: &DispatchInfoOf<MockCall>,
249			_len: usize,
250			_self_implicit: Self::Implicit,
251			_inherited_implication: &impl Implication,
252			_source: TransactionSource,
253		) -> ValidateResult<Self::Val, MockCall> {
254			if !self.success_token {
255				Err(InvalidTransaction::Custom(99).into())
256			} else {
257				Ok((ValidTransaction::default(), (), origin))
258			}
259		}
260
261		fn prepare(
262			self,
263			_val: Self::Val,
264			_origin: &MockOrigin,
265			_call: &MockCall,
266			_info: &DispatchInfoOf<MockCall>,
267			_len: usize,
268		) -> Result<Self::Pre, TransactionValidityError> {
269			Ok(())
270		}
271	}
272
273	// --------------------------------------------------------------------
274	// 3. Another pipeline that is used for "Other" versions: We'll define a minimal versioned
275	//    pipeline with one version
276	// --------------------------------------------------------------------
277
278	/// Another extension for "some version" pipeline.
279	#[derive(Clone, Debug, Encode, Decode, DecodeWithMemTracking, PartialEq, Eq, TypeInfo)]
280	pub struct OtherExtension {
281		pub token: u16,
282		pub w: u64,
283	}
284
285	impl TransactionExtension<MockCall> for OtherExtension {
286		const IDENTIFIER: &'static str = "OtherExtension";
287		type Implicit = ();
288		fn implicit(&self) -> Result<Self::Implicit, TransactionValidityError> {
289			Ok(())
290		}
291		type Val = ();
292		type Pre = ();
293
294		fn weight(&self, _call: &MockCall) -> Weight {
295			Weight::from_parts(self.w, 0)
296		}
297
298		fn validate(
299			&self,
300			origin: MockOrigin,
301			_call: &MockCall,
302			_info: &DispatchInfoOf<MockCall>,
303			_len: usize,
304			_self_implicit: Self::Implicit,
305			_inherited_implication: &impl Implication,
306			_source: TransactionSource,
307		) -> ValidateResult<Self::Val, MockCall> {
308			// If 'token' is 0 => invalid. Else ok.
309			if self.token == 0 {
310				return Err(InvalidTransaction::Custom(7).into());
311			}
312			Ok((ValidTransaction::default(), (), origin))
313		}
314
315		fn prepare(
316			self,
317			_val: Self::Val,
318			_origin: &MockOrigin,
319			_call: &MockCall,
320			_info: &DispatchInfoOf<MockCall>,
321			_len: usize,
322		) -> Result<Self::Pre, TransactionValidityError> {
323			Ok(())
324		}
325	}
326
327	type ExtV2 = PipelineAtVers<2, OtherExtension>;
328
329	// --------------------------------------------------------------------
330	// Actual unit tests
331	// --------------------------------------------------------------------
332
333	type Variant = ExtensionVariant<ExtV0, ExtV2>;
334
335	#[test]
336	fn decode_v0() {
337		// If extension_version == 0 => decode as V0
338		let v0_data = ExtV0 { success_token: true, w: 42 }.encode();
339		let candidate = Variant::decode_with_version(0, &mut &v0_data[..])
340			.expect("decode with v0 must succeed");
341		let ExtensionVariant::V0(ext_v0) = candidate else { panic!("Expected V0 variant") };
342		assert!(ext_v0.success_token);
343		assert_eq!(ext_v0.w, 42);
344	}
345
346	#[test]
347	fn decode_other() {
348		// If extension_version == 2 => decode as Other
349		let pipeline = ExtV2::new(OtherExtension { token: 9, w: 888 });
350		let encoded = pipeline.encode();
351		let candidate = Variant::decode_with_version(2, &mut &encoded[..])
352			.expect("decode with version=2 => 'Other'");
353		let ExtensionVariant::Other(p) = candidate else { panic!("Expected Other variant") };
354		assert_eq!(p.extension.token, 9);
355		assert_eq!(p.extension.w, 888);
356	}
357
358	#[test]
359	fn version_check() {
360		let v0_var: Variant = ExtensionVariant::V0(ExtV0 { success_token: true, w: 1 });
361		let other_var: Variant =
362			ExtensionVariant::Other(ExtV2::new(OtherExtension { token: 1, w: 1 }));
363		assert_eq!(v0_var.version(), 0);
364		assert_eq!(other_var.version(), 2);
365	}
366
367	#[test]
368	fn weight_check() {
369		let v0_var: Variant = ExtensionVariant::V0(ExtV0 { success_token: true, w: 100 });
370		let other_var: Variant =
371			ExtensionVariant::Other(ExtV2::new(OtherExtension { token: 2, w: 555 }));
372		let call = MockCall(123);
373
374		assert_eq!(v0_var.weight(&call).ref_time(), 100);
375		assert_eq!(other_var.weight(&call).ref_time(), 555);
376	}
377
378	#[test]
379	fn validate_only_works() {
380		{
381			// v0 + success_token => ok
382			let v0_var: Variant = ExtensionVariant::V0(ExtV0 { success_token: true, w: 100 });
383			let call = MockCall(1);
384			let valid = v0_var.validate_only(
385				MockOrigin,
386				&call,
387				&Default::default(),
388				0,
389				TransactionSource::External,
390			);
391			assert!(valid.is_ok());
392		}
393		{
394			// other => token=0 => fail
395			let var_other: Variant =
396				ExtensionVariant::Other(ExtV2::new(OtherExtension { token: 0, w: 5 }));
397			let call = MockCall(1);
398			let fail = var_other.validate_only(
399				MockOrigin,
400				&call,
401				&Default::default(),
402				0,
403				TransactionSource::Local,
404			);
405			assert_eq!(fail, Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(7))));
406		}
407	}
408
409	#[test]
410	fn dispatch_transaction_works() {
411		// We'll do "v0" scenario with success_token => true => valid
412		let v0_var: Variant = ExtensionVariant::V0(ExtV0 { success_token: true, w: 12 });
413		let call = MockCall(42);
414		let result = v0_var.dispatch_transaction(MockOrigin, call.clone(), &Default::default(), 0);
415		let extrinsic_outcome = result.expect("Ok(ApplyExtrinsicResultWithInfo)");
416		assert!(extrinsic_outcome.is_ok(), "call with origin Some => dispatch Ok");
417
418		// If call is 0 => call fails at dispatch
419		let err = Variant::V0(ExtV0 { success_token: true, w: 1 })
420			.dispatch_transaction(MockOrigin, MockCall(0), &Default::default(), 0)
421			.expect("valid")
422			.expect_err("dispatch error");
423
424		assert_eq!(err.error, DispatchError::Other("call is 0"));
425
426		// check scenario for "other" too
427		let var_other: Variant =
428			ExtensionVariant::Other(ExtV2::new(OtherExtension { token: 5, w: 55 }));
429		let outcome = var_other
430			.dispatch_transaction(MockOrigin, call, &Default::default(), 0)
431			.expect("Should be ok");
432		assert!(outcome.is_ok());
433	}
434}