1use 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
35const EXTENSION_V0_VERSION: ExtensionVersion = 0;
37
38#[derive(PartialEq, Eq, Clone, Debug, TypeInfo)]
45pub enum ExtensionVariant<ExtensionV0, ExtensionOtherVersions> {
46 V0(ExtensionV0),
48 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 #[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 impl AsTransactionAuthorizedOrigin for MockOrigin {
215 fn is_transaction_authorized(&self) -> bool {
216 true
217 }
218 }
219
220 #[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 #[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 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 type Variant = ExtensionVariant<ExtV0, ExtV2>;
334
335 #[test]
336 fn decode_v0() {
337 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 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 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 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 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 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 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}