frame_decode/methods/
extrinsic_decoder.rs

1// Copyright (C) 2022-2025 Parity Technologies (UK) Ltd. (admin@parity.io)
2// This file is a part of the frame-decode crate.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//         http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use crate::methods::extrinsic_type_info::ExtrinsicInfoError;
17use crate::methods::extrinsic_type_info::ExtrinsicTypeInfo;
18use crate::utils::{DecodeErrorTrace, decode_with_error_tracing};
19use alloc::borrow::Cow;
20use alloc::string::{String, ToString};
21use alloc::vec;
22use alloc::vec::Vec;
23use core::ops::Range;
24use parity_scale_codec::{Compact, Decode};
25use scale_type_resolver::TypeResolver;
26
27/// An error returned trying to decode extrinsic bytes.
28#[non_exhaustive]
29#[allow(missing_docs)]
30#[derive(Debug, Clone, thiserror::Error)]
31pub enum ExtrinsicDecodeError {
32    #[error("Cannot decode the compact-encoded extrinsic length.")]
33    CannotDecodeLength,
34    #[error(
35        "The actual number of bytes does not match the compact-encoded extrinsic length; expected {expected_len} bytes but got {actual_len} bytes."
36    )]
37    WrongLength {
38        expected_len: usize,
39        actual_len: usize,
40    },
41    #[error("Not enough bytes to decode a valid extrinsic.")]
42    NotEnoughBytes,
43    #[error("This extrinsic version ({0}) is not supported.")]
44    VersionNotSupported(u8),
45    #[error(
46        "The extrinsic type 0b{extrinsic_type:02b} is not supported (given extrinsic version {version})."
47    )]
48    ExtrinsicTypeNotSupported { version: u8, extrinsic_type: u8 },
49    #[error("Cannot get extrinsic info:\n\n{0}")]
50    CannotGetInfo(ExtrinsicInfoError<'static>),
51    #[error("Cannot decode signature:\n\n{0}")]
52    CannotDecodeSignature(DecodeErrorTrace),
53    #[error("Cannot decode pallet index byte:\n\n{0}")]
54    CannotDecodePalletIndex(parity_scale_codec::Error),
55    #[error("Cannot decode call index byte:\n\n{0}")]
56    CannotDecodeCallIndex(parity_scale_codec::Error),
57    #[error("Cannot decode transaction extensions version byte:\n\n{0}")]
58    CannotDecodeExtensionsVersion(parity_scale_codec::Error),
59    #[error(
60        "Cannot decode call data for argument {argument_name} in {pallet_name}.{call_name}:\n\n{reason}"
61    )]
62    CannotDecodeCallData {
63        pallet_name: String,
64        call_name: String,
65        argument_name: String,
66        reason: DecodeErrorTrace,
67    },
68}
69
70/// An owned variant of an Extrinsic (note: this may still contain
71/// references if the visitor used to decode the extrinsic contents holds
72/// onto any)
73pub type ExtrinsicOwned<TypeId> = Extrinsic<'static, TypeId>;
74
75/// Information about the extrinsic.
76#[derive(Clone, Debug)]
77pub struct Extrinsic<'info, TypeId> {
78    compact_prefix_len: u8,
79    version: u8,
80    version_ty: ExtrinsicType,
81    byte_len: u32,
82    signature: Option<ExtrinsicSignature<TypeId>>,
83    extensions: Option<ExtrinsicExtensions<'info, TypeId>>,
84    pallet_name: Cow<'info, str>,
85    pallet_index: u8,
86    pallet_index_idx: u32,
87    call_name: Cow<'info, str>,
88    call_index: u8,
89    call_data: Vec<NamedArg<'info, TypeId>>,
90}
91
92impl<'info, TypeId> Extrinsic<'info, TypeId> {
93    /// Take ownership of the extrinsic, so that it no longer references
94    /// the extrinsic info or bytes.
95    pub fn into_owned(self) -> ExtrinsicOwned<TypeId> {
96        Extrinsic {
97            compact_prefix_len: self.compact_prefix_len,
98            version: self.version,
99            version_ty: self.version_ty,
100            byte_len: self.byte_len,
101            signature: self.signature,
102            extensions: self.extensions.map(|e| e.into_owned()),
103            pallet_name: Cow::Owned(self.pallet_name.into_owned()),
104            pallet_index: self.pallet_index,
105            pallet_index_idx: self.pallet_index_idx,
106            call_name: Cow::Owned(self.call_name.into_owned()),
107            call_index: self.call_index,
108            call_data: self.call_data.into_iter().map(|e| e.into_owned()).collect(),
109        }
110    }
111
112    /// The extrinsic version.
113    pub fn version(&self) -> u8 {
114        self.version
115    }
116
117    /// The type of the extrinsic.
118    pub fn ty(&self) -> ExtrinsicType {
119        self.version_ty
120    }
121
122    /// The length of the extrinsic payload, excluding the prefixed compact-encoded length bytes.
123    #[allow(clippy::len_without_is_empty)]
124    pub fn len(&self) -> usize {
125        self.byte_len as usize
126    }
127
128    /// The name of the pallet that this extrinsic is calling into.
129    pub fn pallet_name(&self) -> &str {
130        &self.pallet_name
131    }
132
133    /// the index of the pallet that this extrinsic is calling into.
134    pub fn pallet_index(&self) -> u8 {
135        self.pallet_index
136    }
137
138    /// The name of the call that the extrinsic is making.
139    pub fn call_name(&self) -> &str {
140        &self.call_name
141    }
142
143    /// the index of the call that the extrinsic is making.
144    pub fn call_index(&self) -> u8 {
145        self.call_index
146    }
147
148    /// Does the extrinsic have a signature.
149    pub fn is_signed(&self) -> bool {
150        self.signature.is_some()
151    }
152
153    /// Return the extrinsic signature payload, if present. This contains the
154    /// address and signature information.
155    pub fn signature_payload(&self) -> Option<&ExtrinsicSignature<TypeId>> {
156        self.signature.as_ref()
157    }
158
159    /// Return the transaction extension payload, if present. This contains the
160    /// transaction extensions.
161    pub fn transaction_extension_payload(&self) -> Option<&ExtrinsicExtensions<'info, TypeId>> {
162        self.extensions.as_ref()
163    }
164
165    /// Iterate over the call data argument names and types.
166    pub fn call_data(&self) -> impl ExactSizeIterator<Item = &NamedArg<'info, TypeId>> {
167        self.call_data.iter()
168    }
169
170    /// Return a range denoting the call data bytes. This includes the pallet index and
171    /// call index bytes and then any encoded arguments for the call.
172    pub fn call_data_range(&self) -> Range<usize> {
173        let start = self.pallet_index_idx as usize;
174        let end = self
175            .call_data()
176            .map(|a| a.range.end as usize)
177            .max()
178            .unwrap_or(start);
179
180        Range { start, end }
181    }
182
183    /// Return a range denoting the arguments given to the call. This does *not* include
184    /// the pallet index and call index bytes.
185    pub fn call_data_args_range(&self) -> Range<usize> {
186        let start = (self.pallet_index_idx + 2) as usize;
187        let end = self
188            .call_data()
189            .map(|a| a.range.end as usize)
190            .max()
191            .unwrap_or(start);
192
193        Range { start, end }
194    }
195
196    /// Map the signature type IDs to something else.
197    pub fn map_type_id<NewTypeId, F>(self, mut f: F) -> Extrinsic<'info, NewTypeId>
198    where
199        F: FnMut(TypeId) -> NewTypeId,
200    {
201        Extrinsic {
202            compact_prefix_len: self.compact_prefix_len,
203            version: self.version,
204            version_ty: self.version_ty,
205            byte_len: self.byte_len,
206            signature: self.signature.map(|s| s.map_type_id(&mut f)),
207            extensions: self.extensions.map(|e| e.map_type_id(&mut f)),
208            pallet_name: self.pallet_name,
209            pallet_index: self.pallet_index,
210            pallet_index_idx: self.pallet_index_idx,
211            call_name: self.call_name,
212            call_index: self.call_index,
213            call_data: self
214                .call_data
215                .into_iter()
216                .map(|s| s.map_type_id(&mut f))
217                .collect(),
218        }
219    }
220}
221
222/// The type of the extrinsic.
223#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
224pub enum ExtrinsicType {
225    /// Only call data
226    Bare,
227    /// Transaction extensions and call data
228    General,
229    /// Address, signature, transaction extensions and call data
230    Signed,
231}
232
233/// Information about the extrinsic signature.
234#[derive(Clone, Debug)]
235pub struct ExtrinsicSignature<TypeId> {
236    // Store byte offsets so people can ask for raw
237    // bytes to do their own thing.
238    address_start_idx: u32,
239    address_end_idx: u32,
240    signature_end_idx: u32,
241    // Also decode and store actual values. We could
242    // do this more "on demand" but it complicates
243    // everything. Ultimately just a couple of vec allocs
244    // that we could perhaps optimise into SmallVecs or
245    // something if desired.
246    address_ty: TypeId,
247    signature_ty: TypeId,
248}
249
250impl<TypeId> ExtrinsicSignature<TypeId> {
251    /// Return a range denoting the address bytes.
252    pub fn address_range(&self) -> Range<usize> {
253        Range {
254            start: self.address_start_idx as usize,
255            end: self.address_end_idx as usize,
256        }
257    }
258
259    /// The decoded address.
260    pub fn address_type(&self) -> &TypeId {
261        &self.address_ty
262    }
263
264    /// Return a range denoting the signature bytes.
265    pub fn signature_range(&self) -> Range<usize> {
266        Range {
267            start: self.address_end_idx as usize,
268            end: self.signature_end_idx as usize,
269        }
270    }
271
272    /// The decoded signature.
273    pub fn signature_type(&self) -> &TypeId {
274        &self.signature_ty
275    }
276
277    /// Map the signature type IDs to something else.
278    pub fn map_type_id<NewTypeId, F>(self, mut f: F) -> ExtrinsicSignature<NewTypeId>
279    where
280        F: FnMut(TypeId) -> NewTypeId,
281    {
282        ExtrinsicSignature {
283            address_start_idx: self.address_start_idx,
284            address_end_idx: self.address_end_idx,
285            signature_end_idx: self.signature_end_idx,
286            address_ty: f(self.address_ty),
287            signature_ty: f(self.signature_ty),
288        }
289    }
290}
291
292/// Information about the extrinsic signed extensions.
293#[derive(Clone, Debug)]
294pub struct ExtrinsicExtensions<'info, TypeId> {
295    transaction_extensions_version: u8,
296    transaction_extensions: Vec<NamedArg<'info, TypeId>>,
297}
298
299impl<'info, TypeId> ExtrinsicExtensions<'info, TypeId> {
300    /// Take ownership of the signature.
301    pub fn into_owned(self) -> ExtrinsicExtensions<'static, TypeId> {
302        ExtrinsicExtensions {
303            transaction_extensions_version: self.transaction_extensions_version,
304            transaction_extensions: self
305                .transaction_extensions
306                .into_iter()
307                .map(|e| e.into_owned())
308                .collect(),
309        }
310    }
311
312    /// The version of the transaction extensions
313    pub fn version(&self) -> u8 {
314        self.transaction_extensions_version
315    }
316
317    /// Iterate over the signed extension argument names and types.
318    pub fn iter(&self) -> impl ExactSizeIterator<Item = &NamedArg<'info, TypeId>> {
319        self.transaction_extensions.iter()
320    }
321
322    /// Return a range denoting the transaction extension bytes. This does
323    /// *not* include any version byte.
324    pub fn range(&self) -> Range<usize> {
325        let start = self
326            .iter()
327            .map(|a| a.range.start as usize)
328            .min()
329            .unwrap_or(0);
330        let end = self
331            .iter()
332            .map(|a| a.range.end as usize)
333            .max()
334            .unwrap_or(start);
335
336        Range { start, end }
337    }
338
339    /// Map the extensions type IDs to something else.
340    pub fn map_type_id<NewTypeId, F>(self, mut f: F) -> ExtrinsicExtensions<'info, NewTypeId>
341    where
342        F: FnMut(TypeId) -> NewTypeId,
343    {
344        ExtrinsicExtensions {
345            transaction_extensions_version: self.transaction_extensions_version,
346            transaction_extensions: self
347                .transaction_extensions
348                .into_iter()
349                .map(|s| s.map_type_id(&mut f))
350                .collect(),
351        }
352    }
353}
354
355/// A single named argument.
356#[derive(Clone, Debug)]
357pub struct NamedArg<'info, TypeId> {
358    name: Cow<'info, str>,
359    range: Range<u32>,
360    ty: TypeId,
361}
362
363impl<'info, TypeId> NamedArg<'info, TypeId> {
364    /// Map the type ID to something else.
365    pub fn map_type_id<NewTypeId, F>(self, mut f: F) -> NamedArg<'info, NewTypeId>
366    where
367        F: FnMut(TypeId) -> NewTypeId,
368    {
369        NamedArg {
370            name: self.name,
371            range: self.range,
372            ty: f(self.ty),
373        }
374    }
375}
376
377impl<TypeId> NamedArg<'_, TypeId> {
378    /// Take ownership of this named argument.
379    pub fn into_owned(self) -> NamedArg<'static, TypeId> {
380        NamedArg {
381            name: Cow::Owned(self.name.into_owned()),
382            range: self.range,
383            ty: self.ty,
384        }
385    }
386
387    /// The name of this argument.
388    pub fn name(&self) -> &str {
389        &self.name
390    }
391
392    /// Return a range denoting the bytes associated with this argument value.
393    pub fn range(&self) -> Range<usize> {
394        Range {
395            start: self.range.start as usize,
396            end: self.range.end as usize,
397        }
398    }
399
400    /// The type ID associated with this argument value.
401    pub fn ty(&self) -> &TypeId {
402        &self.ty
403    }
404}
405
406/// Decode an extrinsic, returning information about it.
407///
408/// This information can be used to then decode the various parts of the extrinsic (address,
409/// signature, transaction extensions and call data) to concrete types.
410///
411/// # Example
412///
413/// Here, we decode all of the extrinsics in a block to a [`scale_value::Value`] type.
414///
415/// ```rust
416/// use frame_decode::extrinsics::decode_extrinsic;
417/// use frame_decode::helpers::decode_with_visitor;
418/// use frame_metadata::RuntimeMetadata;
419/// use parity_scale_codec::Decode;
420/// use scale_value::scale::ValueVisitor;
421///
422/// let metadata_bytes = std::fs::read("artifacts/metadata_10000000_9180.scale").unwrap();
423/// let RuntimeMetadata::V14(metadata) = RuntimeMetadata::decode(&mut &*metadata_bytes).unwrap() else { return };
424///
425/// let extrinsics_bytes = std::fs::read("artifacts/exts_10000000_9180.json").unwrap();
426/// let extrinsics_hex: Vec<String> = serde_json::from_slice(&extrinsics_bytes).unwrap();
427///
428/// for ext_hex in extrinsics_hex {
429///     let ext_bytes = hex::decode(ext_hex.trim_start_matches("0x")).unwrap();
430///
431///     // Decode the extrinsic, returning information about it:
432///     let ext_info = decode_extrinsic(&mut &*ext_bytes, &metadata, &metadata.types).unwrap();
433///
434///     // Decode the signature details to scale_value::Value's.
435///     if let Some(sig) = ext_info.signature_payload() {
436///         let address_bytes =  &ext_bytes[sig.address_range()];
437///         let address_value = decode_with_visitor(
438///             &mut &*address_bytes,
439///             *sig.address_type(),
440///             &metadata.types,
441///             ValueVisitor::new()
442///         ).unwrap();
443///
444///         let signature_bytes = &ext_bytes[sig.signature_range()];
445///         let signature_value = decode_with_visitor(
446///             &mut &*signature_bytes,
447///             *sig.signature_type(),
448///             &metadata.types,
449///             ValueVisitor::new()
450///         ).unwrap();
451///     }
452///
453///     // Decode the transaction extensions to scale_value::Value's.
454///     if let Some(exts) = ext_info.transaction_extension_payload() {
455///         for ext in exts.iter() {
456///             let ext_name = ext.name();
457///             let ext_bytes = &ext_bytes[ext.range()];
458///             let ext_value = decode_with_visitor(
459///                 &mut &*ext_bytes,
460///                 *ext.ty(),
461///                 &metadata.types,
462///                 ValueVisitor::new()
463///             ).unwrap();
464///         }
465///     }
466///
467///     // Decode the call data args to scale_value::Value's.
468///     for arg in ext_info.call_data() {
469///         let arg_name = arg.name();
470///         let arg_bytes = &ext_bytes[arg.range()];
471///         let arg_value = decode_with_visitor(
472///             &mut &*arg_bytes,
473///             *arg.ty(),
474///             &metadata.types,
475///             ValueVisitor::new()
476///         ).unwrap();
477///     }
478/// }
479/// ```
480pub fn decode_extrinsic<'info, Info, Resolver>(
481    cursor: &mut &[u8],
482    info: &'info Info,
483    type_resolver: &Resolver,
484) -> Result<Extrinsic<'info, Info::TypeId>, ExtrinsicDecodeError>
485where
486    Info: ExtrinsicTypeInfo,
487    Info::TypeId: core::fmt::Debug + Clone,
488    Resolver: TypeResolver<TypeId = Info::TypeId>,
489{
490    let bytes = *cursor;
491    let ext_len = Compact::<u64>::decode(cursor)
492        .map_err(|_| ExtrinsicDecodeError::CannotDecodeLength)?
493        .0 as usize;
494
495    let compact_prefix_len = (bytes.len() - cursor.len()) as u8;
496
497    if cursor.len() != ext_len {
498        return Err(ExtrinsicDecodeError::WrongLength {
499            expected_len: ext_len,
500            actual_len: cursor.len(),
501        });
502    }
503
504    if cursor.is_empty() {
505        return Err(ExtrinsicDecodeError::NotEnoughBytes);
506    }
507
508    // Decide how to decode the extrinsic based on the version.
509    // As of https://github.com/paritytech/polkadot-sdk/pull/3685,
510    // only 6 bits used for version. Shouldn't break old impls.
511    let version = cursor[0] & 0b0011_1111;
512    let version_type = cursor[0] >> 6;
513    *cursor = &cursor[1..];
514
515    // We only know how to decode v4 and v5 extrinsics.
516    if version != 4 && version != 5 {
517        return Err(ExtrinsicDecodeError::VersionNotSupported(version));
518    }
519
520    // We know about the following types of extrinsics:
521    // - "Bare": no signature or extensions. V4 inherents encode the same as V5 bare.
522    // - "Signed": an address, signature and extensions. Only exists in V4.
523    // - "General": no signature, just extensions (one of which can include sig). Only exists in V5.
524    let version_ty = match version_type {
525        0b00 => ExtrinsicType::Bare,
526        0b10 if version == 4 => ExtrinsicType::Signed,
527        0b01 if version == 5 => ExtrinsicType::General,
528        _ => {
529            return Err(ExtrinsicDecodeError::ExtrinsicTypeNotSupported {
530                version,
531                extrinsic_type: version_type,
532            });
533        }
534    };
535
536    let curr_idx = |cursor: &mut &[u8]| (bytes.len() - cursor.len()) as u32;
537
538    // Signature part. Present for V4 signed extrinsics
539    let signature = (version_ty == ExtrinsicType::Signed)
540        .then(|| {
541            let signature_info = info
542                .extrinsic_signature_info()
543                .map_err(|e| ExtrinsicDecodeError::CannotGetInfo(e.into_owned()))?;
544
545            let address_start_idx = curr_idx(cursor);
546            decode_with_error_tracing(
547                cursor,
548                signature_info.address_id.clone(),
549                type_resolver,
550                scale_decode::visitor::IgnoreVisitor::new(),
551            )
552            .map_err(ExtrinsicDecodeError::CannotDecodeSignature)?;
553            let address_end_idx = curr_idx(cursor);
554
555            decode_with_error_tracing(
556                cursor,
557                signature_info.signature_id.clone(),
558                type_resolver,
559                scale_decode::visitor::IgnoreVisitor::new(),
560            )
561            .map_err(ExtrinsicDecodeError::CannotDecodeSignature)?;
562            let signature_end_idx = curr_idx(cursor);
563
564            Ok(ExtrinsicSignature {
565                address_start_idx,
566                address_end_idx,
567                signature_end_idx,
568                address_ty: signature_info.address_id,
569                signature_ty: signature_info.signature_id,
570            })
571        })
572        .transpose()?;
573
574    // "General" extensions now have a single byte representing the extension version.
575    let extension_version = (version_ty == ExtrinsicType::General)
576        .then(|| u8::decode(cursor).map_err(ExtrinsicDecodeError::CannotDecodeExtensionsVersion))
577        .transpose()?;
578
579    // Signed and General extrinsics both now have a set of transaction extensions.
580    let extensions = (version_ty == ExtrinsicType::General || version_ty == ExtrinsicType::Signed)
581        .then(|| {
582            let extension_info = info
583                .extrinsic_extension_info(extension_version)
584                .map_err(|e| ExtrinsicDecodeError::CannotGetInfo(e.into_owned()))?;
585
586            let mut transaction_extensions = vec![];
587            for ext in extension_info.extension_ids {
588                let start_idx = curr_idx(cursor);
589                decode_with_error_tracing(
590                    cursor,
591                    ext.id.clone(),
592                    type_resolver,
593                    scale_decode::visitor::IgnoreVisitor::new(),
594                )
595                .map_err(ExtrinsicDecodeError::CannotDecodeSignature)?;
596                let end_idx = curr_idx(cursor);
597
598                transaction_extensions.push(NamedArg {
599                    name: ext.name,
600                    range: Range {
601                        start: start_idx,
602                        end: end_idx,
603                    },
604                    ty: ext.id,
605                });
606            }
607
608            Ok::<_, ExtrinsicDecodeError>(ExtrinsicExtensions {
609                transaction_extensions_version: extension_version.unwrap_or(0),
610                transaction_extensions,
611            })
612        })
613        .transpose()?;
614
615    // All extrinsics now have the encoded call data.
616    let pallet_index_idx = curr_idx(cursor);
617    let pallet_index: u8 =
618        Decode::decode(cursor).map_err(ExtrinsicDecodeError::CannotDecodePalletIndex)?;
619    let call_index: u8 =
620        Decode::decode(cursor).map_err(ExtrinsicDecodeError::CannotDecodeCallIndex)?;
621    let call_info = info
622        .extrinsic_call_info(pallet_index, call_index)
623        .map_err(|e| ExtrinsicDecodeError::CannotGetInfo(e.into_owned()))?;
624
625    let mut call_data = vec![];
626    for arg in call_info.args {
627        let start_idx = curr_idx(cursor);
628        decode_with_error_tracing(
629            cursor,
630            arg.id.clone(),
631            type_resolver,
632            scale_decode::visitor::IgnoreVisitor::new(),
633        )
634        .map_err(|e| ExtrinsicDecodeError::CannotDecodeCallData {
635            pallet_name: call_info.pallet_name.to_string(),
636            call_name: call_info.call_name.to_string(),
637            argument_name: arg.name.to_string(),
638            reason: e,
639        })?;
640        let end_idx = curr_idx(cursor);
641
642        call_data.push(NamedArg {
643            name: arg.name,
644            range: Range {
645                start: start_idx,
646                end: end_idx,
647            },
648            ty: arg.id,
649        })
650    }
651
652    let ext = Extrinsic {
653        compact_prefix_len,
654        version,
655        version_ty,
656        byte_len: bytes.len() as u32,
657        signature,
658        extensions,
659        pallet_name: call_info.pallet_name,
660        pallet_index,
661        pallet_index_idx,
662        call_name: call_info.call_name,
663        call_index,
664        call_data,
665    };
666
667    Ok(ext)
668}