// This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Generic implementation of an unchecked (pre-verification) extrinsic. use crate::{ generic::{CheckedExtrinsic, ExtrinsicFormat}, traits::{ self, transaction_extension::TransactionExtension, Checkable, Dispatchable, ExtrinsicCall, ExtrinsicLike, ExtrinsicMetadata, IdentifyAccount, LazyExtrinsic, MaybeDisplay, Member, SignaturePayload, }, transaction_validity::{InvalidTransaction, TransactionValidityError}, OpaqueExtrinsic, }; #[cfg(all(not(feature = "std"), feature = "serde"))] use alloc::format; use alloc::{vec, vec::Vec}; use codec::{ Compact, CountedInput, Decode, DecodeWithMemLimit, DecodeWithMemTracking, Encode, EncodeLike, Input, }; use core::fmt; use scale_info::{build::Fields, meta_type, Path, StaticTypeInfo, Type, TypeInfo, TypeParameter}; use sp_io::hashing::blake2_256; use sp_weights::Weight; /// Type to represent the version of the [Extension](TransactionExtension) used in this extrinsic. pub type ExtensionVersion = u8; /// Type to represent the extrinsic format version which defines an [UncheckedExtrinsic]. pub type ExtrinsicVersion = u8; /// Current version of the [`UncheckedExtrinsic`] encoded format. /// /// This version needs to be bumped if the encoded representation changes. /// It ensures that if the representation is changed and the format is not known, /// the decoding fails. pub const EXTRINSIC_FORMAT_VERSION: ExtrinsicVersion = 5; /// Legacy version of the [`UncheckedExtrinsic`] encoded format. /// /// This version was used in the signed/unsigned transaction model and is still supported for /// compatibility reasons. It will be deprecated in favor of v5 extrinsics and an inherent/general /// transaction model. pub const LEGACY_EXTRINSIC_FORMAT_VERSION: ExtrinsicVersion = 4; /// Current version of the [Extension](TransactionExtension) used in this /// [extrinsic](UncheckedExtrinsic). /// /// This version needs to be bumped if there are breaking changes to the extension used in the /// [UncheckedExtrinsic] implementation. const EXTENSION_VERSION: ExtensionVersion = 0; /// Maximum decoded heap size for a runtime call (in bytes). pub const DEFAULT_MAX_CALL_SIZE: usize = 16 * 1024 * 1024; // 16 MiB /// The `SignaturePayload` of `UncheckedExtrinsic`. pub type UncheckedSignaturePayload = (Address, Signature, Extension); impl SignaturePayload for UncheckedSignaturePayload { type SignatureAddress = Address; type Signature = Signature; type SignatureExtra = Extension; } /// A "header" for extrinsics leading up to the call itself. Determines the type of extrinsic and /// holds any necessary specialized data. #[derive(DecodeWithMemTracking, Eq, PartialEq, Clone)] pub enum Preamble { /// An extrinsic without a signature or any extension. This means it's either an inherent or /// an old-school "Unsigned" (we don't use that terminology any more since it's confusable with /// the general transaction which is without a signature but does have an extension). /// /// NOTE: In the future, once we remove `ValidateUnsigned`, this will only serve Inherent /// extrinsics and thus can be renamed to `Inherent`. Bare(ExtrinsicVersion), /// An old-school transaction extrinsic which includes a signature of some hard-coded crypto. /// Available only on extrinsic version 4. Signed(Address, Signature, Extension), /// A new-school transaction extrinsic which does not include a signature by default. The /// origin authorization, through signatures or other means, is performed by the transaction /// extension in this extrinsic. Available starting with extrinsic version 5. General(ExtensionVersion, Extension), } const VERSION_MASK: u8 = 0b0011_1111; const TYPE_MASK: u8 = 0b1100_0000; const BARE_EXTRINSIC: u8 = 0b0000_0000; const SIGNED_EXTRINSIC: u8 = 0b1000_0000; const GENERAL_EXTRINSIC: u8 = 0b0100_0000; impl Decode for Preamble where Address: Decode, Signature: Decode, Extension: Decode, { fn decode(input: &mut I) -> Result { let version_and_type = input.read_byte()?; let version = version_and_type & VERSION_MASK; let xt_type = version_and_type & TYPE_MASK; let preamble = match (version, xt_type) { ( extrinsic_version @ LEGACY_EXTRINSIC_FORMAT_VERSION..=EXTRINSIC_FORMAT_VERSION, BARE_EXTRINSIC, ) => Self::Bare(extrinsic_version), (LEGACY_EXTRINSIC_FORMAT_VERSION, SIGNED_EXTRINSIC) => { let address = Address::decode(input)?; let signature = Signature::decode(input)?; let ext = Extension::decode(input)?; Self::Signed(address, signature, ext) }, (EXTRINSIC_FORMAT_VERSION, GENERAL_EXTRINSIC) => { let ext_version = ExtensionVersion::decode(input)?; let ext = Extension::decode(input)?; Self::General(ext_version, ext) }, (_, _) => return Err("Invalid transaction version".into()), }; Ok(preamble) } } impl Encode for Preamble where Address: Encode, Signature: Encode, Extension: Encode, { fn size_hint(&self) -> usize { match &self { Preamble::Bare(_) => EXTRINSIC_FORMAT_VERSION.size_hint(), Preamble::Signed(address, signature, ext) => LEGACY_EXTRINSIC_FORMAT_VERSION .size_hint() .saturating_add(address.size_hint()) .saturating_add(signature.size_hint()) .saturating_add(ext.size_hint()), Preamble::General(ext_version, ext) => EXTRINSIC_FORMAT_VERSION .size_hint() .saturating_add(ext_version.size_hint()) .saturating_add(ext.size_hint()), } } fn encode_to(&self, dest: &mut T) { match &self { Preamble::Bare(extrinsic_version) => { (extrinsic_version | BARE_EXTRINSIC).encode_to(dest); }, Preamble::Signed(address, signature, ext) => { (LEGACY_EXTRINSIC_FORMAT_VERSION | SIGNED_EXTRINSIC).encode_to(dest); address.encode_to(dest); signature.encode_to(dest); ext.encode_to(dest); }, Preamble::General(ext_version, ext) => { (EXTRINSIC_FORMAT_VERSION | GENERAL_EXTRINSIC).encode_to(dest); ext_version.encode_to(dest); ext.encode_to(dest); }, } } } impl Preamble { /// Returns `Some` if this is a signed extrinsic, together with the relevant inner fields. pub fn to_signed(self) -> Option<(Address, Signature, Extension)> { match self { Self::Signed(a, s, e) => Some((a, s, e)), _ => None, } } } impl fmt::Debug for Preamble where Address: fmt::Debug, Extension: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Bare(_) => write!(f, "Bare"), Self::Signed(address, _, tx_ext) => write!(f, "Signed({:?}, {:?})", address, tx_ext), Self::General(ext_version, tx_ext) => { write!(f, "General({:?}, {:?})", ext_version, tx_ext) }, } } } /// An extrinsic right from the external world. This is unchecked and so can contain a signature. /// /// An extrinsic is formally described as any external data that is originating from the outside of /// the runtime and fed into the runtime as a part of the block-body. /// /// Inherents are special types of extrinsics that are placed into the block by the block-builder. /// They are unsigned because the assertion is that they are "inherently true" by virtue of getting /// past all validators. /// /// Transactions are all other statements provided by external entities that the chain deems values /// and decided to include in the block. This value is typically in the form of fee payment, but it /// could in principle be any other interaction. Transactions are either signed or unsigned. A /// sensible transaction pool should ensure that only transactions that are worthwhile are /// considered for block-building. #[cfg_attr(all(feature = "std", not(windows)), doc = simple_mermaid::mermaid!("../../docs/mermaid/extrinsics.mmd"))] /// This type is by no means enforced within Substrate, but given its genericness, it is highly /// likely that for most use-cases it will suffice. Thus, the encoding of this type will dictate /// exactly what bytes should be sent to a runtime to transact with it. /// /// This can be checked using [`Checkable`], yielding a [`CheckedExtrinsic`], which is the /// counterpart of this type after its signature (and other non-negotiable validity checks) have /// passed. #[derive(DecodeWithMemTracking, PartialEq, Eq, Clone, Debug)] #[codec(decode_with_mem_tracking_bound( Address: DecodeWithMemTracking, Call: DecodeWithMemTracking, Signature: DecodeWithMemTracking, Extension: DecodeWithMemTracking) )] pub struct UncheckedExtrinsic< Address, Call, Signature, Extension, const MAX_CALL_SIZE: usize = DEFAULT_MAX_CALL_SIZE, > { /// Information regarding the type of extrinsic this is (inherent or transaction) as well as /// associated extension (`Extension`) data if it's a transaction and a possible signature. pub preamble: Preamble, /// The function that should be called. pub function: Call, } /// Manual [`TypeInfo`] implementation because of custom encoding. The data is a valid encoded /// `Vec`, but requires some logic to extract the signature and payload. /// /// See [`UncheckedExtrinsic::encode`] and [`UncheckedExtrinsic::decode`]. impl TypeInfo for UncheckedExtrinsic where Address: StaticTypeInfo, Call: StaticTypeInfo, Signature: StaticTypeInfo, Extension: StaticTypeInfo, { type Identity = UncheckedExtrinsic; fn type_info() -> Type { Type::builder() .path(Path::new("UncheckedExtrinsic", module_path!())) // Include the type parameter types, even though they are not used directly in any of // the described fields. These type definitions can be used by downstream consumers // to help construct the custom decoding from the opaque bytes (see below). .type_params(vec![ TypeParameter::new("Address", Some(meta_type::
())), TypeParameter::new("Call", Some(meta_type::())), TypeParameter::new("Signature", Some(meta_type::())), TypeParameter::new("Extra", Some(meta_type::())), ]) .docs(&["UncheckedExtrinsic raw bytes, requires custom decoding routine"]) // Because of the custom encoding, we can only accurately describe the encoding as an // opaque `Vec`. Downstream consumers will need to manually implement the codec to // encode/decode the `signature` and `function` fields. .composite(Fields::unnamed().field(|f| f.ty::>())) } } impl UncheckedExtrinsic { /// New instance of a bare (ne unsigned) extrinsic. This could be used for an inherent or an /// old-school "unsigned transaction" (which are new being deprecated in favour of general /// transactions). #[deprecated = "Use new_bare instead"] pub fn new_unsigned(function: Call) -> Self { Self::new_bare(function) } /// Returns `true` if this extrinsic instance is an inherent, `false`` otherwise. pub fn is_inherent(&self) -> bool { matches!(self.preamble, Preamble::Bare(_)) } /// Returns `true` if this extrinsic instance is an old-school signed transaction, `false` /// otherwise. pub fn is_signed(&self) -> bool { matches!(self.preamble, Preamble::Signed(..)) } /// Create an `UncheckedExtrinsic` from a `Preamble` and the actual `Call`. pub fn from_parts(function: Call, preamble: Preamble) -> Self { Self { preamble, function } } /// New instance of a bare (ne unsigned) extrinsic. pub fn new_bare(function: Call) -> Self { Self::from_parts(function, Preamble::Bare(EXTRINSIC_FORMAT_VERSION)) } /// New instance of a bare (ne unsigned) extrinsic on extrinsic format version 4. pub fn new_bare_legacy(function: Call) -> Self { Self::from_parts(function, Preamble::Bare(LEGACY_EXTRINSIC_FORMAT_VERSION)) } /// New instance of an old-school signed transaction on extrinsic format version 4. pub fn new_signed( function: Call, signed: Address, signature: Signature, tx_ext: Extension, ) -> Self { Self::from_parts(function, Preamble::Signed(signed, signature, tx_ext)) } /// New instance of an new-school unsigned transaction. pub fn new_transaction(function: Call, tx_ext: Extension) -> Self { Self::from_parts(function, Preamble::General(EXTENSION_VERSION, tx_ext)) } fn decode_with_len(input: &mut I, len: usize) -> Result where Preamble: Decode, Call: DecodeWithMemTracking, { let mut input = CountedInput::new(input); let preamble = Decode::decode(&mut input)?; // Adds 1 byte to the `MAX_CALL_SIZE` as the decoding fails exactly at the given value and // the maximum should be allowed to fit in. let function = Call::decode_with_mem_limit(&mut input, MAX_CALL_SIZE.saturating_add(1))?; if input.count() != len as u64 { return Err("Invalid length prefix".into()); } Ok(Self { preamble, function }) } fn encode_without_prefix(&self) -> Vec where Preamble: Encode, Call: Encode, { (&self.preamble, &self.function).encode() } } impl ExtrinsicLike for UncheckedExtrinsic { fn is_signed(&self) -> Option { Some(matches!(self.preamble, Preamble::Signed(..))) } fn is_bare(&self) -> bool { matches!(self.preamble, Preamble::Bare(_)) } } impl ExtrinsicCall for UncheckedExtrinsic { type Call = Call; fn call(&self) -> &Call { &self.function } fn into_call(self) -> Self::Call { self.function } } // TODO: Migrate existing extension pipelines to support current `Signed` transactions as `General` // transactions by adding an extension to validate signatures, as they are currently validated in // the `Checkable` implementation for `Signed` transactions. impl Checkable for UncheckedExtrinsic where LookupSource: Member + MaybeDisplay, Call: Encode + Member + Dispatchable, Signature: Member + traits::Verify, ::Signer: IdentifyAccount, Extension: Encode + TransactionExtension, AccountId: Member + MaybeDisplay, Lookup: traits::Lookup, { type Checked = CheckedExtrinsic; fn check(self, lookup: &Lookup) -> Result { Ok(match self.preamble { Preamble::Signed(signed, signature, tx_ext) => { let signed = lookup.lookup(signed)?; // The `Implicit` is "implicitly" included in the payload. let raw_payload = SignedPayload::new(self.function, tx_ext)?; if !raw_payload.using_encoded(|payload| signature.verify(payload, &signed)) { return Err(InvalidTransaction::BadProof.into()); } let (function, tx_ext, _) = raw_payload.deconstruct(); CheckedExtrinsic { format: ExtrinsicFormat::Signed(signed, tx_ext), function } }, Preamble::General(extension_version, tx_ext) => CheckedExtrinsic { format: ExtrinsicFormat::General(extension_version, tx_ext), function: self.function, }, Preamble::Bare(_) => CheckedExtrinsic { format: ExtrinsicFormat::Bare, function: self.function }, }) } #[cfg(feature = "try-runtime")] fn unchecked_into_checked_i_know_what_i_am_doing( self, lookup: &Lookup, ) -> Result { Ok(match self.preamble { Preamble::Signed(signed, _, tx_ext) => { let signed = lookup.lookup(signed)?; CheckedExtrinsic { format: ExtrinsicFormat::Signed(signed, tx_ext), function: self.function, } }, Preamble::General(extension_version, tx_ext) => CheckedExtrinsic { format: ExtrinsicFormat::General(extension_version, tx_ext), function: self.function, }, Preamble::Bare(_) => CheckedExtrinsic { format: ExtrinsicFormat::Bare, function: self.function }, }) } } impl> ExtrinsicMetadata for UncheckedExtrinsic { const VERSIONS: &'static [u8] = &[LEGACY_EXTRINSIC_FORMAT_VERSION, EXTRINSIC_FORMAT_VERSION]; type TransactionExtensions = Extension; } impl> UncheckedExtrinsic { /// Returns the weight of the extension of this transaction, if present. If the transaction /// doesn't use any extension, the weight returned is equal to zero. pub fn extension_weight(&self) -> Weight { match &self.preamble { Preamble::Bare(_) => Weight::zero(), Preamble::Signed(_, _, ext) | Preamble::General(_, ext) => ext.weight(&self.function), } } } impl Decode for UncheckedExtrinsic where Address: Decode, Signature: Decode, Call: DecodeWithMemTracking, Extension: Decode, { fn decode(input: &mut I) -> Result { // This is a little more complicated than usual since the binary format must be compatible // with SCALE's generic `Vec` type. Basically this just means accepting that there // will be a prefix of vector length. let expected_length: Compact = Decode::decode(input)?; Self::decode_with_len(input, expected_length.0 as usize) } } #[docify::export(unchecked_extrinsic_encode_impl)] impl Encode for UncheckedExtrinsic where Preamble: Encode, Call: Encode, Extension: Encode, { fn encode(&self) -> Vec { let tmp = self.encode_without_prefix(); let compact_len = codec::Compact::(tmp.len() as u32); // Allocate the output buffer with the correct length let mut output = Vec::with_capacity(compact_len.size_hint() + tmp.len()); compact_len.encode_to(&mut output); output.extend(tmp); output } } impl EncodeLike for UncheckedExtrinsic where Address: Encode, Signature: Encode, Call: Encode + Dispatchable, Extension: TransactionExtension, { } #[cfg(feature = "serde")] impl serde::Serialize for UncheckedExtrinsic { fn serialize(&self, seq: S) -> Result where S: ::serde::Serializer, { self.using_encoded(|bytes| seq.serialize_bytes(bytes)) } } #[cfg(feature = "serde")] impl<'a, Address: Decode, Signature: Decode, Call: DecodeWithMemTracking, Extension: Decode> serde::Deserialize<'a> for UncheckedExtrinsic { fn deserialize(de: D) -> Result where D: serde::Deserializer<'a>, { let r = sp_core::bytes::deserialize(de)?; Self::decode(&mut &r[..]) .map_err(|e| serde::de::Error::custom(format!("Decode error: {}", e))) } } /// A payload that has been signed for an unchecked extrinsics. /// /// Note that the payload that we sign to produce unchecked extrinsic signature /// is going to be different than the `SignaturePayload` - so the thing the extrinsic /// actually contains. pub struct SignedPayload>( (Call, Extension, Extension::Implicit), ); impl SignedPayload where Call: Encode + Dispatchable, Extension: TransactionExtension, { /// Create new `SignedPayload` for extrinsic format version 4. /// /// This function may fail if `implicit` of `Extension` is not available. pub fn new(call: Call, tx_ext: Extension) -> Result { let implicit = Extension::implicit(&tx_ext)?; let raw_payload = (call, tx_ext, implicit); Ok(Self(raw_payload)) } /// Create new `SignedPayload` from raw components. pub fn from_raw(call: Call, tx_ext: Extension, implicit: Extension::Implicit) -> Self { Self((call, tx_ext, implicit)) } /// Deconstruct the payload into it's components. pub fn deconstruct(self) -> (Call, Extension, Extension::Implicit) { self.0 } } impl Encode for SignedPayload where Call: Encode + Dispatchable, Extension: TransactionExtension, { /// Get an encoded version of this `blake2_256`-hashed payload. fn using_encoded R>(&self, f: F) -> R { self.0.using_encoded(|payload| { if payload.len() > 256 { f(&blake2_256(payload)[..]) } else { f(payload) } }) } } impl EncodeLike for SignedPayload where Call: Encode + Dispatchable, Extension: TransactionExtension, { } impl From> for OpaqueExtrinsic where Preamble: Encode, Call: Encode, { fn from(extrinsic: UncheckedExtrinsic) -> Self { Self::from_blob(extrinsic.encode_without_prefix()) } } impl LazyExtrinsic for UncheckedExtrinsic where Preamble: Decode, Call: DecodeWithMemTracking, { fn decode_unprefixed(data: &[u8]) -> Result { Self::decode_with_len(&mut &data[..], data.len()) } } #[cfg(test)] mod legacy { use codec::{Compact, Decode, Encode, EncodeLike, Error, Input}; use scale_info::{ build::Fields, meta_type, Path, StaticTypeInfo, Type, TypeInfo, TypeParameter, }; pub type UncheckedSignaturePayloadV4 = (Address, Signature, Extra); #[derive(PartialEq, Eq, Clone, Debug)] pub struct UncheckedExtrinsicV4 { pub signature: Option>, pub function: Call, } impl TypeInfo for UncheckedExtrinsicV4 where Address: StaticTypeInfo, Call: StaticTypeInfo, Signature: StaticTypeInfo, Extra: StaticTypeInfo, { type Identity = UncheckedExtrinsicV4; fn type_info() -> Type { Type::builder() .path(Path::new("UncheckedExtrinsic", module_path!())) // Include the type parameter types, even though they are not used directly in any // of the described fields. These type definitions can be used by downstream // consumers to help construct the custom decoding from the opaque bytes (see // below). .type_params(vec![ TypeParameter::new("Address", Some(meta_type::
())), TypeParameter::new("Call", Some(meta_type::())), TypeParameter::new("Signature", Some(meta_type::())), TypeParameter::new("Extra", Some(meta_type::())), ]) .docs(&["OldUncheckedExtrinsic raw bytes, requires custom decoding routine"]) // Because of the custom encoding, we can only accurately describe the encoding as // an opaque `Vec`. Downstream consumers will need to manually implement the // codec to encode/decode the `signature` and `function` fields. .composite(Fields::unnamed().field(|f| f.ty::>())) } } impl UncheckedExtrinsicV4 { pub fn new_signed( function: Call, signed: Address, signature: Signature, extra: Extra, ) -> Self { Self { signature: Some((signed, signature, extra)), function } } pub fn new_unsigned(function: Call) -> Self { Self { signature: None, function } } } impl Decode for UncheckedExtrinsicV4 where Address: Decode, Signature: Decode, Call: Decode, Extra: Decode, { fn decode(input: &mut I) -> Result { // This is a little more complicated than usual since the binary format must be // compatible with SCALE's generic `Vec` type. Basically this just means accepting // that there will be a prefix of vector length. let expected_length: Compact = Decode::decode(input)?; let before_length = input.remaining_len()?; let version = input.read_byte()?; let is_signed = version & 0b1000_0000 != 0; let version = version & 0b0111_1111; if version != 4u8 { return Err("Invalid transaction version".into()); } let signature = is_signed.then(|| Decode::decode(input)).transpose()?; let function = Decode::decode(input)?; if let Some((before_length, after_length)) = input.remaining_len()?.and_then(|a| before_length.map(|b| (b, a))) { let length = before_length.saturating_sub(after_length); if length != expected_length.0 as usize { return Err("Invalid length prefix".into()); } } Ok(Self { signature, function }) } } #[docify::export(unchecked_extrinsic_encode_impl)] impl Encode for UncheckedExtrinsicV4 where Address: Encode, Signature: Encode, Call: Encode, Extra: Encode, { fn encode(&self) -> Vec { let mut tmp = Vec::with_capacity(core::mem::size_of::()); // 1 byte version id. match self.signature.as_ref() { Some(s) => { tmp.push(4u8 | 0b1000_0000); s.encode_to(&mut tmp); }, None => { tmp.push(4u8 & 0b0111_1111); }, } self.function.encode_to(&mut tmp); let compact_len = codec::Compact::(tmp.len() as u32); // Allocate the output buffer with the correct length let mut output = Vec::with_capacity(compact_len.size_hint() + tmp.len()); compact_len.encode_to(&mut output); output.extend(tmp); output } } impl EncodeLike for UncheckedExtrinsicV4 where Address: Encode, Signature: Encode, Call: Encode, Extra: Encode, { } } #[cfg(test)] mod tests { use super::{legacy::UncheckedExtrinsicV4, *}; use crate::{ codec::{Decode, Encode}, impl_tx_ext_default, testing::TestSignature as TestSig, traits::{FakeDispatchable, IdentityLookup, TransactionExtension}, }; use sp_io::hashing::blake2_256; type TestContext = IdentityLookup; type TestAccountId = u64; type TestCall = FakeDispatchable>; const TEST_ACCOUNT: TestAccountId = 0; // NOTE: this is demonstration. One can simply use `()` for testing. #[derive( Debug, Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, Ord, PartialOrd, TypeInfo, )] struct DummyExtension; impl TransactionExtension for DummyExtension { const IDENTIFIER: &'static str = "DummyExtension"; type Implicit = (); type Val = (); type Pre = (); impl_tx_ext_default!(TestCall; weight validate prepare); } type Ex = UncheckedExtrinsic; type CEx = CheckedExtrinsic; #[test] fn unsigned_codec_should_work() { let call: TestCall = vec![0u8; 0].into(); let ux = Ex::new_bare(call); let encoded = ux.encode(); assert_eq!(Ex::decode(&mut &encoded[..]), Ok(ux)); } #[test] fn invalid_length_prefix_is_detected() { let ux = Ex::new_bare(vec![0u8; 0].into()); let mut encoded = ux.encode(); let length = Compact::::decode(&mut &encoded[..]).unwrap(); Compact(length.0 + 10).encode_to(&mut &mut encoded[..1]); assert_eq!(Ex::decode(&mut &encoded[..]), Err("Invalid length prefix".into())); } #[test] fn transaction_codec_should_work() { let ux = Ex::new_transaction(vec![0u8; 0].into(), DummyExtension); let encoded = ux.encode(); assert_eq!(Ex::decode(&mut &encoded[..]), Ok(ux)); } #[test] fn signed_codec_should_work() { let ux = Ex::new_signed( vec![0u8; 0].into(), TEST_ACCOUNT, TestSig(TEST_ACCOUNT, (vec![0u8; 0], DummyExtension).encode()), DummyExtension, ); let encoded = ux.encode(); assert_eq!(Ex::decode(&mut &encoded[..]), Ok(ux)); } #[test] fn large_signed_codec_should_work() { let ux = Ex::new_signed( vec![0u8; 0].into(), TEST_ACCOUNT, TestSig( TEST_ACCOUNT, (vec![0u8; 257], DummyExtension).using_encoded(blake2_256)[..].to_owned(), ), DummyExtension, ); let encoded = ux.encode(); assert_eq!(Ex::decode(&mut &encoded[..]), Ok(ux)); } #[test] fn unsigned_check_should_work() { let ux = Ex::new_bare(vec![0u8; 0].into()); assert!(ux.is_inherent()); assert_eq!( >::check(ux, &Default::default()), Ok(CEx { format: ExtrinsicFormat::Bare, function: vec![0u8; 0].into() }), ); } #[test] fn badly_signed_check_should_fail() { let ux = Ex::new_signed( vec![0u8; 0].into(), TEST_ACCOUNT, TestSig(TEST_ACCOUNT, vec![0u8; 0].into()), DummyExtension, ); assert!(!ux.is_inherent()); assert_eq!( >::check(ux, &Default::default()), Err(InvalidTransaction::BadProof.into()), ); } #[test] fn transaction_check_should_work() { let ux = Ex::new_transaction(vec![0u8; 0].into(), DummyExtension); assert!(!ux.is_inherent()); assert_eq!( >::check(ux, &Default::default()), Ok(CEx { format: ExtrinsicFormat::General(0, DummyExtension), function: vec![0u8; 0].into() }), ); } #[test] fn signed_check_should_work() { let sig_payload = SignedPayload::from_raw( FakeDispatchable::from(vec![0u8; 0]), DummyExtension, DummyExtension.implicit().unwrap(), ); let ux = Ex::new_signed( vec![0u8; 0].into(), TEST_ACCOUNT, TestSig(TEST_ACCOUNT, sig_payload.encode()), DummyExtension, ); assert!(!ux.is_inherent()); assert_eq!( >::check(ux, &Default::default()), Ok(CEx { format: ExtrinsicFormat::Signed(TEST_ACCOUNT, DummyExtension), function: vec![0u8; 0].into() }), ); } #[test] fn encoding_matches_vec() { let ex = Ex::new_bare(vec![0u8; 0].into()); let encoded = ex.encode(); let decoded = Ex::decode(&mut encoded.as_slice()).unwrap(); assert_eq!(decoded, ex); let as_vec: Vec = Decode::decode(&mut encoded.as_slice()).unwrap(); assert_eq!(as_vec.encode(), encoded); } #[test] fn conversion_to_opaque() { let ux = Ex::new_bare(vec![0u8; 0].into()); let encoded = ux.encode(); let opaque: OpaqueExtrinsic = ux.into(); let opaque_encoded = opaque.encode(); assert_eq!(opaque_encoded, encoded); } #[test] fn large_bad_prefix_should_work() { let encoded = (Compact::::from(u32::MAX), Preamble::<(), (), ()>::Bare(0)).encode(); assert!(Ex::decode(&mut &encoded[..]).is_err()); } #[test] fn legacy_short_signed_encode_decode() { let call: TestCall = vec![0u8; 4].into(); let signed = TEST_ACCOUNT; let extension = DummyExtension; let implicit = extension.implicit().unwrap(); let legacy_signature = TestSig(TEST_ACCOUNT, (&call, &extension, &implicit).encode()); let old_ux = UncheckedExtrinsicV4::::new_signed( call.clone(), signed, legacy_signature.clone(), extension.clone(), ); let encoded_old_ux = old_ux.encode(); let decoded_old_ux = Ex::decode(&mut &encoded_old_ux[..]).unwrap(); assert_eq!(decoded_old_ux.function, call); assert_eq!( decoded_old_ux.preamble, Preamble::Signed(signed, legacy_signature.clone(), extension.clone()) ); let new_ux = Ex::new_signed(call.clone(), signed, legacy_signature.clone(), extension.clone()); let new_checked = new_ux.check(&IdentityLookup::::default()).unwrap(); let old_checked = decoded_old_ux.check(&IdentityLookup::::default()).unwrap(); assert_eq!(new_checked, old_checked); } #[test] fn legacy_long_signed_encode_decode() { let call: TestCall = vec![0u8; 257].into(); let signed = TEST_ACCOUNT; let extension = DummyExtension; let implicit = extension.implicit().unwrap(); let signature = TestSig( TEST_ACCOUNT, blake2_256(&(&call, DummyExtension, &implicit).encode()[..]).to_vec(), ); let old_ux = UncheckedExtrinsicV4::::new_signed( call.clone(), signed, signature.clone(), extension.clone(), ); let encoded_old_ux = old_ux.encode(); let decoded_old_ux = Ex::decode(&mut &encoded_old_ux[..]).unwrap(); assert_eq!(decoded_old_ux.function, call); assert_eq!( decoded_old_ux.preamble, Preamble::Signed(signed, signature.clone(), extension.clone()) ); let new_ux = Ex::new_signed(call.clone(), signed, signature.clone(), extension.clone()); let new_checked = new_ux.check(&IdentityLookup::::default()).unwrap(); let old_checked = decoded_old_ux.check(&IdentityLookup::::default()).unwrap(); assert_eq!(new_checked, old_checked); } #[test] fn legacy_unsigned_encode_decode() { let call: TestCall = vec![0u8; 0].into(); let old_ux = UncheckedExtrinsicV4::::new_unsigned( call.clone(), ); let encoded_old_ux = old_ux.encode(); let decoded_old_ux = Ex::decode(&mut &encoded_old_ux[..]).unwrap(); assert_eq!(decoded_old_ux.function, call); assert_eq!(decoded_old_ux.preamble, Preamble::Bare(LEGACY_EXTRINSIC_FORMAT_VERSION)); let new_legacy_ux = Ex::new_bare_legacy(call.clone()); assert_eq!(encoded_old_ux, new_legacy_ux.encode()); let new_ux = Ex::new_bare(call.clone()); let encoded_new_ux = new_ux.encode(); let decoded_new_ux = Ex::decode(&mut &encoded_new_ux[..]).unwrap(); assert_eq!(new_ux, decoded_new_ux); let new_checked = new_ux.check(&IdentityLookup::::default()).unwrap(); let old_checked = decoded_old_ux.check(&IdentityLookup::::default()).unwrap(); assert_eq!(new_checked, old_checked); } #[test] fn max_call_heap_size_should_be_checked() { // Should be able to decode an `UncheckedExtrinsic` that contains a call with // heap size < `MAX_CALL_HEAP_SIZE` let ux = Ex::new_bare(vec![0u8; DEFAULT_MAX_CALL_SIZE].into()); let encoded = ux.encode(); assert_eq!(Ex::decode(&mut &encoded[..]), Ok(ux)); // Otherwise should fail let ux = Ex::new_bare(vec![0u8; DEFAULT_MAX_CALL_SIZE + 1].into()); let encoded = ux.encode(); assert_eq!( Ex::decode(&mut &encoded[..]).unwrap_err().to_string(), "Could not decode `FakeDispatchable.0`:\n\tHeap memory limit exceeded while decoding\n" ); } }