use crate::methods::extrinsic_type_info::ExtrinsicInfoError;
use crate::methods::extrinsic_type_info::ExtrinsicTypeInfo;
use crate::utils::{DecodeErrorTrace, decode_with_error_tracing};
use alloc::borrow::Cow;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use core::ops::Range;
use parity_scale_codec::{Compact, Decode};
use scale_type_resolver::TypeResolver;
#[non_exhaustive]
#[allow(missing_docs)]
#[derive(Debug, Clone, thiserror::Error)]
pub enum ExtrinsicDecodeError {
#[error("Cannot decode the compact-encoded extrinsic length.")]
CannotDecodeLength,
#[error(
"The actual number of bytes does not match the compact-encoded extrinsic length; expected {expected_len} bytes but got {actual_len} bytes."
)]
WrongLength {
expected_len: usize,
actual_len: usize,
},
#[error("Not enough bytes to decode a valid extrinsic.")]
NotEnoughBytes,
#[error("This extrinsic version ({0}) is not supported.")]
VersionNotSupported(u8),
#[error(
"The extrinsic type 0b{extrinsic_type:02b} is not supported (given extrinsic version {version})."
)]
ExtrinsicTypeNotSupported { version: u8, extrinsic_type: u8 },
#[error("Cannot get extrinsic info:\n\n{0}")]
CannotGetInfo(ExtrinsicInfoError<'static>),
#[error("Cannot decode signature:\n\n{0}")]
CannotDecodeSignature(DecodeErrorTrace),
#[error("Cannot decode pallet index byte:\n\n{0}")]
CannotDecodePalletIndex(parity_scale_codec::Error),
#[error("Cannot decode call index byte:\n\n{0}")]
CannotDecodeCallIndex(parity_scale_codec::Error),
#[error("Cannot decode transaction extensions version byte:\n\n{0}")]
CannotDecodeExtensionsVersion(parity_scale_codec::Error),
#[error(
"Cannot decode call data for argument {argument_name} in {pallet_name}.{call_name}:\n\n{reason}"
)]
CannotDecodeCallData {
pallet_name: String,
call_name: String,
argument_name: String,
reason: DecodeErrorTrace,
},
}
pub type ExtrinsicOwned<TypeId> = Extrinsic<'static, TypeId>;
#[derive(Clone, Debug)]
pub struct Extrinsic<'info, TypeId> {
compact_prefix_len: u8,
version: u8,
version_ty: ExtrinsicType,
byte_len: u32,
signature: Option<ExtrinsicSignature<TypeId>>,
extensions: Option<ExtrinsicExtensions<'info, TypeId>>,
pallet_name: Cow<'info, str>,
pallet_index: u8,
pallet_index_idx: u32,
call_name: Cow<'info, str>,
call_index: u8,
call_data: Vec<NamedArg<'info, TypeId>>,
}
impl<'info, TypeId> Extrinsic<'info, TypeId> {
pub fn into_owned(self) -> ExtrinsicOwned<TypeId> {
Extrinsic {
compact_prefix_len: self.compact_prefix_len,
version: self.version,
version_ty: self.version_ty,
byte_len: self.byte_len,
signature: self.signature,
extensions: self.extensions.map(|e| e.into_owned()),
pallet_name: Cow::Owned(self.pallet_name.into_owned()),
pallet_index: self.pallet_index,
pallet_index_idx: self.pallet_index_idx,
call_name: Cow::Owned(self.call_name.into_owned()),
call_index: self.call_index,
call_data: self.call_data.into_iter().map(|e| e.into_owned()).collect(),
}
}
pub fn version(&self) -> u8 {
self.version
}
pub fn ty(&self) -> ExtrinsicType {
self.version_ty
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.byte_len as usize
}
pub fn pallet_name(&self) -> &str {
&self.pallet_name
}
pub fn pallet_index(&self) -> u8 {
self.pallet_index
}
pub fn call_name(&self) -> &str {
&self.call_name
}
pub fn call_index(&self) -> u8 {
self.call_index
}
pub fn is_signed(&self) -> bool {
self.signature.is_some()
}
pub fn signature_payload(&self) -> Option<&ExtrinsicSignature<TypeId>> {
self.signature.as_ref()
}
pub fn transaction_extension_payload(&self) -> Option<&ExtrinsicExtensions<'info, TypeId>> {
self.extensions.as_ref()
}
pub fn call_data(&self) -> impl ExactSizeIterator<Item = &NamedArg<'info, TypeId>> {
self.call_data.iter()
}
pub fn call_data_range(&self) -> Range<usize> {
let start = self.pallet_index_idx as usize;
let end = self
.call_data()
.map(|a| a.range.end as usize)
.max()
.unwrap_or(start);
Range { start, end }
}
pub fn call_data_args_range(&self) -> Range<usize> {
let start = (self.pallet_index_idx + 2) as usize;
let end = self
.call_data()
.map(|a| a.range.end as usize)
.max()
.unwrap_or(start);
Range { start, end }
}
pub fn map_type_id<NewTypeId, F>(self, mut f: F) -> Extrinsic<'info, NewTypeId>
where
F: FnMut(TypeId) -> NewTypeId,
{
Extrinsic {
compact_prefix_len: self.compact_prefix_len,
version: self.version,
version_ty: self.version_ty,
byte_len: self.byte_len,
signature: self.signature.map(|s| s.map_type_id(&mut f)),
extensions: self.extensions.map(|e| e.map_type_id(&mut f)),
pallet_name: self.pallet_name,
pallet_index: self.pallet_index,
pallet_index_idx: self.pallet_index_idx,
call_name: self.call_name,
call_index: self.call_index,
call_data: self
.call_data
.into_iter()
.map(|s| s.map_type_id(&mut f))
.collect(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum ExtrinsicType {
Bare,
General,
Signed,
}
#[derive(Clone, Debug)]
pub struct ExtrinsicSignature<TypeId> {
address_start_idx: u32,
address_end_idx: u32,
signature_end_idx: u32,
address_ty: TypeId,
signature_ty: TypeId,
}
impl<TypeId> ExtrinsicSignature<TypeId> {
pub fn address_range(&self) -> Range<usize> {
Range {
start: self.address_start_idx as usize,
end: self.address_end_idx as usize,
}
}
pub fn address_type(&self) -> &TypeId {
&self.address_ty
}
pub fn signature_range(&self) -> Range<usize> {
Range {
start: self.address_end_idx as usize,
end: self.signature_end_idx as usize,
}
}
pub fn signature_type(&self) -> &TypeId {
&self.signature_ty
}
pub fn map_type_id<NewTypeId, F>(self, mut f: F) -> ExtrinsicSignature<NewTypeId>
where
F: FnMut(TypeId) -> NewTypeId,
{
ExtrinsicSignature {
address_start_idx: self.address_start_idx,
address_end_idx: self.address_end_idx,
signature_end_idx: self.signature_end_idx,
address_ty: f(self.address_ty),
signature_ty: f(self.signature_ty),
}
}
}
#[derive(Clone, Debug)]
pub struct ExtrinsicExtensions<'info, TypeId> {
transaction_extensions_version: u8,
transaction_extensions: Vec<NamedArg<'info, TypeId>>,
}
impl<'info, TypeId> ExtrinsicExtensions<'info, TypeId> {
pub fn into_owned(self) -> ExtrinsicExtensions<'static, TypeId> {
ExtrinsicExtensions {
transaction_extensions_version: self.transaction_extensions_version,
transaction_extensions: self
.transaction_extensions
.into_iter()
.map(|e| e.into_owned())
.collect(),
}
}
pub fn version(&self) -> u8 {
self.transaction_extensions_version
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = &NamedArg<'info, TypeId>> {
self.transaction_extensions.iter()
}
pub fn range(&self) -> Range<usize> {
let start = self
.iter()
.map(|a| a.range.start as usize)
.min()
.unwrap_or(0);
let end = self
.iter()
.map(|a| a.range.end as usize)
.max()
.unwrap_or(start);
Range { start, end }
}
pub fn map_type_id<NewTypeId, F>(self, mut f: F) -> ExtrinsicExtensions<'info, NewTypeId>
where
F: FnMut(TypeId) -> NewTypeId,
{
ExtrinsicExtensions {
transaction_extensions_version: self.transaction_extensions_version,
transaction_extensions: self
.transaction_extensions
.into_iter()
.map(|s| s.map_type_id(&mut f))
.collect(),
}
}
}
#[derive(Clone, Debug)]
pub struct NamedArg<'info, TypeId> {
name: Cow<'info, str>,
range: Range<u32>,
ty: TypeId,
}
impl<'info, TypeId> NamedArg<'info, TypeId> {
pub fn map_type_id<NewTypeId, F>(self, mut f: F) -> NamedArg<'info, NewTypeId>
where
F: FnMut(TypeId) -> NewTypeId,
{
NamedArg {
name: self.name,
range: self.range,
ty: f(self.ty),
}
}
}
impl<TypeId> NamedArg<'_, TypeId> {
pub fn into_owned(self) -> NamedArg<'static, TypeId> {
NamedArg {
name: Cow::Owned(self.name.into_owned()),
range: self.range,
ty: self.ty,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn range(&self) -> Range<usize> {
Range {
start: self.range.start as usize,
end: self.range.end as usize,
}
}
pub fn ty(&self) -> &TypeId {
&self.ty
}
}
pub fn decode_extrinsic<'info, Info, Resolver>(
cursor: &mut &[u8],
info: &'info Info,
type_resolver: &Resolver,
) -> Result<Extrinsic<'info, Info::TypeId>, ExtrinsicDecodeError>
where
Info: ExtrinsicTypeInfo,
Info::TypeId: core::fmt::Debug + Clone,
Resolver: TypeResolver<TypeId = Info::TypeId>,
{
let bytes = *cursor;
let ext_len = Compact::<u64>::decode(cursor)
.map_err(|_| ExtrinsicDecodeError::CannotDecodeLength)?
.0 as usize;
let compact_prefix_len = (bytes.len() - cursor.len()) as u8;
if cursor.len() != ext_len {
return Err(ExtrinsicDecodeError::WrongLength {
expected_len: ext_len,
actual_len: cursor.len(),
});
}
if cursor.is_empty() {
return Err(ExtrinsicDecodeError::NotEnoughBytes);
}
let version = cursor[0] & 0b0011_1111;
let version_type = cursor[0] >> 6;
*cursor = &cursor[1..];
if version != 4 && version != 5 {
return Err(ExtrinsicDecodeError::VersionNotSupported(version));
}
let version_ty = match version_type {
0b00 => ExtrinsicType::Bare,
0b10 if version == 4 => ExtrinsicType::Signed,
0b01 if version == 5 => ExtrinsicType::General,
_ => {
return Err(ExtrinsicDecodeError::ExtrinsicTypeNotSupported {
version,
extrinsic_type: version_type,
});
}
};
let curr_idx = |cursor: &mut &[u8]| (bytes.len() - cursor.len()) as u32;
let signature = (version_ty == ExtrinsicType::Signed)
.then(|| {
let signature_info = info
.extrinsic_signature_info()
.map_err(|e| ExtrinsicDecodeError::CannotGetInfo(e.into_owned()))?;
let address_start_idx = curr_idx(cursor);
decode_with_error_tracing(
cursor,
signature_info.address_id.clone(),
type_resolver,
scale_decode::visitor::IgnoreVisitor::new(),
)
.map_err(ExtrinsicDecodeError::CannotDecodeSignature)?;
let address_end_idx = curr_idx(cursor);
decode_with_error_tracing(
cursor,
signature_info.signature_id.clone(),
type_resolver,
scale_decode::visitor::IgnoreVisitor::new(),
)
.map_err(ExtrinsicDecodeError::CannotDecodeSignature)?;
let signature_end_idx = curr_idx(cursor);
Ok(ExtrinsicSignature {
address_start_idx,
address_end_idx,
signature_end_idx,
address_ty: signature_info.address_id,
signature_ty: signature_info.signature_id,
})
})
.transpose()?;
let extension_version = (version_ty == ExtrinsicType::General)
.then(|| u8::decode(cursor).map_err(ExtrinsicDecodeError::CannotDecodeExtensionsVersion))
.transpose()?;
let extensions = (version_ty == ExtrinsicType::General || version_ty == ExtrinsicType::Signed)
.then(|| {
let extension_info = info
.extrinsic_extension_info(extension_version)
.map_err(|e| ExtrinsicDecodeError::CannotGetInfo(e.into_owned()))?;
let mut transaction_extensions = vec![];
for ext in extension_info.extension_ids {
let start_idx = curr_idx(cursor);
decode_with_error_tracing(
cursor,
ext.id.clone(),
type_resolver,
scale_decode::visitor::IgnoreVisitor::new(),
)
.map_err(ExtrinsicDecodeError::CannotDecodeSignature)?;
let end_idx = curr_idx(cursor);
transaction_extensions.push(NamedArg {
name: ext.name,
range: Range {
start: start_idx,
end: end_idx,
},
ty: ext.id,
});
}
Ok::<_, ExtrinsicDecodeError>(ExtrinsicExtensions {
transaction_extensions_version: extension_version.unwrap_or(0),
transaction_extensions,
})
})
.transpose()?;
let pallet_index_idx = curr_idx(cursor);
let pallet_index: u8 =
Decode::decode(cursor).map_err(ExtrinsicDecodeError::CannotDecodePalletIndex)?;
let call_index: u8 =
Decode::decode(cursor).map_err(ExtrinsicDecodeError::CannotDecodeCallIndex)?;
let call_info = info
.extrinsic_call_info_by_index(pallet_index, call_index)
.map_err(|e| ExtrinsicDecodeError::CannotGetInfo(e.into_owned()))?;
let mut call_data = vec![];
for arg in call_info.args {
let start_idx = curr_idx(cursor);
decode_with_error_tracing(
cursor,
arg.id.clone(),
type_resolver,
scale_decode::visitor::IgnoreVisitor::new(),
)
.map_err(|e| ExtrinsicDecodeError::CannotDecodeCallData {
pallet_name: call_info.pallet_name.to_string(),
call_name: call_info.call_name.to_string(),
argument_name: arg.name.to_string(),
reason: e,
})?;
let end_idx = curr_idx(cursor);
call_data.push(NamedArg {
name: arg.name,
range: Range {
start: start_idx,
end: end_idx,
},
ty: arg.id,
})
}
let ext = Extrinsic {
compact_prefix_len,
version,
version_ty,
byte_len: bytes.len() as u32,
signature,
extensions,
pallet_name: call_info.pallet_name,
pallet_index,
pallet_index_idx,
call_name: call_info.call_name,
call_index,
call_data,
};
Ok(ext)
}