diff --git a/src/cards.rs b/src/cards.rs index 8d873fd..a0726c6 100644 --- a/src/cards.rs +++ b/src/cards.rs @@ -59,14 +59,7 @@ impl Info { if self.path.is_empty() { None } else { - let mut collect = String::new(); - for (i, x) in self.path.segments().iter().enumerate() { - if i > 0 { - collect.push_str(" >> "); - } - collect.push_str(x); - } - Some(collect) + Some(self.path.segments().join(" >> ")) } }; InfoFlat { docs, path_flat } @@ -85,12 +78,7 @@ macro_rules! impl_documented { $( impl Documented for $ty { fn collect_docs(&self) -> String { - let mut docs = String::new(); - for (i, docs_line) in self.docs().iter().enumerate() { - if i > 0 {docs.push('\n')} - docs.push_str(docs_line); - } - docs + self.docs().join("\n") } } )* @@ -106,11 +94,11 @@ impl_documented!( /// Parsed data and collected relevant type information. #[derive(Clone, Debug, Eq, PartialEq)] pub struct ExtendedData { - /// All non-empty `Info` encountered while resolving the type. - pub info: Vec, - /// Parsed data, nested. pub data: ParsedData, + + /// All non-empty `Info` encountered while resolving the type. + pub info: Vec, } /// Parsed data for [`PalletSpecificItem`]. @@ -123,11 +111,11 @@ pub struct PalletSpecificData { pub fields: Vec, } -/// Call parsed data. +/// Parsed Call data. Nested. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Call(pub PalletSpecificData); -/// Event parsed data. +/// Parsed Event data. Nested. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Event(pub PalletSpecificData); @@ -143,6 +131,9 @@ impl PalletSpecificData { fn is_balance_display(&self) -> bool { PALLETS_BALANCE_VALID.contains(&self.pallet_name.as_str()) } + + /// Transform `PalletSpecificData` into a set of flat formatted + /// [`ExtendedCard`]s. fn card( &self, indent: u32, @@ -188,17 +179,20 @@ impl PalletSpecificData { } impl Call { + /// Transform `Call` into a set of flat formatted [`ExtendedCard`]s. pub fn card(&self, indent: u32, short_specs: &ShortSpecs) -> Vec { self.0.card(indent, short_specs, PalletSpecificItem::Call) } } impl Event { + /// Transform `Event` into a set of flat formatted [`ExtendedCard`]s. pub fn card(&self, indent: u32, short_specs: &ShortSpecs) -> Vec { self.0.card(indent, short_specs, PalletSpecificItem::Event) } } +/// Parsed data for a [`Field`]. #[derive(Clone, Debug, Eq, PartialEq)] pub struct FieldData { pub field_name: Option, @@ -207,6 +201,7 @@ pub struct FieldData { pub data: ExtendedData, } +/// Parsed data for a [`Variant`]. #[derive(Clone, Debug, Eq, PartialEq)] pub struct VariantData { pub variant_name: String, @@ -214,20 +209,28 @@ pub struct VariantData { pub fields: Vec, } -/// For both vectors and arrays +/// Parsed data for a sequence. #[derive(Clone, Debug, Eq, PartialEq)] pub struct SequenceRawData { - pub element_info: Vec, // info associated with every `ParsedData` + /// [`Info`] associated with every [`ParsedData`] in the sequence. + pub element_info: Vec, + + /// [`ParsedData`] set. Note that all associated [`Info`] is in + /// `element_info`. pub data: Vec, } -/// For both vectors and arrays +/// Parsed data for a wrapped sequence. #[derive(Clone, Debug, Eq, PartialEq)] pub struct SequenceData { - pub element_info: Vec, // info associated with every element of sequence + /// [`Info`] associated with every element of the [`Sequence`]. + pub element_info: Vec, + + /// `Vec` wrapped into `Sequence`. pub data: Sequence, } +/// Wrapped sequence. #[derive(Clone, Debug, Eq, PartialEq)] pub enum Sequence { U8(Vec), @@ -236,11 +239,15 @@ pub enum Sequence { U64(Vec), U128(Vec), VecU8 { + /// Sequence itself. sequence: Vec>, + + /// [`Info`] for individual `u8`. inner_element_info: Vec, }, } +/// Parsed data variants. As many types as possible are preserved. #[derive(Clone, Debug, Eq, PartialEq)] pub enum ParsedData { BitVecU8Lsb0(BitVec), @@ -309,6 +316,7 @@ pub enum ParsedData { Variant(VariantData), } +/// Transform [`ParsedData`] into single-element `Vec`. macro_rules! single_card { ($variant:ident, $value:tt, $indent:tt, $info_flat:tt) => { vec![ExtendedCard { @@ -319,6 +327,8 @@ macro_rules! single_card { }; } +/// Transform [`ParsedData`] into single-element `Vec` for types +/// supporting [`SpecialtyPrimitive`]. macro_rules! specialty_card { ($ty:ty, $variant:ident, $value:tt, $display_balance:tt, $indent:tt, $info_flat:tt, $short_specs:tt, $specialty:tt) => { vec![ExtendedCard { @@ -357,8 +367,10 @@ macro_rules! specialty_card { }; } +/// Transform [`Sequence`] into a vector of [`ExtendedCard`]s. macro_rules! sequence { ($func:ident, $ty:ty, $variant:ident) => { + /// Transform [`Sequence`] of `$ty` into a vector of [`ExtendedCard`]s. fn $func( set: &[$ty], indent: u32, @@ -391,6 +403,7 @@ sequence!(seq_u64, u64, PrimitiveU64); sequence!(seq_u128, u128, PrimitiveU128); impl ParsedData { + /// Transform `ParsedData` into a set of flat formatted [`ExtendedCard`]s. pub fn card( &self, info_flat: Vec, @@ -810,8 +823,6 @@ fn readable(indent: u32, card_type: &str, card_payload: &str) -> String { } /// Formatted and flat decoded data, ready to be displayed. -/// -/// I suppose it must have as simple fields as possible. To be fixed. #[derive(Clone, Debug, Eq, PartialEq)] pub struct ExtendedCard { pub parser_card: ParserCard, @@ -819,30 +830,34 @@ pub struct ExtendedCard { pub info_flat: Vec, } +/// Flat [`Type`] information. +/// +/// At least one of the fields is non-`None`. #[derive(Clone, Debug, Eq, PartialEq)] pub struct InfoFlat { pub docs: Option, pub path_flat: Option, } -impl InfoFlat { - pub fn print(&self) -> String { +impl std::fmt::Display for InfoFlat { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let docs_printed = self.docs.as_ref().map_or("None", |a| a); let path_printed = self.path_flat.as_ref().map_or("None", |a| a); - format!("(docs: {}, path: {})", docs_printed, path_printed) + write!(f, "(docs: {}, path: {})", docs_printed, path_printed) } } +/// Id-associated data. #[derive(Clone, Debug, Eq, PartialEq)] pub struct IdData { + /// Base58 address pub base58: String, + + /// Identicon `png` data pub identicon: Vec, } -/// Cards, flat and formatted, ready for display. -/// -/// Some cards always have associated additional info elements that go into `ExtendedCard`. -/// Some cards (`Sequence`) have info elements inside. +/// Flat cards content. #[derive(Clone, Debug, Eq, PartialEq)] pub enum ParserCard { Balance(Currency), @@ -916,6 +931,7 @@ pub enum ParserCard { } impl ExtendedData { + /// Transform `ExtendedData` into a set of flat formatted [`ExtendedCard`]s. pub fn card( &self, indent: u32, @@ -926,17 +942,18 @@ impl ExtendedData { self.data .card(info_flat, indent, display_balance, short_specs) } + + /// Display without associated type info. pub fn show(&self, indent: u32, display_balance: bool, short_specs: &ShortSpecs) -> String { let cards = self.card(indent, display_balance, short_specs); - let mut out = String::new(); - for (i, x) in cards.iter().enumerate() { - if i > 0 { - out.push('\n') - } - out.push_str(&x.show()) - } - out + cards + .iter() + .map(|a| a.show()) + .collect::>() + .join("\n") } + + /// Display with associated type info. pub fn show_with_docs( &self, indent: u32, @@ -944,18 +961,16 @@ impl ExtendedData { short_specs: &ShortSpecs, ) -> String { let cards = self.card(indent, display_balance, short_specs); - let mut out = String::new(); - for (i, x) in cards.iter().enumerate() { - if i > 0 { - out.push('\n') - } - out.push_str(&x.show_with_docs()) - } - out + cards + .iter() + .map(|a| a.show_with_docs()) + .collect::>() + .join("\n") } } impl ExtendedCard { + /// Display without associated type info. pub fn show(&self) -> String { match &self.parser_card { ParserCard::Balance(a) => { @@ -1076,6 +1091,7 @@ impl ExtendedCard { } } + /// Display with associated type info. pub fn show_with_docs(&self) -> String { let mut info_printed = String::new(); for info_flat in self.info_flat.iter() { @@ -1083,7 +1099,7 @@ impl ExtendedCard { info_printed, "\n{}{}", " ".repeat(self.indent as usize), - info_flat.print() + info_flat ); } let card_printed = match &self.parser_card { @@ -1091,20 +1107,21 @@ impl ExtendedCard { len, element_info_flat, } => { - let mut element_info_printed = String::new(); - for (i, info_flat) in element_info_flat.iter().enumerate() { - if i > 0 { - element_info_printed.push('\n') - } - element_info_printed.push_str(&info_flat.print()) - } + let element_info_printed = element_info_flat + .iter() + .map(|a| a.to_string()) + .collect::>() + .join(","); if element_info_printed.is_empty() { readable(self.indent, "Sequence", &format!("{} element(s)", len)) } else { readable( self.indent, "Sequence", - &format!("{} element(s), element info: {}", len, element_info_printed), + &format!( + "{} element(s), element info: [{}]", + len, element_info_printed + ), ) } } @@ -1113,13 +1130,11 @@ impl ExtendedCard { text, element_info_flat, } => { - let mut element_info_printed = String::new(); - for (i, info_flat) in element_info_flat.iter().enumerate() { - if i > 0 { - element_info_printed.push('\n') - } - element_info_printed.push_str(&info_flat.print()) - } + let element_info_printed = element_info_flat + .iter() + .map(|a| a.to_string()) + .collect::>() + .join(","); if element_info_printed.is_empty() { match text { Some(valid_text) => readable(self.indent, "Text", valid_text), @@ -1130,12 +1145,12 @@ impl ExtendedCard { Some(valid_text) => readable( self.indent, "Text", - &format!("{}, element info: {}", valid_text, element_info_printed), + &format!("{}, element info: [{}]", valid_text, element_info_printed), ), None => readable( self.indent, "Sequence u8", - &format!("{}, element info: {}", hex, element_info_printed), + &format!("{}, element info: [{}]", hex, element_info_printed), ), } } diff --git a/src/compacts.rs b/src/compacts.rs index f8a4245..40240e4 100644 --- a/src/compacts.rs +++ b/src/compacts.rs @@ -3,14 +3,20 @@ use parity_scale_codec::{Compact, Decode, HasCompact}; use crate::error::ParserError; -/// Struct to store results of searching Vec for encoded compact: -/// consists of actual number decoded, and, if it exists, the beginning position for data after the compact -pub struct CutCompact { - pub compact_found: T, +/// Compact found in data. +pub struct FoundCompact { + /// Compact found and decoded. + pub compact: T, + + /// Position of first data element after the compact part, if any. pub start_next_unit: Option, } -pub fn cut_compact(data: &[u8]) -> Result, ParserError> +/// Search `&[u8]` for compact by brute force. +/// +/// Tries to find shortest `[u8]` slice that could be decoded as a compact. +/// Does not modify the input. +pub fn find_compact(data: &[u8]) -> Result, ParserError> where T: HasCompact, Compact: Decode, @@ -30,8 +36,8 @@ where Some(i + 1) } }; - out = Some(CutCompact { - compact_found: hurray.0, + out = Some(FoundCompact { + compact: hurray.0, start_next_unit, }); break; @@ -43,17 +49,16 @@ where } } -/// Function to search &[u8] for shortest compact by brute force. -/// Outputs CutCompact value in case of success. -pub fn get_compact(data: &mut Vec) -> Result +/// Find compact and cut it from the input data. +pub(crate) fn get_compact(data: &mut Vec) -> Result where T: HasCompact, Compact: Decode, { - let cut_compact = cut_compact::(data)?; - *data = match cut_compact.start_next_unit { + let found_compact = find_compact::(data)?; + *data = match found_compact.start_next_unit { Some(start) => data[start..].to_vec(), None => Vec::new(), }; - Ok(cut_compact.compact_found) + Ok(found_compact.compact) } diff --git a/src/decoding_sci.rs b/src/decoding_sci.rs index effe1c0..ea787a7 100644 --- a/src/decoding_sci.rs +++ b/src/decoding_sci.rs @@ -8,73 +8,59 @@ use scale_info::{ TypeDefPrimitive, Variant, }; use sp_arithmetic::{PerU16, Perbill, Percent, Permill, Perquintill}; -use sp_core::{H160, H512}; +use sp_core::{crypto::AccountId32, H160, H512}; use crate::cards::{ Call, Documented, Event, ExtendedData, FieldData, Info, PalletSpecificData, ParsedData, SequenceData, SequenceRawData, VariantData, }; -use crate::compacts::{cut_compact, get_compact}; +use crate::compacts::{find_compact, get_compact}; use crate::error::{ParserError, SignableError}; use crate::propagated::{Checker, Propagated, SpecialtySet}; use crate::special_indicators::{ Hint, PalletSpecificItem, SpecialtyTypeChecked, SpecialtyTypeHinted, }; use crate::special_types::{ - special_case_account_id32, special_case_ecdsa_public, special_case_ecdsa_signature, - special_case_ed25519_public, special_case_ed25519_signature, special_case_era, - special_case_h256, special_case_sr25519_public, special_case_sr25519_signature, wrap_sequence, - SpecialArray, StLenCheckCompact, StLenCheckSpecialtyCompact, + special_case_era, special_case_h256, wrap_sequence, CheckCompact, UnsignedInteger, }; -/// Function to decode types that are variants of TypeDefPrimitive enum. +/// Finalize parsing of primitives (variants of [`TypeDefPrimitive`]). /// -/// The function decodes only given type found_ty, removes already decoded part of input data Vec, -/// and returns whatever remains as DecodedOut field remaining_vector, which is processed later separately. -/// -/// The function takes as arguments -/// - found_ty (TypeDefPrimitive, found in the previous iteration) -/// - data (remaining Vec of data), -/// -/// The function outputs the DecodedOut value in case of success. +/// Decoded data gets consumed. Propagated to this point [`SpecialtySet`] is +/// used. fn decode_type_def_primitive( found_ty: &TypeDefPrimitive, data: &mut Vec, specialty_set: SpecialtySet, ) -> Result { match found_ty { - TypeDefPrimitive::Bool => bool::decode_checked(data, specialty_set.is_compact), - TypeDefPrimitive::Char => char::decode_checked(data, specialty_set.is_compact), + TypeDefPrimitive::Bool => bool::parse_check_compact(data, specialty_set.is_compact), + TypeDefPrimitive::Char => char::parse_check_compact(data, specialty_set.is_compact), TypeDefPrimitive::Str => { specialty_set.reject_compact()?; decode_str(data) } - TypeDefPrimitive::U8 => u8::decode_checked(data, specialty_set), - TypeDefPrimitive::U16 => u16::decode_checked(data, specialty_set), - TypeDefPrimitive::U32 => u32::decode_checked(data, specialty_set), - TypeDefPrimitive::U64 => u64::decode_checked(data, specialty_set), - TypeDefPrimitive::U128 => u128::decode_checked(data, specialty_set), - TypeDefPrimitive::U256 => BigUint::decode_checked(data, specialty_set.is_compact), - TypeDefPrimitive::I8 => i8::decode_checked(data, specialty_set.is_compact), - TypeDefPrimitive::I16 => i16::decode_checked(data, specialty_set.is_compact), - TypeDefPrimitive::I32 => i32::decode_checked(data, specialty_set.is_compact), - TypeDefPrimitive::I64 => i64::decode_checked(data, specialty_set.is_compact), - TypeDefPrimitive::I128 => i128::decode_checked(data, specialty_set.is_compact), - TypeDefPrimitive::I256 => BigInt::decode_checked(data, specialty_set.is_compact), + TypeDefPrimitive::U8 => u8::parse_unsigned_integer(data, specialty_set), + TypeDefPrimitive::U16 => u16::parse_unsigned_integer(data, specialty_set), + TypeDefPrimitive::U32 => u32::parse_unsigned_integer(data, specialty_set), + TypeDefPrimitive::U64 => u64::parse_unsigned_integer(data, specialty_set), + TypeDefPrimitive::U128 => u128::parse_unsigned_integer(data, specialty_set), + TypeDefPrimitive::U256 => BigUint::parse_check_compact(data, specialty_set.is_compact), + TypeDefPrimitive::I8 => i8::parse_check_compact(data, specialty_set.is_compact), + TypeDefPrimitive::I16 => i16::parse_check_compact(data, specialty_set.is_compact), + TypeDefPrimitive::I32 => i32::parse_check_compact(data, specialty_set.is_compact), + TypeDefPrimitive::I64 => i64::parse_check_compact(data, specialty_set.is_compact), + TypeDefPrimitive::I128 => i128::parse_check_compact(data, specialty_set.is_compact), + TypeDefPrimitive::I256 => BigInt::parse_check_compact(data, specialty_set.is_compact), } } -/// Function to decode `str`. -/// `str` is encoded as a vector of utf-converteable elements, and is therefore -/// preluded by the number of elements as compact. +/// Decode `str`. /// -/// The function decodes only `str` part, removes already decoded part of input data Vec, -/// and returns whatever remains as DecodedOut field remaining_vector, which is processed later separately. +/// `str` is a `Vec` with utf-convertible elements, and is decoded as a +/// vector (compact of length precedes the data). /// -/// The function takes as arguments -/// - data (remaining Vec of data), -/// -/// The function outputs the DecodedOut value in case of success. +/// Decoded data gets consumed. fn decode_str(data: &mut Vec) -> Result { let str_length = get_compact::(data)? as usize; if !data.is_empty() { @@ -97,7 +83,17 @@ fn decode_str(data: &mut Vec) -> Result { } } -pub fn decode_as_call_v14( +/// Parse call data with provided `V14` metadata. +/// +/// Intended for call part of a signable transaction. +/// +/// Data is expected to be a call. The first `u8` element is a pallet index, +/// the type within corresponding `PalletCallMetadata` is expected to be an +/// enum with pallet-specific calls. If the pallet-call pattern is not observed, +/// an error occurs. +/// +/// Input data gets consumed during the decoding. +pub fn decode_as_call( data: &mut Vec, meta_v14: &RuntimeMetadataV14, ) -> Result { @@ -160,9 +156,10 @@ pub fn decode_as_call_v14( } } -/// Main decoder function. +/// General decoder function. Parse part of data as [`Ty`]. /// -/// Processes input data byte-by-byte, cutting and decoding data chunks. +/// Processes input data byte-by-byte, cutting and decoding data chunks. Input +/// data is consumed. /// /// This function is sometimes used recursively. Specifically, it could be /// called on inner element(s) when decoding deals with: @@ -207,16 +204,16 @@ pub fn decode_with_type( TypeDef::Composite(x) => { let field_data_set = decode_fields(x.fields(), data, meta_v14, propagated.checker)?; Ok(ExtendedData { - info: propagated.info, data: ParsedData::Composite(field_data_set), + info: propagated.info, }) } TypeDef::Variant(x) => { propagated.reject_compact()?; let variant_data = decode_variant(x.variants(), data, meta_v14)?; Ok(ExtendedData { - info: propagated.info, data: ParsedData::Variant(variant_data), + info: propagated.info, }) } TypeDef::Sequence(x) => { @@ -250,13 +247,13 @@ pub fn decode_with_type( tuple_data_set.push(tuple_data_element); } Ok(ExtendedData { - info: propagated.info, data: ParsedData::Tuple(tuple_data_set), + info: propagated.info, }) } TypeDef::Primitive(x) => Ok(ExtendedData { - info: propagated.info, data: decode_type_def_primitive(x, data, propagated.checker.specialty_set)?, + info: propagated.info, }), TypeDef::Compact(x) => { propagated.reject_compact()?; @@ -267,46 +264,37 @@ pub fn decode_with_type( TypeDef::BitSequence(x) => { propagated.reject_compact()?; Ok(ExtendedData { - info: propagated.info, data: decode_type_def_bit_sequence(x, data, meta_v14)?, + info: propagated.info, }) } }, - SpecialtyTypeChecked::AccountId32 => { - propagated.reject_compact()?; - Ok(ExtendedData { - info: propagated.info, - data: special_case_account_id32(data)?, - }) - } + SpecialtyTypeChecked::AccountId32 => Ok(ExtendedData { + data: AccountId32::parse_check_compact(data, propagated.is_compact())?, + info: propagated.info, + }), SpecialtyTypeChecked::Era => { propagated.reject_compact()?; Ok(ExtendedData { - info: propagated.info, data: special_case_era(data)?, - }) - } - SpecialtyTypeChecked::H160 => { - propagated.reject_compact()?; - Ok(ExtendedData { info: propagated.info, - data: H160::cut_and_decode(data)?, }) } + SpecialtyTypeChecked::H160 => Ok(ExtendedData { + data: H160::parse_check_compact(data, propagated.is_compact())?, + info: propagated.info, + }), SpecialtyTypeChecked::H256 => { propagated.reject_compact()?; Ok(ExtendedData { - info: propagated.info, data: special_case_h256(data, propagated.checker.specialty_set.hash256())?, - }) - } - SpecialtyTypeChecked::H512 => { - propagated.reject_compact()?; - Ok(ExtendedData { info: propagated.info, - data: H512::cut_and_decode(data)?, }) } + SpecialtyTypeChecked::H512 => Ok(ExtendedData { + data: H512::parse_check_compact(data, propagated.is_compact())?, + info: propagated.info, + }), SpecialtyTypeChecked::Option(ty_symbol) => { propagated.reject_compact()?; let param_ty = resolve_ty(meta_v14, ty_symbol.id())?; @@ -325,8 +313,8 @@ pub fn decode_with_type( }; *data = data[1..].to_vec(); Ok(ExtendedData { - info: propagated.info, data: parsed_data, + info: propagated.info, }) } None => Err(ParserError::DataTooShort), @@ -335,8 +323,8 @@ pub fn decode_with_type( Some(0) => { *data = data[1..].to_vec(); Ok(ExtendedData { - info: propagated.info, data: ParsedData::Option(None), + info: propagated.info, }) } Some(1) => { @@ -349,8 +337,8 @@ pub fn decode_with_type( )?; propagated.add_info_slice(&extended_option_data.info); Ok(ExtendedData { - info: propagated.info, data: ParsedData::Option(Some(Box::new(extended_option_data.data))), + info: propagated.info, }) } Some(_) => Err(ParserError::UnexpectedOptionVariant), @@ -375,80 +363,66 @@ pub fn decode_with_type( }; match item { PalletSpecificItem::Call => Ok(ExtendedData { - info: propagated.info, data: ParsedData::Call(Call(pallet_specific_data)), + info: propagated.info, }), PalletSpecificItem::Event => Ok(ExtendedData { - info: propagated.info, data: ParsedData::Event(Event(pallet_specific_data)), + info: propagated.info, }), } } SpecialtyTypeChecked::Perbill => Ok(ExtendedData { + data: Perbill::parse_check_compact(data, propagated.is_compact())?, info: propagated.info, - data: Perbill::decode_checked(data, propagated.checker.specialty_set.is_compact)?, }), SpecialtyTypeChecked::Percent => Ok(ExtendedData { + data: Percent::parse_check_compact(data, propagated.is_compact())?, info: propagated.info, - data: Percent::decode_checked(data, propagated.checker.specialty_set.is_compact)?, }), SpecialtyTypeChecked::Permill => Ok(ExtendedData { + data: Permill::parse_check_compact(data, propagated.is_compact())?, info: propagated.info, - data: Permill::decode_checked(data, propagated.checker.specialty_set.is_compact)?, }), SpecialtyTypeChecked::Perquintill => Ok(ExtendedData { + data: Perquintill::parse_check_compact(data, propagated.is_compact())?, info: propagated.info, - data: Perquintill::decode_checked(data, propagated.checker.specialty_set.is_compact)?, }), SpecialtyTypeChecked::PerU16 => Ok(ExtendedData { + data: PerU16::parse_check_compact(data, propagated.is_compact())?, + info: propagated.info, + }), + SpecialtyTypeChecked::PublicEd25519 => Ok(ExtendedData { + data: sp_core::ed25519::Public::parse_check_compact(data, propagated.is_compact())?, + info: propagated.info, + }), + SpecialtyTypeChecked::PublicSr25519 => Ok(ExtendedData { + data: sp_core::sr25519::Public::parse_check_compact(data, propagated.is_compact())?, + info: propagated.info, + }), + SpecialtyTypeChecked::PublicEcdsa => Ok(ExtendedData { + data: sp_core::ecdsa::Public::parse_check_compact(data, propagated.is_compact())?, + info: propagated.info, + }), + SpecialtyTypeChecked::SignatureEd25519 => Ok(ExtendedData { + data: sp_core::ed25519::Signature::parse_check_compact(data, propagated.is_compact())?, + info: propagated.info, + }), + SpecialtyTypeChecked::SignatureSr25519 => Ok(ExtendedData { + data: sp_core::sr25519::Signature::parse_check_compact(data, propagated.is_compact())?, + info: propagated.info, + }), + SpecialtyTypeChecked::SignatureEcdsa => Ok(ExtendedData { + data: sp_core::ecdsa::Signature::parse_check_compact(data, propagated.is_compact())?, info: propagated.info, - data: PerU16::decode_checked(data, propagated.checker.specialty_set.is_compact)?, }), - SpecialtyTypeChecked::PublicEd25519 => { - propagated.reject_compact()?; - Ok(ExtendedData { - info: propagated.info, - data: special_case_ed25519_public(data)?, - }) - } - SpecialtyTypeChecked::PublicSr25519 => { - propagated.reject_compact()?; - Ok(ExtendedData { - info: propagated.info, - data: special_case_sr25519_public(data)?, - }) - } - SpecialtyTypeChecked::PublicEcdsa => { - propagated.reject_compact()?; - Ok(ExtendedData { - info: propagated.info, - data: special_case_ecdsa_public(data)?, - }) - } - SpecialtyTypeChecked::SignatureEd25519 => { - propagated.reject_compact()?; - Ok(ExtendedData { - info: propagated.info, - data: special_case_ed25519_signature(data)?, - }) - } - SpecialtyTypeChecked::SignatureSr25519 => { - propagated.reject_compact()?; - Ok(ExtendedData { - info: propagated.info, - data: special_case_sr25519_signature(data)?, - }) - } - SpecialtyTypeChecked::SignatureEcdsa => { - propagated.reject_compact()?; - Ok(ExtendedData { - info: propagated.info, - data: special_case_ecdsa_signature(data)?, - }) - } } } +/// Parse part of data as a set of [`Field`]s. Used for structs, enums and call +/// decoding. +/// +/// Used data gets cut off in the process. fn decode_fields( fields: &[Field], data: &mut Vec, @@ -456,7 +430,13 @@ fn decode_fields( mut checker: Checker, ) -> Result, ParserError> { if fields.len() > 1 { + // Only single-field structs can be processed as a compact. + // Note: compact flag was already checked in enum processing at this + // point. checker.reject_compact()?; + + // `Hint` remains relevant only if single-field struct is processed. + // Note: checker gets renewed when fields of enum are processed. checker.forget_hint(); } let mut out: Vec = Vec::new(); @@ -479,6 +459,10 @@ fn decode_fields( Ok(out) } +/// Parse part of data as a known number of identical elements. Used for vectors +/// and arrays. +/// +/// Used data gets cut off in the process. fn decode_elements_set( element: &UntrackedSymbol, number_of_elements: u32, @@ -520,11 +504,16 @@ fn decode_elements_set( } }; Ok(ExtendedData { - info: propagated.info, data, + info: propagated.info, }) } +/// Select an enum variant based on data. +/// +/// First data `u8` element is `index` of [`Variant`]. +/// +/// Does not modify the input. pub(crate) fn pick_variant<'a>( variants: &'a [Variant], data: &[u8], @@ -547,6 +536,7 @@ pub(crate) fn pick_variant<'a>( } } +/// Parse part of data as a variant. Used for enums and call decoding. fn decode_variant( variants: &[Variant], data: &mut Vec, @@ -565,27 +555,34 @@ fn decode_variant( }) } +/// `BitOrder` as determined by the `bit_order_type` for [`TypeDefBitSequence`]. enum FoundBitOrder { Lsb0, Msb0, } +/// [`Type`]-associated [`Path`](scale_info::Path) `ident` for +/// [bitvec::order::Msb0]. const MSB0: &str = "Msb0"; + +/// [`Type`]-associated [`Path`](scale_info::Path) `ident` for +/// [bitvec::order::Lsb0]. const LSB0: &str = "Lsb0"; +/// Parse part of data as a bitvec. fn decode_type_def_bit_sequence( bit_ty: &TypeDefBitSequence, data: &mut Vec, meta_v14: &RuntimeMetadataV14, ) -> Result { - let cut_compact = cut_compact::(data)?; - let bit_length_found = cut_compact.compact_found; + let found_compact = find_compact::(data)?; + let bit_length_found = found_compact.compact; let byte_length = match bit_length_found % 8 { 0 => (bit_length_found / 8), _ => (bit_length_found / 8) + 1, } as usize; - let into_decode = match cut_compact.start_next_unit { + let into_decode = match found_compact.start_next_unit { Some(start) => match data.get(..start + byte_length) { Some(a) => { let into_decode = a.to_vec(); @@ -656,12 +653,23 @@ fn decode_type_def_bit_sequence( .map_err(|_| ParserError::TypeFailure("BitVec")) } +/// Type of set element, resolved as completely as possible. +/// +/// Elements in set (vector or array) could have complex solvable descriptions. +/// +/// Element [`Info`] is collected while resolving the type. No identical +/// [`Type`] `id`s are expected to be encountered (these are collected and +/// checked in [`Checker`]), otherwise the resolving would go indefinitely. struct HuskedType<'a> { info: Vec, checker: Checker, ty: &'a Type, } +/// Resolve [`Type`] of set element. +/// +/// Compact and single-field structs are resolved into corresponding inner +/// types. All available [`Info`] is collected. fn husk_type<'a>( entry_symbol: &'a UntrackedSymbol, meta_v14: &'a RuntimeMetadataV14, @@ -715,11 +723,16 @@ fn husk_type<'a>( Ok(HuskedType { info, checker, ty }) } +/// Type information used for parsing. pub enum Ty<'a> { + /// Type is already resolved in metadata `Registry`. Resolved(&'a Type), + + /// Type is not yet resolved. Symbol(&'a UntrackedSymbol), } +/// Resolve type id in `V14` metadata types `Registry`. fn resolve_ty(meta_v14: &RuntimeMetadataV14, id: u32) -> Result<&Type, ParserError> { match meta_v14.types.resolve(id) { Some(a) => Ok(a), diff --git a/src/decoding_sci_ext.rs b/src/decoding_sci_ext.rs index 3135540..6503e73 100644 --- a/src/decoding_sci_ext.rs +++ b/src/decoding_sci_ext.rs @@ -8,9 +8,21 @@ use crate::error::{ExtensionsError, SignableError}; use crate::metadata_check::CheckedMetadata; use crate::propagated::Propagated; use crate::special_indicators::SpecialtyPrimitive; -use crate::special_types::StLenCheckSpecialtyCompact; +use crate::special_types::UnsignedInteger; -pub fn decode_ext_attempt( +/// Parse signable transaction extensions with provided `V14` metadata. +/// +/// Data gets consumed. All input data is expected to be used in parsing. +/// +/// Metadata spec version and chain genesis hash are used to check that correct +/// metadata is used for parsing. +/// +/// Extensions and their order are determined by `signed_extensions` in +/// [`ExtrinsicMetadata`](frame_metadata::v14::ExtrinsicMetadata). +/// +/// Whole `signed_extensions` set is scanned first for types in `ty` field, and +/// then the second time, for types in `additional_signed` field. +pub fn decode_extensions( data: &mut Vec, checked_metadata: &CheckedMetadata, genesis_hash: H256, @@ -45,68 +57,25 @@ pub fn decode_ext_attempt( Ok(extensions) } -pub fn check_extensions( +/// Check collected extensions. +/// +/// Extensions must include metadata spec version and chain genesis hash. +/// If extensions also include `Era`, block hash for immortal `Era` must match +/// chain genesis hash. +fn check_extensions( extensions: &[ExtendedData], version: &str, genesis_hash: H256, ) -> Result<(), SignableError> { let mut collected_ext = CollectedExt::new(); for ext in extensions.iter() { - match ext.data { - ParsedData::Era(era) => collected_ext.add_era(era)?, - ParsedData::GenesisHash(h) => collected_ext.add_genesis_hash(h)?, - ParsedData::BlockHash(h) => collected_ext.add_block_hash(h)?, - ParsedData::PrimitiveU8 { - value, - specialty: SpecialtyPrimitive::SpecVersion, - } => collected_ext.add_spec_version::(value)?, - ParsedData::PrimitiveU16 { - value, - specialty: SpecialtyPrimitive::SpecVersion, - } => collected_ext.add_spec_version::(value)?, - ParsedData::PrimitiveU32 { - value, - specialty: SpecialtyPrimitive::SpecVersion, - } => collected_ext.add_spec_version::(value)?, - ParsedData::PrimitiveU64 { - value, - specialty: SpecialtyPrimitive::SpecVersion, - } => collected_ext.add_spec_version::(value)?, - ParsedData::PrimitiveU128 { - value, - specialty: SpecialtyPrimitive::SpecVersion, - } => collected_ext.add_spec_version::(value)?, - ParsedData::Composite(ref field_data) => { - if field_data.len() == 1 { - match field_data[0].data.data { - ParsedData::Era(era) => collected_ext.add_era(era)?, - ParsedData::GenesisHash(h) => collected_ext.add_genesis_hash(h)?, - ParsedData::BlockHash(h) => collected_ext.add_block_hash(h)?, - ParsedData::PrimitiveU8 { - value, - specialty: SpecialtyPrimitive::SpecVersion, - } => collected_ext.add_spec_version::(value)?, - ParsedData::PrimitiveU16 { - value, - specialty: SpecialtyPrimitive::SpecVersion, - } => collected_ext.add_spec_version::(value)?, - ParsedData::PrimitiveU32 { - value, - specialty: SpecialtyPrimitive::SpecVersion, - } => collected_ext.add_spec_version::(value)?, - ParsedData::PrimitiveU64 { - value, - specialty: SpecialtyPrimitive::SpecVersion, - } => collected_ext.add_spec_version::(value)?, - ParsedData::PrimitiveU128 { - value, - specialty: SpecialtyPrimitive::SpecVersion, - } => collected_ext.add_spec_version::(value)?, - _ => (), - } - } + // single-field structs are also checked + if let ParsedData::Composite(ref field_data) = ext.data { + if field_data.len() == 1 { + collected_ext.update(&field_data[0].data.data)?; } - _ => (), + } else { + collected_ext.update(&ext.data)?; } } match collected_ext.spec_version_printed { @@ -149,6 +118,7 @@ pub fn check_extensions( Ok(()) } +/// Collector for extensions that must be checked. struct CollectedExt { era: Option, genesis_hash: Option, @@ -157,6 +127,7 @@ struct CollectedExt { } impl CollectedExt { + /// Initiate new set. fn new() -> Self { Self { era: None, @@ -165,6 +136,38 @@ impl CollectedExt { spec_version_printed: None, } } + + /// Update set with `ParsedData`. + fn update(&mut self, parsed_data: &ParsedData) -> Result<(), SignableError> { + match parsed_data { + ParsedData::Era(era) => self.add_era(*era), + ParsedData::GenesisHash(h) => self.add_genesis_hash(*h), + ParsedData::BlockHash(h) => self.add_block_hash(*h), + ParsedData::PrimitiveU8 { + value, + specialty: SpecialtyPrimitive::SpecVersion, + } => self.add_spec_version::(*value), + ParsedData::PrimitiveU16 { + value, + specialty: SpecialtyPrimitive::SpecVersion, + } => self.add_spec_version::(*value), + ParsedData::PrimitiveU32 { + value, + specialty: SpecialtyPrimitive::SpecVersion, + } => self.add_spec_version::(*value), + ParsedData::PrimitiveU64 { + value, + specialty: SpecialtyPrimitive::SpecVersion, + } => self.add_spec_version::(*value), + ParsedData::PrimitiveU128 { + value, + specialty: SpecialtyPrimitive::SpecVersion, + } => self.add_spec_version::(*value), + _ => Ok(()), + } + } + + /// Add `Era` to set. fn add_era(&mut self, era: Era) -> Result<(), SignableError> { if self.era.is_some() { Err(SignableError::ExtensionsList(ExtensionsError::EraTwice)) @@ -173,6 +176,8 @@ impl CollectedExt { Ok(()) } } + + /// Add genesis hash to set. fn add_genesis_hash(&mut self, genesis_hash: H256) -> Result<(), SignableError> { if self.genesis_hash.is_some() { Err(SignableError::ExtensionsList( @@ -183,6 +188,8 @@ impl CollectedExt { Ok(()) } } + + /// Add block hash to set. fn add_block_hash(&mut self, block_hash: H256) -> Result<(), SignableError> { if self.block_hash.is_some() { Err(SignableError::ExtensionsList( @@ -193,7 +200,9 @@ impl CollectedExt { Ok(()) } } - fn add_spec_version( + + /// Add metadata spec version to set. + fn add_spec_version( &mut self, spec_version: T, ) -> Result<(), SignableError> { diff --git a/src/error.rs b/src/error.rs index fef236a..4e28301 100644 --- a/src/error.rs +++ b/src/error.rs @@ -64,6 +64,9 @@ pub enum ParserError { #[error("Declared type is not suitable BitOrder type for BitVec.")] NotBitOrderType, + #[error("Expected to use all data provided in decoding. Some data remained unused.")] + SomeDataNotUsedBlob, + #[error("Unable to decode data piece as {0}.")] TypeFailure(&'static str), @@ -117,7 +120,7 @@ pub enum ExtensionsError { SpecVersionTwice, } -/// Error in metadata version constant. +/// Error in metadata version constant search. #[derive(Debug, Eq, PartialEq, thiserror::Error)] pub enum MetaVersionError { #[error("No spec version found in decoded `Version` constant.")] diff --git a/src/lib.rs b/src/lib.rs index 4db0838..2e943bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,18 +147,6 @@ //! type_name: Some(String::from("::Source")), //! field_docs: String::new(), //! data: ExtendedData { -//! info: vec![ -//! Info { -//! docs: String::new(), -//! path: Path::from_segments(vec![ -//! "sp_runtime", -//! "multiaddress", -//! "MultiAddress" -//! ]) -//! .unwrap() -//! .into_portable(&mut Registry::new()), -//! } -//! ], //! data: ParsedData::Variant(VariantData { //! variant_name: String::from("Id"), //! variant_docs: String::new(), @@ -168,6 +156,7 @@ //! type_name: Some(String::from("AccountId")), //! field_docs: String::new(), //! data: ExtendedData { +//! data: ParsedData::Id(AccountId32::from_str("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48").unwrap()), //! info: vec![ //! Info { //! docs: String::new(), @@ -179,12 +168,23 @@ //! .unwrap() //! .into_portable(&mut Registry::new()), //! } -//! ], -//! data: ParsedData::Id(AccountId32::from_str("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48").unwrap()), +//! ] //! } //! } //! ] -//! }) +//! }), +//! info: vec![ +//! Info { +//! docs: String::new(), +//! path: Path::from_segments(vec![ +//! "sp_runtime", +//! "multiaddress", +//! "MultiAddress" +//! ]) +//! .unwrap() +//! .into_portable(&mut Registry::new()), +//! } +//! ], //! } //! }, //! FieldData { @@ -192,11 +192,11 @@ //! type_name: Some(String::from("T::Balance")), //! field_docs: String::new(), //! data: ExtendedData { -//! info: Vec::new(), //! data: ParsedData::PrimitiveU128{ //! value: 100000000, //! specialty: SpecialtyPrimitive::Balance, -//! } +//! }, +//! info: Vec::new() //! } //! } //! ]; @@ -205,6 +205,7 @@ //! // Parsed extensions. Note that many extensions are empty. //! let expected_extensions_data = vec![ //! ExtendedData { +//! data: ParsedData::Composite(Vec::new()), //! info: vec![ //! Info { //! docs: String::new(), @@ -217,10 +218,10 @@ //! .unwrap() //! .into_portable(&mut Registry::new()), //! } -//! ], -//! data: ParsedData::Composite(Vec::new()), +//! ] //! }, //! ExtendedData { +//! data: ParsedData::Composite(Vec::new()), //! info: vec![ //! Info { //! docs: String::new(), @@ -233,10 +234,10 @@ //! .unwrap() //! .into_portable(&mut Registry::new()), //! } -//! ], -//! data: ParsedData::Composite(Vec::new()), +//! ] //! }, //! ExtendedData { +//! data: ParsedData::Composite(Vec::new()), //! info: vec![ //! Info { //! docs: String::new(), @@ -249,23 +250,9 @@ //! .unwrap() //! .into_portable(&mut Registry::new()), //! } -//! ], -//! data: ParsedData::Composite(Vec::new()) +//! ] //! }, //! ExtendedData { -//! info: vec![ -//! Info { -//! docs: String::new(), -//! path: Path::from_segments(vec![ -//! "frame_system", -//! "extensions", -//! "check_mortality", -//! "CheckMortality", -//! ]) -//! .unwrap() -//! .into_portable(&mut Registry::new()), -//! } -//! ], //! data: ParsedData::Composite(vec![ //! FieldData { //! field_name: None, @@ -288,95 +275,109 @@ //! data: ParsedData::Era(Era::Mortal(64, 61)), //! } //! } -//! ]) -//! }, -//! ExtendedData { +//! ]), //! info: vec![ //! Info { //! docs: String::new(), //! path: Path::from_segments(vec![ //! "frame_system", //! "extensions", -//! "check_nonce", -//! "CheckNonce", +//! "check_mortality", +//! "CheckMortality", //! ]) //! .unwrap() //! .into_portable(&mut Registry::new()), //! } -//! ], +//! ] +//! }, +//! ExtendedData { //! data: ParsedData::Composite(vec![ //! FieldData { //! field_name: None, //! type_name: Some(String::from("T::Index")), //! field_docs: String::new(), //! data: ExtendedData { -//! info: Vec::new(), //! data: ParsedData::PrimitiveU32 { //! value: 261, //! specialty: SpecialtyPrimitive::Nonce, -//! } +//! }, +//! info: Vec::new() //! } //! } -//! ]) -//! }, -//! ExtendedData { +//! ]), //! info: vec![ //! Info { //! docs: String::new(), //! path: Path::from_segments(vec![ //! "frame_system", //! "extensions", -//! "check_weight", -//! "CheckWeight", +//! "check_nonce", +//! "CheckNonce", //! ]) //! .unwrap() //! .into_portable(&mut Registry::new()), //! } -//! ], -//! data: ParsedData::Composite(Vec::new()) +//! ] //! }, //! ExtendedData { +//! data: ParsedData::Composite(Vec::new()), //! info: vec![ //! Info { //! docs: String::new(), //! path: Path::from_segments(vec![ -//! "pallet_transaction_payment", -//! "ChargeTransactionPayment", +//! "frame_system", +//! "extensions", +//! "check_weight", +//! "CheckWeight", //! ]) //! .unwrap() //! .into_portable(&mut Registry::new()), //! } -//! ], +//! ] +//! }, +//! ExtendedData { //! data: ParsedData::Composite(vec![ //! FieldData { //! field_name: None, //! type_name: Some(String::from("BalanceOf")), //! field_docs: String::new(), //! data: ExtendedData { -//! info: Vec::new(), //! data: ParsedData::PrimitiveU128 { //! value: 10000000, //! specialty: SpecialtyPrimitive::Tip -//! } +//! }, +//! info: Vec::new() //! } //! } -//! ]) +//! ]), +//! info: vec![ +//! Info { +//! docs: String::new(), +//! path: Path::from_segments(vec![ +//! "pallet_transaction_payment", +//! "ChargeTransactionPayment", +//! ]) +//! .unwrap() +//! .into_portable(&mut Registry::new()), +//! } +//! ] //! }, //! ExtendedData { -//! info: Vec::new(), //! data: ParsedData::PrimitiveU32 { //! value: 9111, //! specialty: SpecialtyPrimitive::SpecVersion -//! } +//! }, +//! info: Vec::new() //! }, //! ExtendedData { -//! info: Vec::new(), //! data: ParsedData::PrimitiveU32 { //! value: 7, //! specialty: SpecialtyPrimitive::TxVersion -//! } +//! }, +//! info: Vec::new() //! }, //! ExtendedData { +//! data: ParsedData::GenesisHash(H256::from_str("e143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e").unwrap()), //! info: vec![ //! Info { //! docs: String::new(), @@ -387,10 +388,10 @@ //! .unwrap() //! .into_portable(&mut Registry::new()), //! } -//! ], -//! data: ParsedData::GenesisHash(H256::from_str("e143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e").unwrap()), +//! ] //! }, //! ExtendedData { +//! data: ParsedData::BlockHash(H256::from_str("98a8ee9e389043cd8a9954b254d822d34138b9ae97d3b7f50dc6781b13df8d84").unwrap()), //! info: vec![ //! Info { //! docs: String::new(), @@ -401,20 +402,19 @@ //! .unwrap() //! .into_portable(&mut Registry::new()), //! } -//! ], -//! data: ParsedData::BlockHash(H256::from_str("98a8ee9e389043cd8a9954b254d822d34138b9ae97d3b7f50dc6781b13df8d84").unwrap()), +//! ] //! }, //! ExtendedData { -//! info: Vec::new(), -//! data: ParsedData::Tuple(Vec::new()) +//! data: ParsedData::Tuple(Vec::new()), +//! info: Vec::new() //! }, //! ExtendedData { -//! info: Vec::new(), -//! data: ParsedData::Tuple(Vec::new()) +//! data: ParsedData::Tuple(Vec::new()), +//! info: Vec::new() //! }, //! ExtendedData { -//! info: Vec::new(), -//! data: ParsedData::Tuple(Vec::new()) +//! data: ParsedData::Tuple(Vec::new()), +//! info: Vec::new() //! } //! ]; //! @@ -436,9 +436,9 @@ use cards::{Call, ExtendedCard, ExtendedData}; pub mod compacts; use compacts::get_compact; mod decoding_sci; -pub use decoding_sci::{decode_as_call_v14, decode_with_type, Ty}; +pub use decoding_sci::{decode_as_call, decode_with_type, Ty}; mod decoding_sci_ext; -pub use decoding_sci_ext::decode_ext_attempt; +pub use decoding_sci_ext::decode_extensions; pub mod error; use error::{ParserError, SignableError}; mod metadata_check; @@ -447,7 +447,7 @@ pub mod printing_balance; mod propagated; use propagated::Propagated; pub mod special_indicators; -pub mod special_types; +mod special_types; #[cfg(test)] mod tests; @@ -524,10 +524,10 @@ pub fn parse_transaction( // try parsing extensions, check that spec version and genesis hash are // correct - let extensions = decode_ext_attempt(&mut extensions_data, &checked_metadata, genesis_hash)?; + let extensions = decode_extensions(&mut extensions_data, &checked_metadata, genesis_hash)?; // try parsing call data - let call_result = decode_as_call_v14(&mut call_data, checked_metadata.meta_v14); + let call_result = decode_as_call(&mut call_data, checked_metadata.meta_v14); Ok(TransactionParsed { call_result, @@ -535,13 +535,18 @@ pub fn parse_transaction( }) } -/// Decode data blob with known type. +/// Decode data with a known type using `V14` metadata. /// -/// No check here for all data being used. This check must be added elsewhere. +/// All data is expected to be used for the decoding. pub fn decode_blob_as_type( ty_symbol: &UntrackedSymbol, data: &mut Vec, meta_v14: &RuntimeMetadataV14, ) -> Result { - decode_with_type(&Ty::Symbol(ty_symbol), data, meta_v14, Propagated::new()) + let out = decode_with_type(&Ty::Symbol(ty_symbol), data, meta_v14, Propagated::new())?; + if !data.is_empty() { + Err(ParserError::SomeDataNotUsedBlob) + } else { + Ok(out) + } } diff --git a/src/metadata_check.rs b/src/metadata_check.rs index 319ad3e..f2e828a 100644 --- a/src/metadata_check.rs +++ b/src/metadata_check.rs @@ -1,3 +1,13 @@ +//! Determine metadata spec version. +//! +//! Metadata spec version is used when decoding signable transactions, to verify +//! that the metadata used for decoding has the same version as the metadata +//! used to generate the transaction. The metadata spec version used to +//! generate the transaction is one of the extensions. +//! +//! Metadata could be supplied into parser as `Raw` (metadata only) or as +//! `Checked` (metadata and spec version derived from elsewhere). +//! [`CheckedMetadata`] does not get re-checked in this crate. use frame_metadata::v14::RuntimeMetadataV14; use crate::cards::ParsedData; @@ -5,16 +15,20 @@ use crate::decode_blob_as_type; use crate::error::MetaVersionError; use crate::special_indicators::SpecialtyPrimitive; +/// Metadata with spec version. pub struct CheckedMetadata<'a> { + /// Runtime metadata. pub meta_v14: &'a RuntimeMetadataV14, + + /// Metadata spec version, printed. pub version: String, } /// Metadata used for signable transaction parsing. pub enum MetaInput<'a> { - /// Metadata is accompanied with spec version already checked elsewhere. + /// Metadata is accompanied by spec version already determined elsewhere. /// - /// No need to check it again. + /// No need to check the spec version again. Checked(CheckedMetadata<'a>), /// Spec version is not yet known and must be determined here. @@ -26,8 +40,7 @@ impl<'a> MetaInput<'a> { /// /// If `MetaInput` is `Raw`, search metadata for `System` pallet and /// `Version` constant within it, decode `Version` constant and find the - /// field with `spec_version` name. This is the spec version that goes into - /// `CheckedMetadata`. + /// field with `SpecVersion` content. pub(crate) fn checked(self) -> Result, MetaVersionError> { match self { Self::Checked(checked_metadata) => Ok(checked_metadata), diff --git a/src/printing_balance.rs b/src/printing_balance.rs index 12665d2..14546b5 100644 --- a/src/printing_balance.rs +++ b/src/printing_balance.rs @@ -55,7 +55,7 @@ //! assert!(balance.units == "pSMTH"); //! ``` //! -//! This crate **only formats** the data for output as text, it is not expected +//! This module **only formats** the data for output as text, it is not expected //! that any operations will be performed on the values except displaying them. /// Trait for correct displaying of balance-related values. diff --git a/src/propagated.rs b/src/propagated.rs index b2ad50b..7d516d6 100644 --- a/src/propagated.rs +++ b/src/propagated.rs @@ -230,6 +230,11 @@ impl Propagated { }) } + /// Get associated `is_compact` + pub fn is_compact(&self) -> bool { + self.checker.specialty_set.is_compact + } + /// Check that `is_compact` field in associated [`SpecialtySet`] is not /// `true`. pub fn reject_compact(&self) -> Result<(), ParserError> { diff --git a/src/special_types.rs b/src/special_types.rs index e0c7d9e..5a89331 100644 --- a/src/special_types.rs +++ b/src/special_types.rs @@ -2,7 +2,7 @@ use num_bigint::{BigInt, BigUint}; use parity_scale_codec::{Decode, HasCompact}; use sp_arithmetic::{PerU16, Perbill, Percent, Permill, Perquintill}; -use sp_core::{crypto::AccountId32, H160, H256, H512}; +use sp_core::{crypto::AccountId32, ByteArray, H160, H256, H512}; use sp_runtime::generic::Era; use std::{convert::TryInto, mem::size_of}; @@ -14,21 +14,33 @@ use crate::propagated::SpecialtySet; use crate::special_indicators::{SpecialtyH256, SpecialtyPrimitive}; /// Stable length trait. -pub(crate) trait StLen: Sized { - fn decode_value(data: &mut Vec) -> Result; +/// +/// Encoded data length in bytes is always identical for the type. +pub(crate) trait StableLength: Sized { + /// Encoded length for the type. + fn len_encoded() -> usize; + + /// Type value from the data. + /// + /// Cut data is consumed. + fn cut_and_decode(data: &mut Vec) -> Result; } -macro_rules! impl_stable_length_decodable { +/// Implement [`StableLength`] for types with stable [`size_of`], value is +/// decoded. +macro_rules! impl_stable_length_mem_size_decode { ($($ty: ty), *) => { $( - impl StLen for $ty { - fn decode_value(data: &mut Vec) -> Result { - let length = size_of::(); - match data.get(..length) { + impl StableLength for $ty { + fn len_encoded() -> usize { + size_of::() + } + fn cut_and_decode(data: &mut Vec) -> Result { + match data.get(..Self::len_encoded()) { Some(slice_to_decode) => { let out = ::decode(&mut &slice_to_decode[..]) .map_err(|_| ParserError::TypeFailure(stringify!($ty)))?; - *data = data[length..].to_vec(); + *data = data[Self::len_encoded()..].to_vec(); Ok(out) }, None => Err(ParserError::DataTooShort) @@ -39,7 +51,7 @@ macro_rules! impl_stable_length_decodable { } } -impl_stable_length_decodable!( +impl_stable_length_mem_size_decode!( bool, i8, i16, @@ -58,15 +70,25 @@ impl_stable_length_decodable!( Perquintill ); -macro_rules! impl_stable_length_big { +/// Known size for [`BigInt`] and [`BigUint`]. +const BIG_LEN: usize = 32; + +/// Known size for [`char`]. +const CHAR_LEN: usize = 4; + +/// Implement [`StableLength`] for [`BigInt`] and [`BigUint`]. +macro_rules! impl_stable_length_big_construct { ($($big: ty, $get: ident), *) => { $( - impl StLen for $big { - fn decode_value(data: &mut Vec) -> Result { - match data.get(0..32) { + impl StableLength for $big { + fn len_encoded() -> usize { + BIG_LEN + } + fn cut_and_decode(data: &mut Vec) -> Result { + match data.get(0..Self::len_encoded()) { Some(slice_to_big256) => { let out = Self::$get(slice_to_big256); - *data = data[32..].to_vec(); + *data = data[Self::len_encoded()..].to_vec(); Ok(out) }, None => Err(ParserError::DataTooShort), @@ -77,19 +99,22 @@ macro_rules! impl_stable_length_big { } } -impl_stable_length_big!(BigUint, from_bytes_le); -impl_stable_length_big!(BigInt, from_signed_bytes_le); +impl_stable_length_big_construct!(BigUint, from_bytes_le); +impl_stable_length_big_construct!(BigInt, from_signed_bytes_le); -impl StLen for char { - fn decode_value(data: &mut Vec) -> Result { - match data.get(0..4) { +impl StableLength for char { + fn len_encoded() -> usize { + CHAR_LEN + } + fn cut_and_decode(data: &mut Vec) -> Result { + match data.get(0..Self::len_encoded()) { Some(slice_to_char) => match char::from_u32(::from_le_bytes( slice_to_char .try_into() .expect("contstant length, always fit"), )) { Some(ch) => { - *data = data[4..].to_vec(); + *data = data[Self::len_encoded()..].to_vec(); Ok(ch) } None => Err(ParserError::TypeFailure("char")), @@ -99,24 +124,91 @@ impl StLen for char { } } -pub(crate) trait StLenCheckSpecialtyCompact: - StLen + AsBalance + HasCompact + std::fmt::Display +/// Implement [`StableLength`] for well-known arrays. +macro_rules! impl_stable_length_array { + ($($array: ty, $length: stmt, $make: ident), *) => { + $( + impl StableLength for $array { + fn len_encoded() -> usize { + $length + } + fn cut_and_decode(data: &mut Vec) -> Result { + match data.get(0..Self::len_encoded()) { + Some(slice_to_array) => { + let out = Self::$make(slice_to_array.try_into().expect("stable known length")); + *data = data[Self::len_encoded()..].to_vec(); + Ok(out) + }, + None => Err(ParserError::DataTooShort), + } + } + } + )* + } +} + +impl_stable_length_array!(AccountId32, Self::LEN, new); +impl_stable_length_array!(sp_core::ed25519::Public, Self::LEN, from_raw); +impl_stable_length_array!(sp_core::sr25519::Public, Self::LEN, from_raw); +impl_stable_length_array!(sp_core::ecdsa::Public, Self::LEN, from_raw); + +/// Known size for [sp_core::ed25519::Signature] and +/// [sp_core::sr25519::Signature]. +const SIGNATURE_LEN_64: usize = 64; + +/// Known size for [sp_core::ecdsa::Signature]. +const SIGNATURE_LEN_65: usize = 65; + +impl_stable_length_array!(sp_core::ed25519::Signature, SIGNATURE_LEN_64, from_raw); +impl_stable_length_array!(sp_core::sr25519::Signature, SIGNATURE_LEN_64, from_raw); +impl_stable_length_array!(sp_core::ecdsa::Signature, SIGNATURE_LEN_65, from_raw); + +/// Implement [`StableLength`] for well-known hashes. +macro_rules! impl_stable_length_hash { + ($($array: ty), *) => { + $( + impl StableLength for $array { + fn len_encoded() -> usize { + Self::len_bytes() + } + fn cut_and_decode(data: &mut Vec) -> Result { + match data.get(0..Self::len_encoded()) { + Some(slice_to_array) => { + let out = Self(slice_to_array.try_into().expect("stable known length")); + *data = data[Self::len_encoded()..].to_vec(); + Ok(out) + }, + None => Err(ParserError::DataTooShort), + } + } + } + )* + } +} + +impl_stable_length_hash!(H160, H256, H512); + +/// Unsigned integer trait. Compatible with compacts, uses the propagated +/// [`SpecialtyPrimitive`]. +pub(crate) trait UnsignedInteger: + StableLength + AsBalance + HasCompact + std::fmt::Display { - fn decode_checked( + fn parse_unsigned_integer( data: &mut Vec, specialty_set: SpecialtySet, ) -> Result; fn default_card_name() -> &'static str; } -macro_rules! impl_check_specialty_compact { +/// Implement [`UnsignedInteger`] trait for all unsigned integers. +macro_rules! impl_unsigned_integer { ($($ty: ty, $enum_variant: ident), *) => { $( - impl StLenCheckSpecialtyCompact for $ty { - fn decode_checked(data: &mut Vec, specialty_set: SpecialtySet) -> Result { + impl UnsignedInteger for $ty { + fn parse_unsigned_integer(data: &mut Vec, specialty_set: SpecialtySet) -> Result { let value = { if specialty_set.is_compact {get_compact::(data)?} - else {::decode_value(data)?} + else {::cut_and_decode(data)?} }; Ok(ParsedData::$enum_variant{value, specialty: specialty_set.primitive()}) } @@ -128,24 +220,28 @@ macro_rules! impl_check_specialty_compact { } } -impl_check_specialty_compact!(u8, PrimitiveU8); -impl_check_specialty_compact!(u16, PrimitiveU16); -impl_check_specialty_compact!(u32, PrimitiveU32); -impl_check_specialty_compact!(u64, PrimitiveU64); -impl_check_specialty_compact!(u128, PrimitiveU128); +impl_unsigned_integer!(u8, PrimitiveU8); +impl_unsigned_integer!(u16, PrimitiveU16); +impl_unsigned_integer!(u32, PrimitiveU32); +impl_unsigned_integer!(u64, PrimitiveU64); +impl_unsigned_integer!(u128, PrimitiveU128); -pub(crate) trait StLenCheckCompact: StLen { - fn decode_checked(data: &mut Vec, is_compact: bool) -> Result; +/// Trait for stable length types that must be checked for propagated compact +/// flag. +pub(crate) trait CheckCompact: StableLength { + fn parse_check_compact(data: &mut Vec, is_compact: bool) + -> Result; } +/// Implement [`CheckCompact`] for `PerThing` that can be compact. macro_rules! impl_allow_compact { ($($perthing: ident), *) => { $( - impl StLenCheckCompact for $perthing where $perthing: HasCompact { - fn decode_checked(data: &mut Vec, is_compact: bool) -> Result { + impl CheckCompact for $perthing where $perthing: HasCompact { + fn parse_check_compact(data: &mut Vec, is_compact: bool) -> Result { let value = { if is_compact {get_compact::(data)?} - else {::decode_value(data)?} + else {::cut_and_decode(data)?} }; Ok(ParsedData::$perthing(value)) } @@ -156,14 +252,15 @@ macro_rules! impl_allow_compact { impl_allow_compact!(PerU16, Percent, Permill, Perbill, Perquintill); +/// Implement [`CheckCompact`] for types that can not be compact. macro_rules! impl_block_compact { ($($ty: ty, $enum_variant: ident), *) => { $( - impl StLenCheckCompact for $ty { - fn decode_checked(data: &mut Vec, is_compact: bool) -> Result { + impl CheckCompact for $ty { + fn parse_check_compact(data: &mut Vec, is_compact: bool) -> Result { let value = { if is_compact {return Err(ParserError::UnexpectedCompactInsides)} - else {::decode_value(data)?} + else {::cut_and_decode(data)?} }; Ok(ParsedData::$enum_variant(value)) } @@ -181,15 +278,36 @@ impl_block_compact!(i64, PrimitiveI64); impl_block_compact!(i128, PrimitiveI128); impl_block_compact!(BigInt, PrimitiveI256); impl_block_compact!(BigUint, PrimitiveU256); +impl_block_compact!(AccountId32, Id); +impl_block_compact!(sp_core::ed25519::Public, PublicEd25519); +impl_block_compact!(sp_core::sr25519::Public, PublicSr25519); +impl_block_compact!(sp_core::ecdsa::Public, PublicEcdsa); +impl_block_compact!(sp_core::ed25519::Signature, SignatureEd25519); +impl_block_compact!(sp_core::sr25519::Signature, SignatureSr25519); +impl_block_compact!(sp_core::ecdsa::Signature, SignatureEcdsa); +impl_block_compact!(H160, H160); +impl_block_compact!(H512, H512); -pub trait Collectable: Sized { +/// Trait to collect some variants of [`ParsedData`] into [`Sequence`]. +/// +/// Some simple types are easier displayed if `Vec` is re-arranged +/// into single `ParsedData::Sequence(_)`. This is expecially true for `u8` and +/// `Vec`. +trait Collectable: Sized { fn husk_set(parsed_data_set: &[ParsedData]) -> Option; } +/// Implement [`Collectable`] for unsigned integers. macro_rules! impl_collect_vec { ($($ty: ty, $enum_variant_input: ident, $enum_variant_output: ident), *) => { $( impl Collectable for $ty { + /// Collecting data into `Sequence`. + /// + /// This function is unfallible. If somehow not all data is + /// of the same `ParsedData` variant, the `Sequence` just does + /// not get assembled and parsed data would be displayed as + /// `SequenceRaw`. fn husk_set(parsed_data_set: &[ParsedData]) -> Option { let mut out: Vec = Vec::new(); for x in parsed_data_set.iter() { @@ -254,7 +372,9 @@ impl Collectable for Vec { } } -pub fn wrap_sequence(set: &[ParsedData]) -> Option { +/// Try collecting [`Sequence`]. Expected variant of [`ParsedData`] is +/// determined by the first set element. +pub(crate) fn wrap_sequence(set: &[ParsedData]) -> Option { match set.first() { Some(ParsedData::PrimitiveU8 { .. }) => u8::husk_set(set), Some(ParsedData::PrimitiveU16 { .. }) => u16::husk_set(set), @@ -269,130 +389,25 @@ pub fn wrap_sequence(set: &[ParsedData]) -> Option { } } -/// Function to decode of AccountId special case and transform the result into base58 format. -/// -/// The function decodes only a single AccountId type entry, -/// removes already decoded part of input data Vec, -/// and returns whatever remains as DecodedOut field remaining_vector, which is processed later separately. -/// -/// The function takes as arguments -/// - data (remaining Vec of data), +/// Parse part of the data as [`H256`], apply available [`SpecialtyH256`]. /// -/// The function outputs the DecodedOut value in case of success. -/// -/// Resulting AccountId in base58 form is added to fancy_out on js card "Id". -pub(crate) fn special_case_account_id32(data: &mut Vec) -> Result { - match data.get(0..32) { - Some(a) => { - let array_decoded: [u8; 32] = a.try_into().expect("constant length, always fits"); - *data = data[32..].to_vec(); - let account_id = AccountId32::new(array_decoded); - Ok(ParsedData::Id(account_id)) - } - None => Err(ParserError::DataTooShort), - } -} - -macro_rules! crypto_type_decoder { - ($func:ident, $module:ident, $target:ident, $len:literal, $enum_variant: ident) => { - pub(crate) fn $func(data: &mut Vec) -> Result { - match data.get(0..$len) { - Some(a) => { - let array_decoded: [u8; $len] = - a.try_into().expect("constant length, always fits"); - *data = data[$len..].to_vec(); - let public = sp_core::$module::$target::from_raw(array_decoded); - Ok(ParsedData::$enum_variant(public)) - } - None => Err(ParserError::DataTooShort), - } - } - }; -} - -crypto_type_decoder!( - special_case_ed25519_public, - ed25519, - Public, - 32, - PublicEd25519 -); -crypto_type_decoder!( - special_case_sr25519_public, - sr25519, - Public, - 32, - PublicSr25519 -); -crypto_type_decoder!(special_case_ecdsa_public, ecdsa, Public, 33, PublicEcdsa); -crypto_type_decoder!( - special_case_ed25519_signature, - ed25519, - Signature, - 64, - SignatureEd25519 -); -crypto_type_decoder!( - special_case_sr25519_signature, - sr25519, - Signature, - 64, - SignatureSr25519 -); -crypto_type_decoder!( - special_case_ecdsa_signature, - ecdsa, - Signature, - 65, - SignatureEcdsa -); - -pub(crate) trait SpecialArray { - fn cut_and_decode(data: &mut Vec) -> Result; -} - -macro_rules! impl_special_array_h { - ($($hash: ident), *) => { - $( - impl SpecialArray for $hash { - fn cut_and_decode(data: &mut Vec) -> Result { - let length = <$hash>::len_bytes(); - match data.get(..length) { - Some(slice) => { - let out_data = $hash(slice.try_into().expect("fixed checked length, always fits")); - *data = data[length..].to_vec(); - Ok(ParsedData::$hash(out_data)) - }, - None => Err(ParserError::DataTooShort) - } - } - } - )* - } -} - -impl_special_array_h!(H160, H512); - -pub fn special_case_h256( +/// Used data gets cut off in the process. +pub(crate) fn special_case_h256( data: &mut Vec, specialty_hash: SpecialtyH256, ) -> Result { - let length = H256::len_bytes(); - match data.get(..length) { - Some(slice) => { - let out_data = H256(slice.try_into().expect("fixed checked length, always fits")); - *data = data[length..].to_vec(); - match specialty_hash { - SpecialtyH256::GenesisHash => Ok(ParsedData::GenesisHash(out_data)), - SpecialtyH256::BlockHash => Ok(ParsedData::BlockHash(out_data)), - SpecialtyH256::None => Ok(ParsedData::H256(out_data)), - } - } - None => Err(ParserError::DataTooShort), + let hash = H256::cut_and_decode(data)?; + match specialty_hash { + SpecialtyH256::GenesisHash => Ok(ParsedData::GenesisHash(hash)), + SpecialtyH256::BlockHash => Ok(ParsedData::BlockHash(hash)), + SpecialtyH256::None => Ok(ParsedData::H256(hash)), } } -pub fn special_case_era(data: &mut Vec) -> Result { +/// Parse part of the data as [`Era`]. +/// +/// Used data gets cut off in the process. +pub(crate) fn special_case_era(data: &mut Vec) -> Result { let (era_data, remaining_vector) = match data.first() { Some(0) => (data[0..1].to_vec(), data[1..].to_vec()), Some(_) => match data.get(0..2) { diff --git a/src/tests.rs b/src/tests.rs index 971305b..f90b26a 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -173,7 +173,7 @@ bypassing `frame_system::Config::BaseCallFilter`). - Complexity: O(C) where C is the number of calls to be batched. # , path: None) Field Name: calls - Sequence: 2 element(s), element info: (docs: None, path: westend_runtime >> Call) + Sequence: 2 element(s), element info: [(docs: None, path: westend_runtime >> Call)] Pallet: Staking (docs: Contains one variant per dispatchable that can be called by an extrinsic., path: pallet_staking >> pallet >> pallet >> Call) Call: bond @@ -221,7 +221,7 @@ which is capped at CompactAssignments::LIMIT (MAX_NOMINATIONS). - Both the reads and writes follow a similar pattern. # , path: None) Field Name: targets - Sequence: 3 element(s), element info: (docs: None, path: sp_runtime >> multiaddress >> MultiAddress) + Sequence: 3 element(s), element info: [(docs: None, path: sp_runtime >> multiaddress >> MultiAddress)] Enum Enum Variant Name: Id Id: 5CFPcUJgYgWryPaV1aYjSbTpbTLu42V32Ytw1L9rfoMAsfGh @@ -425,18 +425,11 @@ fn storage_1_good() { let reply = decode_blob_as_type(&system_digest_ty, &mut data, &metadata).unwrap(); let reply_known = ExtendedData { - info: vec![Info { - docs: String::new(), - path: Path::from_segments(vec!["sp_runtime", "generic", "digest", "Digest"]) - .unwrap() - .into_portable(&mut Registry::new()), - }], data: ParsedData::Composite(vec![FieldData { field_name: Some(String::from("logs")), type_name: Some(String::from("Vec")), field_docs: String::new(), data: ExtendedData { - info: Vec::new(), data: ParsedData::SequenceRaw(SequenceRawData { element_info: vec![Info { docs: String::new(), @@ -458,11 +451,11 @@ fn storage_1_good() { type_name: Some(String::from("ConsensusEngineId")), field_docs: String::new(), data: ExtendedData { - info: Vec::new(), data: ParsedData::Sequence(SequenceData { element_info: Vec::new(), data: Sequence::U8(vec![97, 117, 114, 97]), }), + info: Vec::new(), }, }, FieldData { @@ -470,18 +463,25 @@ fn storage_1_good() { type_name: Some(String::from("Vec")), field_docs: String::new(), data: ExtendedData { - info: Vec::new(), data: ParsedData::Sequence(SequenceData { element_info: Vec::new(), data: Sequence::U8(vec![193, 242, 65, 8, 0, 0, 0, 0]), }), + info: Vec::new(), }, }, ], })], }), + info: Vec::new(), }, }]), + info: vec![Info { + docs: String::new(), + path: Path::from_segments(vec!["sp_runtime", "generic", "digest", "Digest"]) + .unwrap() + .into_portable(&mut Registry::new()), + }], }; assert_eq!(reply_known, reply); }