mod transaction_extension;
mod transaction_extensions;
use super::extrinsic_type_info::{
ExtrinsicCallInfo, ExtrinsicExtensionInfo, ExtrinsicInfoError, ExtrinsicSignatureInfo,
ExtrinsicTypeInfo,
};
use alloc::vec::Vec;
use parity_scale_codec::Encode;
use scale_encode::{EncodeAsFields, EncodeAsType};
use scale_type_resolver::{Field, TypeResolver};
pub use transaction_extension::{TransactionExtension, TransactionExtensionError};
pub use transaction_extensions::{TransactionExtensions, TransactionExtensionsError};
#[non_exhaustive]
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
pub enum ExtrinsicEncodeError {
#[error("Cannot get extrinsic info: {0}")]
CannotGetInfo(ExtrinsicInfoError<'static>),
#[error("Extrinsic encoding failed: cannot encode call data: {0}")]
CannotEncodeCallData(scale_encode::Error),
#[error("Extrinsic encoding failed: cannot encode address: {0}")]
CannotEncodeAddress(scale_encode::Error),
#[error("Extrinsic encoding failed: cannot encode signature: {0}")]
CannotEncodeSignature(scale_encode::Error),
#[error("Extrinsic encoding failed: cannot encode transaction extensions: {0}")]
TransactionExtensions(TransactionExtensionsError),
#[error(
"Extrinsic encoding failed: cannot find a transaction extensions version which relies only on the transaction extensions we were given."
)]
CannotFindGoodExtensionVersion,
}
pub fn encode_v4_unsigned<CallData, Info, Resolver>(
pallet_name: &str,
call_name: &str,
call_data: &CallData,
info: &Info,
type_resolver: &Resolver,
) -> Result<Vec<u8>, ExtrinsicEncodeError>
where
CallData: EncodeAsFields,
Resolver: TypeResolver<TypeId = Info::TypeId>,
Info: ExtrinsicTypeInfo,
{
let mut out = Vec::new();
encode_v4_unsigned_to(
pallet_name,
call_name,
call_data,
info,
type_resolver,
&mut out,
)?;
Ok(out)
}
pub fn encode_v4_unsigned_to<CallData, Info, Resolver>(
pallet_name: &str,
call_name: &str,
call_data: &CallData,
info: &Info,
type_resolver: &Resolver,
out: &mut Vec<u8>,
) -> Result<(), ExtrinsicEncodeError>
where
CallData: EncodeAsFields,
Resolver: TypeResolver<TypeId = Info::TypeId>,
Info: ExtrinsicTypeInfo,
{
let call_info = info
.extrinsic_call_info_by_name(pallet_name, call_name)
.map_err(|i| i.into_owned())
.map_err(ExtrinsicEncodeError::CannotGetInfo)?;
encode_v4_unsigned_with_info_to(call_data, type_resolver, &call_info, out)
}
pub fn encode_v4_unsigned_with_info_to<CallData, Resolver>(
call_data: &CallData,
type_resolver: &Resolver,
call_info: &ExtrinsicCallInfo<Resolver::TypeId>,
out: &mut Vec<u8>,
) -> Result<(), ExtrinsicEncodeError>
where
CallData: EncodeAsFields,
Resolver: TypeResolver,
{
encode_unsigned_at_version_with_info_to(
call_data,
call_info,
type_resolver,
TransactionVersion::V4,
out,
)
}
#[allow(clippy::too_many_arguments)]
pub fn encode_v4_signed<CallData, Info, Resolver, Exts, Address, Signature>(
pallet_name: &str,
call_name: &str,
call_data: &CallData,
transaction_extensions: &Exts,
address: &Address,
signature: &Signature,
info: &Info,
type_resolver: &Resolver,
) -> Result<Vec<u8>, ExtrinsicEncodeError>
where
CallData: EncodeAsFields,
Resolver: TypeResolver<TypeId = Info::TypeId>,
Info: ExtrinsicTypeInfo,
Exts: TransactionExtensions<Resolver>,
Address: EncodeAsType,
Signature: EncodeAsType,
{
let mut out = Vec::new();
encode_v4_signed_to(
pallet_name,
call_name,
call_data,
transaction_extensions,
address,
signature,
info,
type_resolver,
&mut out,
)?;
Ok(out)
}
#[allow(clippy::too_many_arguments)]
pub fn encode_v4_signed_to<CallData, Info, Resolver, Exts, Address, Signature>(
pallet_name: &str,
call_name: &str,
call_data: &CallData,
transaction_extensions: &Exts,
address: &Address,
signature: &Signature,
info: &Info,
type_resolver: &Resolver,
out: &mut Vec<u8>,
) -> Result<(), ExtrinsicEncodeError>
where
CallData: EncodeAsFields,
Resolver: TypeResolver<TypeId = Info::TypeId>,
Info: ExtrinsicTypeInfo,
Exts: TransactionExtensions<Resolver>,
Address: EncodeAsType,
Signature: EncodeAsType,
{
let call_info = info
.extrinsic_call_info_by_name(pallet_name, call_name)
.map_err(|i| i.into_owned())
.map_err(ExtrinsicEncodeError::CannotGetInfo)?;
let ext_info = info
.extrinsic_extension_info(None)
.map_err(|i| i.into_owned())
.map_err(ExtrinsicEncodeError::CannotGetInfo)?;
let sig_info = info
.extrinsic_signature_info()
.map_err(|i| i.into_owned())
.map_err(ExtrinsicEncodeError::CannotGetInfo)?;
encode_v4_signed_with_info_to(
call_data,
transaction_extensions,
address,
signature,
type_resolver,
&call_info,
&sig_info,
&ext_info,
out,
)
}
#[allow(clippy::too_many_arguments)]
pub fn encode_v4_signed_with_info_to<CallData, Resolver, Exts, Address, Signature>(
call_data: &CallData,
transaction_extensions: &Exts,
address: &Address,
signature: &Signature,
type_resolver: &Resolver,
call_info: &ExtrinsicCallInfo<Resolver::TypeId>,
sig_info: &ExtrinsicSignatureInfo<Resolver::TypeId>,
ext_info: &ExtrinsicExtensionInfo<Resolver::TypeId>,
out: &mut Vec<u8>,
) -> Result<(), ExtrinsicEncodeError>
where
CallData: EncodeAsFields,
Resolver: TypeResolver,
Exts: TransactionExtensions<Resolver>,
Address: EncodeAsType,
Signature: EncodeAsType,
{
let mut encoded_inner = Vec::new();
(0b10000000 + 4u8).encode_to(&mut encoded_inner);
address
.encode_as_type_to(
sig_info.address_id.clone(),
type_resolver,
&mut encoded_inner,
)
.map_err(ExtrinsicEncodeError::CannotEncodeAddress)?;
signature
.encode_as_type_to(
sig_info.signature_id.clone(),
type_resolver,
&mut encoded_inner,
)
.map_err(ExtrinsicEncodeError::CannotEncodeSignature)?;
encode_transaction_extension_values(
ext_info,
transaction_extensions,
type_resolver,
&mut encoded_inner,
)
.map_err(ExtrinsicEncodeError::TransactionExtensions)?;
encode_call_data_with_info_to(call_data, call_info, type_resolver, &mut encoded_inner)?;
encoded_inner.encode_to(out);
Ok(())
}
pub fn encode_v4_signer_payload<CallData, Info, Resolver, Exts>(
pallet_name: &str,
call_name: &str,
call_data: &CallData,
transaction_extensions: &Exts,
info: &Info,
type_resolver: &Resolver,
) -> Result<Vec<u8>, ExtrinsicEncodeError>
where
CallData: EncodeAsFields,
Resolver: TypeResolver<TypeId = Info::TypeId>,
Info: ExtrinsicTypeInfo,
Exts: TransactionExtensions<Resolver>,
Info::TypeId: Clone,
{
let call_info = info
.extrinsic_call_info_by_name(pallet_name, call_name)
.map_err(|i| i.into_owned())
.map_err(ExtrinsicEncodeError::CannotGetInfo)?;
let ext_info = info
.extrinsic_extension_info(None)
.map_err(|i| i.into_owned())
.map_err(ExtrinsicEncodeError::CannotGetInfo)?;
encode_v4_signer_payload_with_info(
call_data,
transaction_extensions,
type_resolver,
&call_info,
&ext_info,
)
}
pub fn encode_v4_signer_payload_with_info<CallData, Resolver, Exts>(
call_data: &CallData,
transaction_extensions: &Exts,
type_resolver: &Resolver,
call_info: &ExtrinsicCallInfo<Resolver::TypeId>,
ext_info: &ExtrinsicExtensionInfo<Resolver::TypeId>,
) -> Result<Vec<u8>, ExtrinsicEncodeError>
where
CallData: EncodeAsFields,
Resolver: TypeResolver,
Exts: TransactionExtensions<Resolver>,
{
let mut out = Vec::new();
encode_call_data_with_info_to(call_data, call_info, type_resolver, &mut out)?;
encode_transaction_extension_values(ext_info, transaction_extensions, type_resolver, &mut out)
.map_err(ExtrinsicEncodeError::TransactionExtensions)?;
encode_transaction_extension_implicits(
ext_info,
transaction_extensions,
type_resolver,
&mut out,
)
.map_err(ExtrinsicEncodeError::TransactionExtensions)?;
if out.len() > 256 {
out = sp_crypto_hashing::blake2_256(&out).to_vec();
}
Ok(out)
}
pub fn encode_v5_bare<CallData, Info, Resolver>(
pallet_name: &str,
call_name: &str,
call_data: &CallData,
info: &Info,
type_resolver: &Resolver,
) -> Result<Vec<u8>, ExtrinsicEncodeError>
where
CallData: EncodeAsFields,
Resolver: TypeResolver<TypeId = Info::TypeId>,
Info: ExtrinsicTypeInfo,
{
let mut out = Vec::new();
encode_v5_bare_to(
pallet_name,
call_name,
call_data,
info,
type_resolver,
&mut out,
)?;
Ok(out)
}
pub fn encode_v5_bare_to<CallData, Info, Resolver>(
pallet_name: &str,
call_name: &str,
call_data: &CallData,
info: &Info,
type_resolver: &Resolver,
out: &mut Vec<u8>,
) -> Result<(), ExtrinsicEncodeError>
where
CallData: EncodeAsFields,
Resolver: TypeResolver<TypeId = Info::TypeId>,
Info: ExtrinsicTypeInfo,
{
let call_info = info
.extrinsic_call_info_by_name(pallet_name, call_name)
.map_err(|i| i.into_owned())
.map_err(ExtrinsicEncodeError::CannotGetInfo)?;
encode_v5_bare_with_info_to(call_data, type_resolver, &call_info, out)
}
pub fn encode_v5_bare_with_info_to<CallData, Resolver>(
call_data: &CallData,
type_resolver: &Resolver,
call_info: &ExtrinsicCallInfo<Resolver::TypeId>,
out: &mut Vec<u8>,
) -> Result<(), ExtrinsicEncodeError>
where
CallData: EncodeAsFields,
Resolver: TypeResolver,
{
encode_unsigned_at_version_with_info_to(
call_data,
call_info,
type_resolver,
TransactionVersion::V5,
out,
)
}
pub fn best_v5_general_transaction_extension_version<Exts, Info, Resolver>(
transaction_extensions: &Exts,
info: &Info,
type_resolver: &Resolver,
) -> Result<u8, ExtrinsicEncodeError>
where
Info: ExtrinsicTypeInfo,
Exts: TransactionExtensions<Resolver>,
Resolver: TypeResolver<TypeId = Info::TypeId>,
Info::TypeId: Clone,
{
let extension_versions = info
.extrinsic_extension_version_info()
.map_err(|i| i.into_owned())
.map_err(ExtrinsicEncodeError::CannotGetInfo)?;
for ext_version in extension_versions {
let ext_info = info
.extrinsic_extension_info(Some(ext_version))
.map_err(|i| i.into_owned())
.map_err(ExtrinsicEncodeError::CannotGetInfo)?;
let have_data = ext_info.extension_ids.iter().all(|e| {
let is_value_empty = is_type_empty(e.id.clone(), type_resolver);
let is_value_option = is_type_option(e.id.clone(), type_resolver);
let is_implicit_empty = is_type_empty(e.implicit_id.clone(), type_resolver);
((is_value_empty || is_value_option) && is_implicit_empty)
|| transaction_extensions.contains_extension(&e.name)
});
if have_data {
return Ok(ext_version);
}
}
Err(ExtrinsicEncodeError::CannotFindGoodExtensionVersion)
}
pub fn encode_v5_general<CallData, Info, Resolver, Exts>(
pallet_name: &str,
call_name: &str,
call_data: &CallData,
transaction_extension_version: u8,
transaction_extensions: &Exts,
info: &Info,
type_resolver: &Resolver,
) -> Result<Vec<u8>, ExtrinsicEncodeError>
where
CallData: EncodeAsFields,
Resolver: TypeResolver<TypeId = Info::TypeId>,
Info: ExtrinsicTypeInfo,
Exts: TransactionExtensions<Resolver>,
{
let mut out = Vec::new();
encode_v5_general_to(
pallet_name,
call_name,
call_data,
transaction_extension_version,
transaction_extensions,
info,
type_resolver,
&mut out,
)?;
Ok(out)
}
#[allow(clippy::too_many_arguments)]
pub fn encode_v5_general_to<CallData, Info, Resolver, Exts>(
pallet_name: &str,
call_name: &str,
call_data: &CallData,
transaction_extension_version: u8,
transaction_extensions: &Exts,
info: &Info,
type_resolver: &Resolver,
out: &mut Vec<u8>,
) -> Result<(), ExtrinsicEncodeError>
where
CallData: EncodeAsFields,
Resolver: TypeResolver<TypeId = Info::TypeId>,
Info: ExtrinsicTypeInfo,
Exts: TransactionExtensions<Resolver>,
{
let call_info = info
.extrinsic_call_info_by_name(pallet_name, call_name)
.map_err(|i| i.into_owned())
.map_err(ExtrinsicEncodeError::CannotGetInfo)?;
let ext_info = info
.extrinsic_extension_info(Some(transaction_extension_version))
.map_err(|i| i.into_owned())
.map_err(ExtrinsicEncodeError::CannotGetInfo)?;
encode_v5_general_with_info_to(
call_data,
transaction_extension_version,
transaction_extensions,
type_resolver,
&call_info,
&ext_info,
out,
)
}
pub fn encode_v5_general_with_info_to<CallData, Resolver, Exts>(
call_data: &CallData,
transaction_extension_version: u8,
transaction_extensions: &Exts,
type_resolver: &Resolver,
call_info: &ExtrinsicCallInfo<Resolver::TypeId>,
ext_info: &ExtrinsicExtensionInfo<Resolver::TypeId>,
out: &mut Vec<u8>,
) -> Result<(), ExtrinsicEncodeError>
where
CallData: EncodeAsFields,
Resolver: TypeResolver,
Exts: TransactionExtensions<Resolver>,
{
let mut encoded_inner = Vec::new();
(0b01000000 + 5u8).encode_to(&mut encoded_inner);
transaction_extension_version.encode_to(&mut encoded_inner);
encode_transaction_extension_values(
ext_info,
transaction_extensions,
type_resolver,
&mut encoded_inner,
)
.map_err(ExtrinsicEncodeError::TransactionExtensions)?;
encode_call_data_with_info_to(call_data, call_info, type_resolver, &mut encoded_inner)?;
encoded_inner.encode_to(out);
Ok(())
}
pub fn encode_v5_signer_payload<CallData, Info, Resolver, Exts>(
pallet_name: &str,
call_name: &str,
call_data: &CallData,
transaction_extension_version: u8,
transaction_extensions: &Exts,
info: &Info,
type_resolver: &Resolver,
) -> Result<[u8; 32], ExtrinsicEncodeError>
where
CallData: EncodeAsFields,
Resolver: TypeResolver<TypeId = Info::TypeId>,
Info: ExtrinsicTypeInfo,
Exts: TransactionExtensions<Resolver>,
Info::TypeId: Clone,
{
let call_info = info
.extrinsic_call_info_by_name(pallet_name, call_name)
.map_err(|i| i.into_owned())
.map_err(ExtrinsicEncodeError::CannotGetInfo)?;
let ext_info = info
.extrinsic_extension_info(Some(transaction_extension_version))
.map_err(|i| i.into_owned())
.map_err(ExtrinsicEncodeError::CannotGetInfo)?;
encode_v5_signer_payload_with_info(
call_data,
transaction_extensions,
type_resolver,
&call_info,
&ext_info,
)
}
pub fn encode_v5_signer_payload_with_info<CallData, Resolver, Exts>(
call_data: &CallData,
transaction_extensions: &Exts,
type_resolver: &Resolver,
call_info: &ExtrinsicCallInfo<Resolver::TypeId>,
ext_info: &ExtrinsicExtensionInfo<Resolver::TypeId>,
) -> Result<[u8; 32], ExtrinsicEncodeError>
where
CallData: EncodeAsFields,
Resolver: TypeResolver,
Exts: TransactionExtensions<Resolver>,
{
let mut out = Vec::new();
encode_call_data_with_info_to(call_data, call_info, type_resolver, &mut out)?;
encode_transaction_extension_values(ext_info, transaction_extensions, type_resolver, &mut out)
.map_err(ExtrinsicEncodeError::TransactionExtensions)?;
encode_transaction_extension_implicits(
ext_info,
transaction_extensions,
type_resolver,
&mut out,
)
.map_err(ExtrinsicEncodeError::TransactionExtensions)?;
Ok(sp_crypto_hashing::blake2_256(&out))
}
pub fn encode_call_data<CallData, Info, Resolver>(
pallet_name: &str,
call_name: &str,
call_data: &CallData,
info: &Info,
type_resolver: &Resolver,
) -> Result<Vec<u8>, ExtrinsicEncodeError>
where
CallData: EncodeAsFields,
Resolver: TypeResolver<TypeId = Info::TypeId>,
Info: ExtrinsicTypeInfo,
{
let mut out = Vec::new();
encode_call_data_to(
pallet_name,
call_name,
call_data,
info,
type_resolver,
&mut out,
)?;
Ok(out)
}
pub fn encode_call_data_to<CallData, Info, Resolver>(
pallet_name: &str,
call_name: &str,
call_data: &CallData,
info: &Info,
type_resolver: &Resolver,
out: &mut Vec<u8>,
) -> Result<(), ExtrinsicEncodeError>
where
CallData: EncodeAsFields,
Resolver: TypeResolver<TypeId = Info::TypeId>,
Info: ExtrinsicTypeInfo,
{
let call_info = info
.extrinsic_call_info_by_name(pallet_name, call_name)
.map_err(|i| i.into_owned())
.map_err(ExtrinsicEncodeError::CannotGetInfo)?;
encode_call_data_with_info_to(call_data, &call_info, type_resolver, out)
}
pub fn encode_call_data_with_info<CallData, Info, Resolver>(
call_data: &CallData,
call_info: &ExtrinsicCallInfo<Resolver::TypeId>,
type_resolver: &Resolver,
) -> Result<Vec<u8>, ExtrinsicEncodeError>
where
Resolver: TypeResolver,
CallData: EncodeAsFields,
{
let mut out = Vec::new();
encode_call_data_with_info_to(call_data, call_info, type_resolver, &mut out)?;
Ok(out)
}
pub fn encode_call_data_with_info_to<CallData, Resolver>(
call_data: &CallData,
call_info: &ExtrinsicCallInfo<Resolver::TypeId>,
type_resolver: &Resolver,
out: &mut Vec<u8>,
) -> Result<(), ExtrinsicEncodeError>
where
Resolver: TypeResolver,
CallData: EncodeAsFields,
{
call_info.pallet_index.encode_to(out);
call_info.call_index.encode_to(out);
let mut fields = call_info.args.iter().map(|arg| Field {
name: Some(&*arg.name),
id: arg.id.clone(),
});
call_data
.encode_as_fields_to(&mut fields, type_resolver, out)
.map_err(ExtrinsicEncodeError::CannotEncodeCallData)?;
Ok(())
}
fn encode_unsigned_at_version_with_info_to<CallData, Resolver>(
call_data: &CallData,
call_info: &ExtrinsicCallInfo<Resolver::TypeId>,
type_resolver: &Resolver,
tx_version: TransactionVersion,
out: &mut Vec<u8>,
) -> Result<(), ExtrinsicEncodeError>
where
Resolver: TypeResolver,
CallData: EncodeAsFields,
{
let inner = {
let mut out = Vec::new();
(tx_version as u8).encode_to(&mut out);
encode_call_data_with_info_to(call_data, call_info, type_resolver, &mut out)?;
out
};
inner.encode_to(out);
Ok(())
}
#[derive(Copy, Clone)]
#[repr(u8)]
enum TransactionVersion {
V4 = 4u8,
V5 = 5u8,
}
fn encode_transaction_extension_values<'exts, 'info, Resolver, Exts>(
ext_info: &'exts ExtrinsicExtensionInfo<'info, <Resolver as TypeResolver>::TypeId>,
transaction_extensions: &Exts,
type_resolver: &Resolver,
out: &mut Vec<u8>,
) -> Result<(), TransactionExtensionsError>
where
Resolver: TypeResolver,
Exts: TransactionExtensions<Resolver>,
{
let nonempty_values = ext_info
.extension_ids
.iter()
.filter(|arg| !is_type_empty(arg.id.clone(), type_resolver))
.map(|arg| (&*arg.name, arg.id.clone()));
for (name, id) in nonempty_values {
let res =
transaction_extensions.encode_extension_value_to(name, id.clone(), type_resolver, out);
match res {
Ok(()) => {}
Err(TransactionExtensionsError::NotFound(name)) => {
if is_type_option(id, type_resolver) {
0u8.encode_to(out);
} else {
return Err(TransactionExtensionsError::NotFound(name));
}
}
Err(e) => return Err(e),
}
}
Ok(())
}
fn encode_transaction_extension_implicits<'exts, 'info, Resolver, Exts>(
ext_info: &'exts ExtrinsicExtensionInfo<'info, <Resolver as TypeResolver>::TypeId>,
transaction_extensions: &Exts,
type_resolver: &Resolver,
out: &mut Vec<u8>,
) -> Result<(), TransactionExtensionsError>
where
Resolver: TypeResolver,
Exts: TransactionExtensions<Resolver>,
{
let nonempty_implicits = ext_info
.extension_ids
.iter()
.filter(|arg| !is_type_empty(arg.implicit_id.clone(), type_resolver))
.map(|arg| (&*arg.name, arg.implicit_id.clone()));
for (name, id) in nonempty_implicits {
transaction_extensions.encode_extension_implicit_to(name, id, type_resolver, out)?;
}
Ok(())
}
fn is_type_empty<Resolver: TypeResolver>(type_id: Resolver::TypeId, types: &Resolver) -> bool {
struct IsEmptyVisitor<'r, R> {
types: &'r R,
}
impl<'r, R: TypeResolver> scale_type_resolver::ResolvedTypeVisitor<'r> for IsEmptyVisitor<'r, R> {
type TypeId = R::TypeId;
type Value = bool;
fn visit_unhandled(self, _: scale_type_resolver::UnhandledKind) -> Self::Value {
false
}
fn visit_array(self, type_id: Self::TypeId, len: usize) -> Self::Value {
len == 0 || is_type_empty(type_id, self.types)
}
fn visit_composite<Path, Fields>(self, _path: Path, mut fields: Fields) -> Self::Value
where
Path: scale_type_resolver::PathIter<'r>,
Fields: scale_decode::FieldIter<'r, Self::TypeId>,
{
fields.all(|f| is_type_empty(f.id, self.types))
}
fn visit_tuple<TypeIds>(self, mut type_ids: TypeIds) -> Self::Value
where
TypeIds: ExactSizeIterator<Item = Self::TypeId>,
{
type_ids.all(|id| is_type_empty(id, self.types))
}
}
types
.resolve_type(type_id, IsEmptyVisitor { types })
.unwrap_or_default()
}
fn is_type_option<Resolver: TypeResolver>(type_id: Resolver::TypeId, types: &Resolver) -> bool {
struct IsOptionVisitor<'r, R> {
types: &'r R,
}
impl<'r, R: TypeResolver> scale_type_resolver::ResolvedTypeVisitor<'r> for IsOptionVisitor<'r, R> {
type TypeId = R::TypeId;
type Value = bool;
fn visit_unhandled(self, _: scale_type_resolver::UnhandledKind) -> Self::Value {
false
}
fn visit_composite<Path, Fields>(self, _path: Path, mut fields: Fields) -> Self::Value
where
Path: scale_type_resolver::PathIter<'r>,
Fields: scale_decode::FieldIter<'r, Self::TypeId>,
{
match (fields.next(), fields.next()) {
(Some(f), None) => is_type_option(f.id, self.types),
_ => false,
}
}
fn visit_tuple<TypeIds>(self, mut type_ids: TypeIds) -> Self::Value
where
TypeIds: ExactSizeIterator<Item = Self::TypeId>,
{
match (type_ids.next(), type_ids.next()) {
(Some(id), None) => is_type_option(id, self.types),
_ => false,
}
}
fn visit_variant<Path, Fields, Var>(self, mut path: Path, mut variants: Var) -> Self::Value
where
Path: scale_type_resolver::PathIter<'r>,
Fields: scale_decode::FieldIter<'r, Self::TypeId>,
Var: scale_type_resolver::VariantIter<'r, Fields>,
{
match (path.next(), path.next()) {
(Some("Option"), None) => {
match (variants.next(), variants.next(), variants.next()) {
(Some(v1), Some(v2), None) => {
(v1.name == "None" && v1.index == 0)
|| (v2.name == "None" && v2.index == 0)
}
_ => false,
}
}
_ => false,
}
}
}
types
.resolve_type(type_id, IsOptionVisitor { types })
.unwrap_or_default()
}
#[cfg(test)]
mod test {
use super::*;
use crate::methods::extrinsic_type_info::{ExtrinsicExtensionInfo, ExtrinsicExtensionInfoArg};
use scale_info::PortableRegistry;
struct TestExtension<Value, Implicit> {
value: Value,
implicit: Implicit,
}
trait GetTestExtensionTypes {
type Value;
type Implicit;
}
impl<Value, Implicit> GetTestExtensionTypes for TestExtension<Value, Implicit> {
type Value = Value;
type Implicit = Implicit;
}
macro_rules! make_test_extension {
( $name:ident value=$value:ty, implicit=$implicit:ty ) => {
type $name = TestExtension<$value, $implicit>;
impl TransactionExtension<PortableRegistry> for TestExtension<$value, $implicit> {
const NAME: &str = stringify!($name);
fn encode_value_to(
&self,
type_id: u32,
type_resolver: &PortableRegistry,
out: &mut Vec<u8>,
) -> Result<(), TransactionExtensionError> {
self.value.encode_as_type_to(type_id, type_resolver, out)?;
Ok(())
}
fn encode_implicit_to(
&self,
type_id: u32,
type_resolver: &PortableRegistry,
out: &mut Vec<u8>,
) -> Result<(), TransactionExtensionError> {
self.implicit
.encode_as_type_to(type_id, type_resolver, out)?;
Ok(())
}
}
};
}
#[derive(scale_encode::EncodeAsType, scale_info::TypeInfo, Debug, Clone, PartialEq)]
struct NestedOption<T>(Option<T>);
make_test_extension!(ExtensionContainingOption value=Option<bool>, implicit=u64);
make_test_extension!(ExtensionContainingNestedOption value=(NestedOption<bool>,), implicit=u64);
make_test_extension!(ExtensionContainingNothing value=(), implicit=());
make_test_extension!(Extension1 value=(bool, u64), implicit=bool);
make_test_extension!(Extension2 value=String, implicit=());
macro_rules! make_extension_info {
( $($ident:ident),* $(,)? ) => {{
use scale_info::meta_type;
let mut registry = scale_info::Registry::new();
let extension_info = vec![
$(
ExtrinsicExtensionInfoArg {
name: <$ident as TransactionExtension<PortableRegistry>>::NAME.into(),
id: registry.register_type(&meta_type::<<$ident as GetTestExtensionTypes>::Value>()).id,
implicit_id: registry.register_type(&meta_type::<<$ident as GetTestExtensionTypes>::Implicit>()).id,
}
),*
];
let portable_registry: PortableRegistry = registry.into();
let info = ExtrinsicExtensionInfo { extension_ids: extension_info };
(info, portable_registry)
}}
}
fn assert_decodes_into<T: parity_scale_codec::Decode + std::fmt::Debug + PartialEq>(
bytes: &[u8],
target: T,
) {
let cursor = &mut &*bytes;
let actual = T::decode(cursor).expect("decoding should succeed");
assert_eq!(actual, target, "actual does not match target");
if !cursor.is_empty() {
panic!("Leftvoer bytes after decoding");
}
}
#[test]
fn encode_transaction_extension_values_basic() {
let (info, types) = make_extension_info![Extension1, Extension2,];
let exts = (
Extension1 {
value: (true, 123),
implicit: false,
},
Extension2 {
value: "Hello".to_owned(),
implicit: (),
},
);
let mut out = vec![];
encode_transaction_extension_values(&info, &exts, &types, &mut out)
.expect("Encoding should succeed");
assert_decodes_into(&out, (true, 123u64, "Hello".to_owned()));
}
#[test]
fn encode_transaction_extension_values_order_irrelevant() {
let (info, types) = make_extension_info![Extension1, Extension2,];
let exts = (
Extension2 {
value: "Hello".to_owned(),
implicit: (),
},
Extension1 {
value: (true, 123),
implicit: false,
},
);
let mut out = vec![];
encode_transaction_extension_values(&info, &exts, &types, &mut out)
.expect("Encoding should succeed");
assert_decodes_into(&out, (true, 123u64, "Hello".to_owned()));
}
#[test]
fn encode_transaction_extension_values_skips_empty() {
let (info, types) =
make_extension_info![Extension1, ExtensionContainingNothing, Extension2,];
let exts = (
Extension1 {
value: (true, 123),
implicit: false,
},
Extension2 {
value: "Hello".to_owned(),
implicit: (),
},
);
let mut out = vec![];
encode_transaction_extension_values(&info, &exts, &types, &mut out)
.expect("Encoding should succeed despite Option");
assert_decodes_into(&out, (true, 123u64, "Hello".to_owned()));
}
#[test]
fn encode_transaction_extension_values_skips_option() {
let (info, types) = make_extension_info![
Extension1,
ExtensionContainingOption,
ExtensionContainingNestedOption,
Extension2,
];
let exts = (
Extension1 {
value: (true, 123),
implicit: false,
},
Extension2 {
value: "Hello".to_owned(),
implicit: (),
},
);
let mut out = vec![];
encode_transaction_extension_values(&info, &exts, &types, &mut out)
.expect("Encoding should succeed despite Option");
assert_decodes_into(&out, (true, 123u64, 0u8, 0u8, "Hello".to_owned()));
}
#[test]
fn encode_transaction_extension_implicits_skips_empty() {
let (info, types) =
make_extension_info![Extension1, Extension2, ExtensionContainingOption,];
let exts = (
Extension1 {
value: (true, 123),
implicit: false,
},
Extension2 {
value: "Hello".to_owned(),
implicit: (),
},
ExtensionContainingOption {
value: None,
implicit: 12345,
},
);
let mut out = vec![];
encode_transaction_extension_implicits(&info, &exts, &types, &mut out)
.expect("Encoding should succeed");
assert_decodes_into(&out, (false, 12345u64));
}
#[test]
fn encode_transaction_extension_implicits_order_irrelevant() {
let (info, types) =
make_extension_info![Extension1, Extension2, ExtensionContainingOption,];
let exts = (
ExtensionContainingOption {
value: None,
implicit: 12345,
},
Extension2 {
value: "Hello".to_owned(),
implicit: (),
},
Extension1 {
value: (true, 123),
implicit: false,
},
);
let mut out = vec![];
encode_transaction_extension_implicits(&info, &exts, &types, &mut out)
.expect("Encoding should succeed");
assert_decodes_into(&out, (false, 12345u64));
}
}