use crate::{
generic::{CheckedExtrinsic, ExtrinsicFormat},
traits::{
self, transaction_extension::TransactionExtension, Checkable, Dispatchable, ExtrinsicLike,
ExtrinsicMetadata, IdentifyAccount, 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, Decode, Encode, EncodeLike, Error, 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;
pub type ExtensionVersion = u8;
pub type ExtrinsicVersion = u8;
pub const EXTRINSIC_FORMAT_VERSION: ExtrinsicVersion = 5;
pub const LEGACY_EXTRINSIC_FORMAT_VERSION: ExtrinsicVersion = 4;
const EXTENSION_VERSION: ExtensionVersion = 0;
pub type UncheckedSignaturePayload<Address, Signature, Extension> = (Address, Signature, Extension);
impl<Address: TypeInfo, Signature: TypeInfo, Extension: TypeInfo> SignaturePayload
for UncheckedSignaturePayload<Address, Signature, Extension>
{
type SignatureAddress = Address;
type Signature = Signature;
type SignatureExtra = Extension;
}
#[derive(Eq, PartialEq, Clone)]
pub enum Preamble<Address, Signature, Extension> {
Bare(ExtrinsicVersion),
Signed(Address, Signature, Extension),
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<Address, Signature, Extension> Decode for Preamble<Address, Signature, Extension>
where
Address: Decode,
Signature: Decode,
Extension: Decode,
{
fn decode<I: Input>(input: &mut I) -> Result<Self, Error> {
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<Address, Signature, Extension> Encode for Preamble<Address, Signature, Extension>
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<T: codec::Output + ?Sized>(&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<Address, Signature, Extension> Preamble<Address, Signature, Extension> {
pub fn to_signed(self) -> Option<(Address, Signature, Extension)> {
match self {
Self::Signed(a, s, e) => Some((a, s, e)),
_ => None,
}
}
}
impl<Address, Signature, Extension> fmt::Debug for Preamble<Address, Signature, Extension>
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),
}
}
}
#[cfg_attr(all(feature = "std", not(windows)), doc = simple_mermaid::mermaid!("../../docs/mermaid/extrinsics.mmd"))]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct UncheckedExtrinsic<Address, Call, Signature, Extension> {
pub preamble: Preamble<Address, Signature, Extension>,
pub function: Call,
}
impl<Address, Call, Signature, Extension> TypeInfo
for UncheckedExtrinsic<Address, Call, Signature, Extension>
where
Address: StaticTypeInfo,
Call: StaticTypeInfo,
Signature: StaticTypeInfo,
Extension: StaticTypeInfo,
{
type Identity = UncheckedExtrinsic<Address, Call, Signature, Extension>;
fn type_info() -> Type {
Type::builder()
.path(Path::new("UncheckedExtrinsic", module_path!()))
.type_params(vec![
TypeParameter::new("Address", Some(meta_type::<Address>())),
TypeParameter::new("Call", Some(meta_type::<Call>())),
TypeParameter::new("Signature", Some(meta_type::<Signature>())),
TypeParameter::new("Extra", Some(meta_type::<Extension>())),
])
.docs(&["UncheckedExtrinsic raw bytes, requires custom decoding routine"])
.composite(Fields::unnamed().field(|f| f.ty::<Vec<u8>>()))
}
}
impl<Address, Call, Signature, Extension> UncheckedExtrinsic<Address, Call, Signature, Extension> {
#[deprecated = "Use new_bare instead"]
pub fn new_unsigned(function: Call) -> Self {
Self::new_bare(function)
}
pub fn is_inherent(&self) -> bool {
matches!(self.preamble, Preamble::Bare(_))
}
pub fn is_signed(&self) -> bool {
matches!(self.preamble, Preamble::Signed(..))
}
pub fn from_parts(function: Call, preamble: Preamble<Address, Signature, Extension>) -> Self {
Self { preamble, function }
}
pub fn new_bare(function: Call) -> Self {
Self { preamble: Preamble::Bare(EXTRINSIC_FORMAT_VERSION), function }
}
pub fn new_bare_legacy(function: Call) -> Self {
Self { preamble: Preamble::Bare(LEGACY_EXTRINSIC_FORMAT_VERSION), function }
}
pub fn new_signed(
function: Call,
signed: Address,
signature: Signature,
tx_ext: Extension,
) -> Self {
Self { preamble: Preamble::Signed(signed, signature, tx_ext), function }
}
pub fn new_transaction(function: Call, tx_ext: Extension) -> Self {
Self { preamble: Preamble::General(EXTENSION_VERSION, tx_ext), function }
}
}
impl<Address: TypeInfo, Call: TypeInfo, Signature: TypeInfo, Extension: TypeInfo> ExtrinsicLike
for UncheckedExtrinsic<Address, Call, Signature, Extension>
{
fn is_bare(&self) -> bool {
matches!(self.preamble, Preamble::Bare(_))
}
fn is_signed(&self) -> Option<bool> {
Some(matches!(self.preamble, Preamble::Signed(..)))
}
}
impl<LookupSource, AccountId, Call, Signature, Extension, Lookup> Checkable<Lookup>
for UncheckedExtrinsic<LookupSource, Call, Signature, Extension>
where
LookupSource: Member + MaybeDisplay,
Call: Encode + Member + Dispatchable,
Signature: Member + traits::Verify,
<Signature as traits::Verify>::Signer: IdentifyAccount<AccountId = AccountId>,
Extension: Encode + TransactionExtension<Call>,
AccountId: Member + MaybeDisplay,
Lookup: traits::Lookup<Source = LookupSource, Target = AccountId>,
{
type Checked = CheckedExtrinsic<AccountId, Call, Extension>;
fn check(self, lookup: &Lookup) -> Result<Self::Checked, TransactionValidityError> {
Ok(match self.preamble {
Preamble::Signed(signed, signature, tx_ext) => {
let signed = lookup.lookup(signed)?;
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<Self::Checked, TransactionValidityError> {
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<Address, Call: Dispatchable, Signature, Extension: TransactionExtension<Call>>
ExtrinsicMetadata for UncheckedExtrinsic<Address, Call, Signature, Extension>
{
const VERSIONS: &'static [u8] = &[LEGACY_EXTRINSIC_FORMAT_VERSION, EXTRINSIC_FORMAT_VERSION];
type TransactionExtensions = Extension;
}
impl<Address, Call: Dispatchable, Signature, Extension: TransactionExtension<Call>>
UncheckedExtrinsic<Address, Call, Signature, Extension>
{
pub fn extension_weight(&self) -> Weight {
match &self.preamble {
Preamble::Bare(_) => Weight::zero(),
Preamble::Signed(_, _, ext) | Preamble::General(_, ext) => ext.weight(&self.function),
}
}
}
impl<Address, Call, Signature, Extension> Decode
for UncheckedExtrinsic<Address, Call, Signature, Extension>
where
Address: Decode,
Signature: Decode,
Call: Decode,
Extension: Decode,
{
fn decode<I: Input>(input: &mut I) -> Result<Self, Error> {
let expected_length: Compact<u32> = Decode::decode(input)?;
let before_length = input.remaining_len()?;
let preamble = Decode::decode(input)?;
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 { preamble, function })
}
}
#[docify::export(unchecked_extrinsic_encode_impl)]
impl<Address, Call, Signature, Extension> Encode
for UncheckedExtrinsic<Address, Call, Signature, Extension>
where
Preamble<Address, Signature, Extension>: Encode,
Call: Encode,
Extension: Encode,
{
fn encode(&self) -> Vec<u8> {
let mut tmp = self.preamble.encode();
self.function.encode_to(&mut tmp);
let compact_len = codec::Compact::<u32>(tmp.len() as u32);
let mut output = Vec::with_capacity(compact_len.size_hint() + tmp.len());
compact_len.encode_to(&mut output);
output.extend(tmp);
output
}
}
impl<Address, Call, Signature, Extension> EncodeLike
for UncheckedExtrinsic<Address, Call, Signature, Extension>
where
Address: Encode,
Signature: Encode,
Call: Encode + Dispatchable,
Extension: TransactionExtension<Call>,
{
}
#[cfg(feature = "serde")]
impl<Address: Encode, Signature: Encode, Call: Encode, Extension: Encode> serde::Serialize
for UncheckedExtrinsic<Address, Call, Signature, Extension>
{
fn serialize<S>(&self, seq: S) -> Result<S::Ok, S::Error>
where
S: ::serde::Serializer,
{
self.using_encoded(|bytes| seq.serialize_bytes(bytes))
}
}
#[cfg(feature = "serde")]
impl<'a, Address: Decode, Signature: Decode, Call: Decode, Extension: Decode> serde::Deserialize<'a>
for UncheckedExtrinsic<Address, Call, Signature, Extension>
{
fn deserialize<D>(de: D) -> Result<Self, D::Error>
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)))
}
}
pub struct SignedPayload<Call: Dispatchable, Extension: TransactionExtension<Call>>(
(Call, Extension, Extension::Implicit),
);
impl<Call, Extension> SignedPayload<Call, Extension>
where
Call: Encode + Dispatchable,
Extension: TransactionExtension<Call>,
{
pub fn new(call: Call, tx_ext: Extension) -> Result<Self, TransactionValidityError> {
let implicit = Extension::implicit(&tx_ext)?;
let raw_payload = (call, tx_ext, implicit);
Ok(Self(raw_payload))
}
pub fn from_raw(call: Call, tx_ext: Extension, implicit: Extension::Implicit) -> Self {
Self((call, tx_ext, implicit))
}
pub fn deconstruct(self) -> (Call, Extension, Extension::Implicit) {
self.0
}
}
impl<Call, Extension> Encode for SignedPayload<Call, Extension>
where
Call: Encode + Dispatchable,
Extension: TransactionExtension<Call>,
{
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
self.0.using_encoded(|payload| {
if payload.len() > 256 {
f(&blake2_256(payload)[..])
} else {
f(payload)
}
})
}
}
impl<Call, Extension> EncodeLike for SignedPayload<Call, Extension>
where
Call: Encode + Dispatchable,
Extension: TransactionExtension<Call>,
{
}
impl<Address, Call, Signature, Extension>
From<UncheckedExtrinsic<Address, Call, Signature, Extension>> for OpaqueExtrinsic
where
Address: Encode,
Signature: Encode,
Call: Encode,
Extension: Encode,
{
fn from(extrinsic: UncheckedExtrinsic<Address, Call, Signature, Extension>) -> Self {
Self::from_bytes(extrinsic.encode().as_slice()).expect(
"both OpaqueExtrinsic and UncheckedExtrinsic have encoding that is compatible with \
raw Vec<u8> encoding; qed",
)
}
}
#[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> = (Address, Signature, Extra);
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct UncheckedExtrinsicV4<Address, Call, Signature, Extra> {
pub signature: Option<UncheckedSignaturePayloadV4<Address, Signature, Extra>>,
pub function: Call,
}
impl<Address, Call, Signature, Extra> TypeInfo
for UncheckedExtrinsicV4<Address, Call, Signature, Extra>
where
Address: StaticTypeInfo,
Call: StaticTypeInfo,
Signature: StaticTypeInfo,
Extra: StaticTypeInfo,
{
type Identity = UncheckedExtrinsicV4<Address, Call, Signature, Extra>;
fn type_info() -> Type {
Type::builder()
.path(Path::new("UncheckedExtrinsic", module_path!()))
.type_params(vec![
TypeParameter::new("Address", Some(meta_type::<Address>())),
TypeParameter::new("Call", Some(meta_type::<Call>())),
TypeParameter::new("Signature", Some(meta_type::<Signature>())),
TypeParameter::new("Extra", Some(meta_type::<Extra>())),
])
.docs(&["OldUncheckedExtrinsic raw bytes, requires custom decoding routine"])
.composite(Fields::unnamed().field(|f| f.ty::<Vec<u8>>()))
}
}
impl<Address, Call, Signature, Extra> UncheckedExtrinsicV4<Address, Call, Signature, Extra> {
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<Address, Call, Signature, Extra> Decode
for UncheckedExtrinsicV4<Address, Call, Signature, Extra>
where
Address: Decode,
Signature: Decode,
Call: Decode,
Extra: Decode,
{
fn decode<I: Input>(input: &mut I) -> Result<Self, Error> {
let expected_length: Compact<u32> = 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<Address, Call, Signature, Extra> Encode
for UncheckedExtrinsicV4<Address, Call, Signature, Extra>
where
Address: Encode,
Signature: Encode,
Call: Encode,
Extra: Encode,
{
fn encode(&self) -> Vec<u8> {
let mut tmp = Vec::with_capacity(sp_std::mem::size_of::<Self>());
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::<u32>(tmp.len() as u32);
let mut output = Vec::with_capacity(compact_len.size_hint() + tmp.len());
compact_len.encode_to(&mut output);
output.extend(tmp);
output
}
}
impl<Address, Call, Signature, Extra> EncodeLike
for UncheckedExtrinsicV4<Address, Call, Signature, Extra>
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<u64>;
type TestAccountId = u64;
type TestCall = FakeDispatchable<Vec<u8>>;
const TEST_ACCOUNT: TestAccountId = 0;
#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, Ord, PartialOrd, TypeInfo)]
struct DummyExtension;
impl TransactionExtension<TestCall> for DummyExtension {
const IDENTIFIER: &'static str = "DummyExtension";
type Implicit = ();
type Val = ();
type Pre = ();
impl_tx_ext_default!(TestCall; weight validate prepare);
}
type Ex = UncheckedExtrinsic<TestAccountId, TestCall, TestSig, DummyExtension>;
type CEx = CheckedExtrinsic<TestAccountId, TestCall, DummyExtension>;
#[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::<u32>::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!(
<Ex as Checkable<TestContext>>::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!(
<Ex as Checkable<TestContext>>::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!(
<Ex as Checkable<TestContext>>::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!(
<Ex as Checkable<TestContext>>::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<u8> = 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::<u32>::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::<TestAccountId, TestCall, TestSig, DummyExtension>::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::<TestAccountId>::default()).unwrap();
let old_checked =
decoded_old_ux.check(&IdentityLookup::<TestAccountId>::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::<TestAccountId, TestCall, TestSig, DummyExtension>::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::<TestAccountId>::default()).unwrap();
let old_checked =
decoded_old_ux.check(&IdentityLookup::<TestAccountId>::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::<TestAccountId, TestCall, TestSig, DummyExtension>::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::<TestAccountId>::default()).unwrap();
let old_checked =
decoded_old_ux.check(&IdentityLookup::<TestAccountId>::default()).unwrap();
assert_eq!(new_checked, old_checked);
}
}