mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-29 04:27:57 +00:00
subxt-core crate (#1466)
* start migrating, broken * first iteration of updating * fmt and clippy * add Composite<u32> decoding via scale value patch * bump scale type gen versions * fix decoding with new scale decode * compiling with changed deps * core utils, condig, client, metadata * core crate compiling * signer crate no once lock * add core to no-std-tests, change imports * broken commit, start pulling everything together in subxt * port more things to subxt * events in core crate, extrinsics sadly much more difficult * almost all examples pass again * dynamic values fix in examples * fix no std issue and fmt * remove unused dependencies * fix lightclient impl * runtime version refactor * formatting and addressing nits * more comments addressed * update wasm example and no-std-signer tests * other nits and error impl on signer errors * fix feature flag * fix runtime version refactor * fix doc links * fix integration tests * fix feature flag gated client state * fix native feature in CI * fix lightclient utils * make imports more lean in subxt-core * integrate changes from subxt-core imports into subxt * other changes in subxt simplify imports more * fix and docs * doc false for cli * fix clippy * remove events block hash in tests * codegen no-std support in generated code * export alloc crate for no-std codegen * fix doc test * implement James comments * remove std traits, use core traits instead * address nits * remove unusued dep in no-std tests * fix Box import in no_std * sp-crypto-hashing instead of sp-core-hashing * bump scale-typegen, add no std codegen tests * fix some things * replace unmaintained derivative with derive_where to remove non-canonical warnings * fmt * remove unused dep * fix deps * update artifacts to fix type ID mismatches * bump to latest scale-typegen --------- Co-authored-by: James Wilson <james@jsdw.me>
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! The "default" Substrate/Polkadot AccountId. This is used in codegen, as well as signing related bits.
|
||||
//! This doesn't contain much functionality itself, but is easy to convert to/from an `sp_core::AccountId32`
|
||||
//! for instance, to gain functionality without forcing a dependency on Substrate crates here.
|
||||
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, Encode};
|
||||
use derive_more::Display;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A 32-byte cryptographic identifier. This is a simplified version of Substrate's
|
||||
/// `sp_core::crypto::AccountId32`. To obtain more functionality, convert this into
|
||||
/// that type.
|
||||
#[derive(
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Encode,
|
||||
Decode,
|
||||
Debug,
|
||||
scale_encode::EncodeAsType,
|
||||
scale_decode::DecodeAsType,
|
||||
scale_info::TypeInfo,
|
||||
)]
|
||||
pub struct AccountId32(pub [u8; 32]);
|
||||
|
||||
impl AsRef<[u8]> for AccountId32 {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8; 32]> for AccountId32 {
|
||||
fn as_ref(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for AccountId32 {
|
||||
fn from(x: [u8; 32]) -> Self {
|
||||
AccountId32(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl AccountId32 {
|
||||
// Return the ss58-check string for this key. Adapted from `sp_core::crypto`. We need this to
|
||||
// serialize our account appropriately but otherwise don't care.
|
||||
fn to_ss58check(&self) -> String {
|
||||
// For serializing to a string to obtain the account nonce, we use the default substrate
|
||||
// prefix (since we have no way to otherwise pick one). It doesn't really matter, since when
|
||||
// it's deserialized back in system_accountNextIndex, we ignore this (so long as it's valid).
|
||||
const SUBSTRATE_SS58_PREFIX: u8 = 42;
|
||||
// prefix <= 63 just take up one byte at the start:
|
||||
let mut v = vec![SUBSTRATE_SS58_PREFIX];
|
||||
// then push the account ID bytes.
|
||||
v.extend(self.0);
|
||||
// then push a 2 byte checksum of what we have so far.
|
||||
let r = ss58hash(&v);
|
||||
v.extend(&r[0..2]);
|
||||
// then encode to base58.
|
||||
use base58::ToBase58;
|
||||
v.to_base58()
|
||||
}
|
||||
|
||||
// This isn't strictly needed, but to give our AccountId32 a little more usefulness, we also
|
||||
// implement the logic needed to decode an AccountId32 from an SS58 encoded string. This is exposed
|
||||
// via a `FromStr` impl.
|
||||
fn from_ss58check(s: &str) -> Result<Self, FromSs58Error> {
|
||||
const CHECKSUM_LEN: usize = 2;
|
||||
let body_len = 32;
|
||||
|
||||
use base58::FromBase58;
|
||||
let data = s.from_base58().map_err(|_| FromSs58Error::BadBase58)?;
|
||||
if data.len() < 2 {
|
||||
return Err(FromSs58Error::BadLength);
|
||||
}
|
||||
let prefix_len = match data[0] {
|
||||
0..=63 => 1,
|
||||
64..=127 => 2,
|
||||
_ => return Err(FromSs58Error::InvalidPrefix),
|
||||
};
|
||||
if data.len() != prefix_len + body_len + CHECKSUM_LEN {
|
||||
return Err(FromSs58Error::BadLength);
|
||||
}
|
||||
let hash = ss58hash(&data[0..body_len + prefix_len]);
|
||||
let checksum = &hash[0..CHECKSUM_LEN];
|
||||
if data[body_len + prefix_len..body_len + prefix_len + CHECKSUM_LEN] != *checksum {
|
||||
// Invalid checksum.
|
||||
return Err(FromSs58Error::InvalidChecksum);
|
||||
}
|
||||
|
||||
let result = data[prefix_len..body_len + prefix_len]
|
||||
.try_into()
|
||||
.map_err(|_| FromSs58Error::BadLength)?;
|
||||
Ok(AccountId32(result))
|
||||
}
|
||||
}
|
||||
|
||||
/// An error obtained from trying to interpret an SS58 encoded string into an AccountId32
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Display)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum FromSs58Error {
|
||||
#[display(fmt = "Base 58 requirement is violated")]
|
||||
BadBase58,
|
||||
#[display(fmt = "Length is bad")]
|
||||
BadLength,
|
||||
#[display(fmt = "Invalid checksum")]
|
||||
InvalidChecksum,
|
||||
#[display(fmt = "Invalid SS58 prefix byte.")]
|
||||
InvalidPrefix,
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for FromSs58Error {}
|
||||
|
||||
// We do this just to get a checksum to help verify the validity of the address in to_ss58check
|
||||
fn ss58hash(data: &[u8]) -> Vec<u8> {
|
||||
use blake2::{Blake2b512, Digest};
|
||||
const PREFIX: &[u8] = b"SS58PRE";
|
||||
let mut ctx = Blake2b512::new();
|
||||
ctx.update(PREFIX);
|
||||
ctx.update(data);
|
||||
ctx.finalize().to_vec()
|
||||
}
|
||||
|
||||
impl Serialize for AccountId32 {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_ss58check())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for AccountId32 {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
AccountId32::from_ss58check(&String::deserialize(deserializer)?)
|
||||
.map_err(|e| serde::de::Error::custom(format!("{e:?}")))
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for AccountId32 {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
write!(f, "{}", self.to_ss58check())
|
||||
}
|
||||
}
|
||||
|
||||
impl core::str::FromStr for AccountId32 {
|
||||
type Err = FromSs58Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
AccountId32::from_ss58check(s)
|
||||
}
|
||||
}
|
||||
|
||||
// Improve compat with the substrate version if we're using those crates:
|
||||
#[cfg(feature = "substrate-compat")]
|
||||
mod substrate_impls {
|
||||
use super::*;
|
||||
|
||||
impl From<sp_runtime::AccountId32> for AccountId32 {
|
||||
fn from(value: sp_runtime::AccountId32) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
impl From<sp_core::sr25519::Public> for AccountId32 {
|
||||
fn from(value: sp_core::sr25519::Public) -> Self {
|
||||
let acc: sp_runtime::AccountId32 = value.into();
|
||||
acc.into()
|
||||
}
|
||||
}
|
||||
impl From<sp_core::ed25519::Public> for AccountId32 {
|
||||
fn from(value: sp_core::ed25519::Public) -> Self {
|
||||
let acc: sp_runtime::AccountId32 = value.into();
|
||||
acc.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use sp_core::crypto::Ss58Codec;
|
||||
use sp_keyring::AccountKeyring;
|
||||
|
||||
#[test]
|
||||
fn ss58_is_compatible_with_substrate_impl() {
|
||||
let keyrings = vec![
|
||||
AccountKeyring::Alice,
|
||||
AccountKeyring::Bob,
|
||||
AccountKeyring::Charlie,
|
||||
];
|
||||
|
||||
for keyring in keyrings {
|
||||
let substrate_account = keyring.to_account_id();
|
||||
// Avoid "From" impl hidden behind "substrate-compat" feature so that this test
|
||||
// can work either way:
|
||||
let local_account = AccountId32(substrate_account.clone().into());
|
||||
|
||||
// Both should encode to ss58 the same way:
|
||||
let substrate_ss58 = substrate_account.to_ss58check();
|
||||
assert_eq!(substrate_ss58, local_account.to_ss58check());
|
||||
|
||||
// Both should decode from ss58 back to the same:
|
||||
assert_eq!(
|
||||
sp_core::crypto::AccountId32::from_ss58check(&substrate_ss58).unwrap(),
|
||||
substrate_account
|
||||
);
|
||||
assert_eq!(
|
||||
AccountId32::from_ss58check(&substrate_ss58).unwrap(),
|
||||
local_account
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! Generic `scale_bits` over `bitvec`-like `BitOrder` and `BitFormat` types.
|
||||
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Compact, Input};
|
||||
use core::marker::PhantomData;
|
||||
use scale_bits::{
|
||||
scale::format::{Format, OrderFormat, StoreFormat},
|
||||
Bits,
|
||||
};
|
||||
use scale_decode::{IntoVisitor, TypeResolver};
|
||||
|
||||
/// Associates `bitvec::store::BitStore` trait with corresponding, type-erased `scale_bits::StoreFormat` enum.
|
||||
///
|
||||
/// Used to decode bit sequences by providing `scale_bits::StoreFormat` using
|
||||
/// `bitvec`-like type type parameters.
|
||||
pub trait BitStore {
|
||||
/// Corresponding `scale_bits::StoreFormat` value.
|
||||
const FORMAT: StoreFormat;
|
||||
/// Number of bits that the backing store types holds.
|
||||
const BITS: u32;
|
||||
}
|
||||
macro_rules! impl_store {
|
||||
($ty:ident, $wrapped:ty) => {
|
||||
impl BitStore for $wrapped {
|
||||
const FORMAT: StoreFormat = StoreFormat::$ty;
|
||||
const BITS: u32 = <$wrapped>::BITS;
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_store!(U8, u8);
|
||||
impl_store!(U16, u16);
|
||||
impl_store!(U32, u32);
|
||||
impl_store!(U64, u64);
|
||||
|
||||
/// Associates `bitvec::order::BitOrder` trait with corresponding, type-erased `scale_bits::OrderFormat` enum.
|
||||
///
|
||||
/// Used to decode bit sequences in runtime by providing `scale_bits::OrderFormat` using
|
||||
/// `bitvec`-like type type parameters.
|
||||
pub trait BitOrder {
|
||||
/// Corresponding `scale_bits::OrderFormat` value.
|
||||
const FORMAT: OrderFormat;
|
||||
}
|
||||
macro_rules! impl_order {
|
||||
($ty:ident) => {
|
||||
#[doc = concat!("Type-level value that corresponds to `scale_bits::OrderFormat::", stringify!($ty), "` at run-time")]
|
||||
#[doc = concat!(" and `bitvec::order::BitOrder::", stringify!($ty), "` at the type level.")]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum $ty {}
|
||||
impl BitOrder for $ty {
|
||||
const FORMAT: OrderFormat = OrderFormat::$ty;
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_order!(Lsb0);
|
||||
impl_order!(Msb0);
|
||||
|
||||
/// Constructs a run-time format parameters based on the corresponding type-level parameters.
|
||||
fn bit_format<Store: BitStore, Order: BitOrder>() -> Format {
|
||||
Format {
|
||||
order: Order::FORMAT,
|
||||
store: Store::FORMAT,
|
||||
}
|
||||
}
|
||||
|
||||
/// `scale_bits::Bits` generic over the bit store (`u8`/`u16`/`u32`/`u64`) and bit order (LSB, MSB)
|
||||
/// used for SCALE encoding/decoding. Uses `scale_bits::Bits`-default `u8` and LSB format underneath.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DecodedBits<Store, Order> {
|
||||
bits: Bits,
|
||||
_marker: PhantomData<(Store, Order)>,
|
||||
}
|
||||
|
||||
impl<Store, Order> DecodedBits<Store, Order> {
|
||||
/// Extracts the underlying `scale_bits::Bits` value.
|
||||
pub fn into_bits(self) -> Bits {
|
||||
self.bits
|
||||
}
|
||||
|
||||
/// References the underlying `scale_bits::Bits` value.
|
||||
pub fn as_bits(&self) -> &Bits {
|
||||
&self.bits
|
||||
}
|
||||
}
|
||||
|
||||
impl<Store, Order> core::iter::FromIterator<bool> for DecodedBits<Store, Order> {
|
||||
fn from_iter<T: IntoIterator<Item = bool>>(iter: T) -> Self {
|
||||
DecodedBits {
|
||||
bits: Bits::from_iter(iter),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Store: BitStore, Order: BitOrder> codec::Decode for DecodedBits<Store, Order> {
|
||||
fn decode<I: Input>(input: &mut I) -> Result<Self, codec::Error> {
|
||||
/// Equivalent of `BitSlice::MAX_BITS` on 32bit machine.
|
||||
const ARCH32BIT_BITSLICE_MAX_BITS: u32 = 0x1fff_ffff;
|
||||
|
||||
let Compact(bits) = <Compact<u32>>::decode(input)?;
|
||||
// Otherwise it is impossible to store it on 32bit machine.
|
||||
if bits > ARCH32BIT_BITSLICE_MAX_BITS {
|
||||
return Err("Attempt to decode a BitVec with too many bits".into());
|
||||
}
|
||||
// NOTE: Replace with `bits.div_ceil(Store::BITS)` if `int_roundings` is stabilised
|
||||
let elements = (bits / Store::BITS) + u32::from(bits % Store::BITS != 0);
|
||||
let bytes_in_elem = Store::BITS.saturating_div(u8::BITS);
|
||||
let bytes_needed = (elements * bytes_in_elem) as usize;
|
||||
|
||||
// NOTE: We could reduce allocations if it would be possible to directly
|
||||
// decode from an `Input` type using a custom format (rather than default <u8, Lsb0>)
|
||||
// for the `Bits` type.
|
||||
let mut storage = codec::Encode::encode(&Compact(bits));
|
||||
let prefix_len = storage.len();
|
||||
storage.reserve_exact(bytes_needed);
|
||||
storage.extend(vec![0; bytes_needed]);
|
||||
input.read(&mut storage[prefix_len..])?;
|
||||
|
||||
let decoder = scale_bits::decode_using_format_from(&storage, bit_format::<Store, Order>())?;
|
||||
let bits = decoder.collect::<Result<Vec<_>, _>>()?;
|
||||
let bits = Bits::from_iter(bits);
|
||||
|
||||
Ok(DecodedBits {
|
||||
bits,
|
||||
_marker: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<Store: BitStore, Order: BitOrder> codec::Encode for DecodedBits<Store, Order> {
|
||||
fn size_hint(&self) -> usize {
|
||||
self.bits.size_hint()
|
||||
}
|
||||
|
||||
fn encoded_size(&self) -> usize {
|
||||
self.bits.encoded_size()
|
||||
}
|
||||
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
scale_bits::encode_using_format(self.bits.iter(), bit_format::<Store, Order>())
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct DecodedBitsVisitor<S, O, R: TypeResolver>(core::marker::PhantomData<(S, O, R)>);
|
||||
|
||||
impl<Store, Order, R: TypeResolver> scale_decode::Visitor for DecodedBitsVisitor<Store, Order, R> {
|
||||
type Value<'scale, 'info> = DecodedBits<Store, Order>;
|
||||
type Error = scale_decode::Error;
|
||||
type TypeResolver = R;
|
||||
|
||||
fn unchecked_decode_as_type<'scale, 'info>(
|
||||
self,
|
||||
input: &mut &'scale [u8],
|
||||
type_id: &R::TypeId,
|
||||
types: &'info R,
|
||||
) -> scale_decode::visitor::DecodeAsTypeResult<
|
||||
Self,
|
||||
Result<Self::Value<'scale, 'info>, Self::Error>,
|
||||
> {
|
||||
let res =
|
||||
scale_decode::visitor::decode_with_visitor(input, type_id, types, Bits::into_visitor())
|
||||
.map(|bits| DecodedBits {
|
||||
bits,
|
||||
_marker: PhantomData,
|
||||
});
|
||||
scale_decode::visitor::DecodeAsTypeResult::Decoded(res)
|
||||
}
|
||||
}
|
||||
impl<Store, Order> scale_decode::IntoVisitor for DecodedBits<Store, Order> {
|
||||
type AnyVisitor<R: scale_decode::TypeResolver> = DecodedBitsVisitor<Store, Order, R>;
|
||||
fn into_visitor<R: TypeResolver>() -> DecodedBitsVisitor<Store, Order, R> {
|
||||
DecodedBitsVisitor(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Store, Order> scale_encode::EncodeAsType for DecodedBits<Store, Order> {
|
||||
fn encode_as_type_to<R: TypeResolver>(
|
||||
&self,
|
||||
type_id: &R::TypeId,
|
||||
types: &R,
|
||||
out: &mut Vec<u8>,
|
||||
) -> Result<(), scale_encode::Error> {
|
||||
self.bits.encode_as_type_to(type_id, types, out)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use core::fmt::Debug;
|
||||
|
||||
use bitvec::vec::BitVec;
|
||||
use codec::Decode as _;
|
||||
|
||||
// NOTE: We don't use `bitvec::order` types in our implementation, since we
|
||||
// don't want to depend on `bitvec`. Rather than reimplementing the unsafe
|
||||
// trait on our types here for testing purposes, we simply convert and
|
||||
// delegate to `bitvec`'s own types.
|
||||
trait ToBitVec {
|
||||
type Order: bitvec::order::BitOrder;
|
||||
}
|
||||
impl ToBitVec for Lsb0 {
|
||||
type Order = bitvec::order::Lsb0;
|
||||
}
|
||||
impl ToBitVec for Msb0 {
|
||||
type Order = bitvec::order::Msb0;
|
||||
}
|
||||
|
||||
fn scales_like_bitvec_and_roundtrips<
|
||||
'a,
|
||||
Store: BitStore + bitvec::store::BitStore + PartialEq,
|
||||
Order: BitOrder + ToBitVec + Debug + PartialEq,
|
||||
>(
|
||||
input: impl IntoIterator<Item = &'a bool>,
|
||||
) where
|
||||
BitVec<Store, <Order as ToBitVec>::Order>: codec::Encode + codec::Decode,
|
||||
{
|
||||
let input: Vec<_> = input.into_iter().copied().collect();
|
||||
|
||||
let decoded_bits = DecodedBits::<Store, Order>::from_iter(input.clone());
|
||||
let bitvec = BitVec::<Store, <Order as ToBitVec>::Order>::from_iter(input);
|
||||
|
||||
let decoded_bits_encoded = codec::Encode::encode(&decoded_bits);
|
||||
let bitvec_encoded = codec::Encode::encode(&bitvec);
|
||||
assert_eq!(decoded_bits_encoded, bitvec_encoded);
|
||||
|
||||
let decoded_bits_decoded =
|
||||
DecodedBits::<Store, Order>::decode(&mut &decoded_bits_encoded[..])
|
||||
.expect("SCALE-encoding DecodedBits to roundtrip");
|
||||
let bitvec_decoded =
|
||||
BitVec::<Store, <Order as ToBitVec>::Order>::decode(&mut &bitvec_encoded[..])
|
||||
.expect("SCALE-encoding BitVec to roundtrip");
|
||||
assert_eq!(decoded_bits, decoded_bits_decoded);
|
||||
assert_eq!(bitvec, bitvec_decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decoded_bitvec_scales_and_roundtrips() {
|
||||
let test_cases = [
|
||||
vec![],
|
||||
vec![true],
|
||||
vec![false],
|
||||
vec![true, false, true],
|
||||
vec![true, false, true, false, false, false, false, false, true],
|
||||
[vec![true; 5], vec![false; 5], vec![true; 1], vec![false; 3]].concat(),
|
||||
[vec![true; 9], vec![false; 9], vec![true; 9], vec![false; 9]].concat(),
|
||||
];
|
||||
|
||||
for test_case in &test_cases {
|
||||
scales_like_bitvec_and_roundtrips::<u8, Lsb0>(test_case);
|
||||
scales_like_bitvec_and_roundtrips::<u16, Lsb0>(test_case);
|
||||
scales_like_bitvec_and_roundtrips::<u32, Lsb0>(test_case);
|
||||
scales_like_bitvec_and_roundtrips::<u64, Lsb0>(test_case);
|
||||
scales_like_bitvec_and_roundtrips::<u8, Msb0>(test_case);
|
||||
scales_like_bitvec_and_roundtrips::<u16, Msb0>(test_case);
|
||||
scales_like_bitvec_and_roundtrips::<u32, Msb0>(test_case);
|
||||
scales_like_bitvec_and_roundtrips::<u64, Msb0>(test_case);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use scale_decode::DecodeAsType;
|
||||
use scale_encode::EncodeAsType;
|
||||
|
||||
// Dev note: This and related bits taken from `sp_runtime::generic::Era`
|
||||
/// An era to describe the longevity of a transaction.
|
||||
#[derive(
|
||||
PartialEq,
|
||||
Default,
|
||||
Eq,
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
DecodeAsType,
|
||||
EncodeAsType,
|
||||
scale_info::TypeInfo,
|
||||
)]
|
||||
pub enum Era {
|
||||
/// The transaction is valid forever. The genesis hash must be present in the signed content.
|
||||
#[default]
|
||||
Immortal,
|
||||
|
||||
/// The transaction will expire. Use [`Era::mortal`] to construct this with correct values.
|
||||
///
|
||||
/// When used on `FRAME`-based runtimes, `period` cannot exceed `BlockHashCount` parameter
|
||||
/// of `system` module.
|
||||
Mortal {
|
||||
/// The number of blocks that the tx will be valid for after the checkpoint block
|
||||
/// hash found in the signer payload.
|
||||
period: u64,
|
||||
/// The phase in the period that this transaction's lifetime begins (and, importantly,
|
||||
/// implies which block hash is included in the signature material). If the `period` is
|
||||
/// greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that
|
||||
/// `period` is.
|
||||
phase: u64,
|
||||
},
|
||||
}
|
||||
|
||||
// E.g. with period == 4:
|
||||
// 0 10 20 30 40
|
||||
// 0123456789012345678901234567890123456789012
|
||||
// |...|
|
||||
// authored -/ \- expiry
|
||||
// phase = 1
|
||||
// n = Q(current - phase, period) + phase
|
||||
impl Era {
|
||||
/// Create a new era based on a period (which should be a power of two between 4 and 65536
|
||||
/// inclusive) and a block number on which it should start (or, for long periods, be shortly
|
||||
/// after the start).
|
||||
///
|
||||
/// If using `Era` in the context of `FRAME` runtime, make sure that `period`
|
||||
/// does not exceed `BlockHashCount` parameter passed to `system` module, since that
|
||||
/// prunes old blocks and renders transactions immediately invalid.
|
||||
pub fn mortal(period: u64, current: u64) -> Self {
|
||||
let period = period
|
||||
.checked_next_power_of_two()
|
||||
.unwrap_or(1 << 16)
|
||||
.clamp(4, 1 << 16);
|
||||
let phase = current % period;
|
||||
let quantize_factor = (period >> 12).max(1);
|
||||
let quantized_phase = phase / quantize_factor * quantize_factor;
|
||||
|
||||
Self::Mortal {
|
||||
period,
|
||||
phase: quantized_phase,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Both copied from `sp_runtime::generic::Era`; this is the wire interface and so
|
||||
// it's really the most important bit here.
|
||||
impl codec::Encode for Era {
|
||||
fn encode_to<T: codec::Output + ?Sized>(&self, output: &mut T) {
|
||||
match self {
|
||||
Self::Immortal => output.push_byte(0),
|
||||
Self::Mortal { period, phase } => {
|
||||
let quantize_factor = (*period >> 12).max(1);
|
||||
let encoded = (period.trailing_zeros() - 1).clamp(1, 15) as u16
|
||||
| ((phase / quantize_factor) << 4) as u16;
|
||||
encoded.encode_to(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl codec::Decode for Era {
|
||||
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
|
||||
let first = input.read_byte()?;
|
||||
if first == 0 {
|
||||
Ok(Self::Immortal)
|
||||
} else {
|
||||
let encoded = first as u64 + ((input.read_byte()? as u64) << 8);
|
||||
let period = 2 << (encoded % (1 << 4));
|
||||
let quantize_factor = (period >> 12).max(1);
|
||||
let phase = (encoded >> 4) * quantize_factor;
|
||||
if period >= 4 && phase < period {
|
||||
Ok(Self::Mortal { period, phase })
|
||||
} else {
|
||||
Err("Invalid period and phase".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! Miscellaneous utility helpers.
|
||||
|
||||
mod account_id;
|
||||
pub mod bits;
|
||||
mod era;
|
||||
mod multi_address;
|
||||
mod multi_signature;
|
||||
mod static_type;
|
||||
mod unchecked_extrinsic;
|
||||
mod wrapper_opaque;
|
||||
|
||||
use alloc::borrow::ToOwned;
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Compact, Decode, Encode};
|
||||
use derive_where::derive_where;
|
||||
|
||||
pub use account_id::AccountId32;
|
||||
pub use era::Era;
|
||||
pub use multi_address::MultiAddress;
|
||||
pub use multi_signature::MultiSignature;
|
||||
pub use static_type::Static;
|
||||
pub use unchecked_extrinsic::UncheckedExtrinsic;
|
||||
pub use wrapper_opaque::WrapperKeepOpaque;
|
||||
|
||||
// Used in codegen
|
||||
#[doc(hidden)]
|
||||
pub use primitive_types::{H160, H256, H512};
|
||||
|
||||
/// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of
|
||||
/// the transaction payload
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct Encoded(pub Vec<u8>);
|
||||
|
||||
impl codec::Encode for Encoded {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
self.0.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Decodes a compact encoded value from the beginning of the provided bytes,
|
||||
/// returning the value and any remaining bytes.
|
||||
pub fn strip_compact_prefix(bytes: &[u8]) -> Result<(u64, &[u8]), codec::Error> {
|
||||
let cursor = &mut &*bytes;
|
||||
let val = <Compact<u64>>::decode(cursor)?;
|
||||
Ok((val.0, *cursor))
|
||||
}
|
||||
|
||||
/// A version of [`core::marker::PhantomData`] that is also Send and Sync (which is fine
|
||||
/// because regardless of the generic param, it is always possible to Send + Sync this
|
||||
/// 0 size type).
|
||||
#[derive(Encode, Decode, scale_info::TypeInfo)]
|
||||
#[derive_where(Clone, PartialEq, Debug, Eq, Default, Hash)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
#[doc(hidden)]
|
||||
pub struct PhantomDataSendSync<T>(core::marker::PhantomData<T>);
|
||||
|
||||
impl<T> PhantomDataSendSync<T> {
|
||||
pub fn new() -> Self {
|
||||
Self(core::marker::PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> Send for PhantomDataSendSync<T> {}
|
||||
unsafe impl<T> Sync for PhantomDataSendSync<T> {}
|
||||
|
||||
/// This represents a key-value collection and is SCALE compatible
|
||||
/// with collections like BTreeMap. This has the same type params
|
||||
/// as `BTreeMap` which allows us to easily swap the two during codegen.
|
||||
pub type KeyedVec<K, V> = Vec<(K, V)>;
|
||||
|
||||
/// A unit marker struct signalling that some property is true
|
||||
pub struct Yes;
|
||||
|
||||
/// A quick helper to encode some bytes to hex.
|
||||
pub fn to_hex(bytes: impl AsRef<[u8]>) -> String {
|
||||
format!("0x{}", hex::encode(bytes.as_ref()))
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! The "default" Substrate/Polkadot Address type. This is used in codegen, as well as signing related bits.
|
||||
//! This doesn't contain much functionality itself, but is easy to convert to/from an `sp_runtime::MultiAddress`
|
||||
//! for instance, to gain functionality without forcing a dependency on Substrate crates here.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
/// A multi-format address wrapper for on-chain accounts. This is a simplified version of Substrate's
|
||||
/// `sp_runtime::MultiAddress`. To obtain more functionality, convert this into that type (this conversion
|
||||
/// functionality is provided via `From` impls if the `substrate-compat` feature is enabled).
|
||||
#[derive(
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Encode,
|
||||
Decode,
|
||||
Debug,
|
||||
scale_encode::EncodeAsType,
|
||||
scale_decode::DecodeAsType,
|
||||
scale_info::TypeInfo,
|
||||
)]
|
||||
pub enum MultiAddress<AccountId, AccountIndex> {
|
||||
/// It's an account ID (pubkey).
|
||||
Id(AccountId),
|
||||
/// It's an account index.
|
||||
Index(#[codec(compact)] AccountIndex),
|
||||
/// It's some arbitrary raw bytes.
|
||||
Raw(Vec<u8>),
|
||||
/// It's a 32 byte representation.
|
||||
Address32([u8; 32]),
|
||||
/// Its a 20 byte representation.
|
||||
Address20([u8; 20]),
|
||||
}
|
||||
|
||||
impl<AccountId, AccountIndex> From<AccountId> for MultiAddress<AccountId, AccountIndex> {
|
||||
fn from(a: AccountId) -> Self {
|
||||
Self::Id(a)
|
||||
}
|
||||
}
|
||||
|
||||
// Improve compat with the substrate version if we're using those crates:
|
||||
#[cfg(feature = "substrate-compat")]
|
||||
mod substrate_impls {
|
||||
use super::{super::AccountId32, *};
|
||||
|
||||
impl<N> From<sp_runtime::AccountId32> for MultiAddress<AccountId32, N> {
|
||||
fn from(value: sp_runtime::AccountId32) -> Self {
|
||||
let val: AccountId32 = value.into();
|
||||
val.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Id, N> From<sp_runtime::MultiAddress<Id, N>> for MultiAddress<AccountId32, N>
|
||||
where
|
||||
Id: Into<AccountId32>,
|
||||
{
|
||||
fn from(value: sp_runtime::MultiAddress<Id, N>) -> Self {
|
||||
match value {
|
||||
sp_runtime::MultiAddress::Id(v) => Self::Id(v.into()),
|
||||
sp_runtime::MultiAddress::Index(v) => Self::Index(v),
|
||||
sp_runtime::MultiAddress::Raw(v) => Self::Raw(v),
|
||||
sp_runtime::MultiAddress::Address32(v) => Self::Address32(v),
|
||||
sp_runtime::MultiAddress::Address20(v) => Self::Address20(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! The "default" Substrate/Polkadot Signature type. This is used in codegen, as well as signing related bits.
|
||||
//! This doesn't contain much functionality itself, but is easy to convert to/from an `sp_runtime::MultiSignature`
|
||||
//! for instance, to gain functionality without forcing a dependency on Substrate crates here.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
/// Signature container that can store known signature types. This is a simplified version of
|
||||
/// `sp_runtime::MultiSignature`. To obtain more functionality, convert this into that type.
|
||||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, scale_info::TypeInfo)]
|
||||
pub enum MultiSignature {
|
||||
/// An Ed25519 signature.
|
||||
Ed25519([u8; 64]),
|
||||
/// An Sr25519 signature.
|
||||
Sr25519([u8; 64]),
|
||||
/// An ECDSA/SECP256k1 signature (a 512-bit value, plus 8 bits for recovery ID).
|
||||
Ecdsa([u8; 65]),
|
||||
}
|
||||
|
||||
// Improve compat with the substrate version if we're using those crates:
|
||||
#[cfg(feature = "substrate-compat")]
|
||||
mod substrate_impls {
|
||||
use super::*;
|
||||
|
||||
impl From<sp_runtime::MultiSignature> for MultiSignature {
|
||||
fn from(value: sp_runtime::MultiSignature) -> Self {
|
||||
match value {
|
||||
sp_runtime::MultiSignature::Ed25519(s) => Self::Ed25519(s.0),
|
||||
sp_runtime::MultiSignature::Sr25519(s) => Self::Sr25519(s.0),
|
||||
sp_runtime::MultiSignature::Ecdsa(s) => Self::Ecdsa(s.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sp_core::ed25519::Signature> for MultiSignature {
|
||||
fn from(value: sp_core::ed25519::Signature) -> Self {
|
||||
let sig: sp_runtime::MultiSignature = value.into();
|
||||
sig.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sp_core::sr25519::Signature> for MultiSignature {
|
||||
fn from(value: sp_core::sr25519::Signature) -> Self {
|
||||
let sig: sp_runtime::MultiSignature = value.into();
|
||||
sig.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sp_core::ecdsa::Signature> for MultiSignature {
|
||||
fn from(value: sp_core::ecdsa::Signature) -> Self {
|
||||
let sig: sp_runtime::MultiSignature = value.into();
|
||||
sig.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use scale_decode::{visitor::DecodeAsTypeResult, IntoVisitor, TypeResolver, Visitor};
|
||||
use scale_encode::EncodeAsType;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
/// If the type inside this implements [`Encode`], this will implement [`scale_encode::EncodeAsType`].
|
||||
/// If the type inside this implements [`Decode`], this will implement [`scale_decode::DecodeAsType`].
|
||||
///
|
||||
/// In either direction, we ignore any type information and just attempt to encode/decode statically
|
||||
/// via the [`Encode`] and [`Decode`] implementations. This can be useful as an adapter for types which
|
||||
/// do not implement [`scale_encode::EncodeAsType`] and [`scale_decode::DecodeAsType`] themselves, but
|
||||
/// it's best to avoid using it where possible as it will not take into account any type information,
|
||||
/// and is thus more likely to encode or decode incorrectly.
|
||||
#[derive(Debug, Encode, Decode, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
|
||||
pub struct Static<T>(pub T);
|
||||
|
||||
impl<T: Encode> EncodeAsType for Static<T> {
|
||||
fn encode_as_type_to<R: TypeResolver>(
|
||||
&self,
|
||||
_type_id: &R::TypeId,
|
||||
_types: &R,
|
||||
out: &mut Vec<u8>,
|
||||
) -> Result<(), scale_encode::Error> {
|
||||
self.0.encode_to(out);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StaticDecodeAsTypeVisitor<T, R>(core::marker::PhantomData<(T, R)>);
|
||||
|
||||
impl<T: Decode, R: TypeResolver> Visitor for StaticDecodeAsTypeVisitor<T, R> {
|
||||
type Value<'scale, 'info> = Static<T>;
|
||||
type Error = scale_decode::Error;
|
||||
type TypeResolver = R;
|
||||
|
||||
fn unchecked_decode_as_type<'scale, 'info>(
|
||||
self,
|
||||
input: &mut &'scale [u8],
|
||||
_type_id: &R::TypeId,
|
||||
_types: &'info R,
|
||||
) -> DecodeAsTypeResult<Self, Result<Self::Value<'scale, 'info>, Self::Error>> {
|
||||
use scale_decode::{visitor::DecodeError, Error};
|
||||
let decoded = T::decode(input)
|
||||
.map(Static)
|
||||
.map_err(|e| Error::new(DecodeError::CodecError(e).into()));
|
||||
DecodeAsTypeResult::Decoded(decoded)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Decode> IntoVisitor for Static<T> {
|
||||
type AnyVisitor<R: TypeResolver> = StaticDecodeAsTypeVisitor<T, R>;
|
||||
fn into_visitor<R: TypeResolver>() -> StaticDecodeAsTypeVisitor<T, R> {
|
||||
StaticDecodeAsTypeVisitor(core::marker::PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
// Make it easy to convert types into Static where required.
|
||||
impl<T> From<T> for Static<T> {
|
||||
fn from(value: T) -> Self {
|
||||
Static(value)
|
||||
}
|
||||
}
|
||||
|
||||
// Static<T> is just a marker type and should be as transparent as possible:
|
||||
impl<T> core::ops::Deref for Static<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> core::ops::DerefMut for Static<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! The "default" Substrate/Polkadot UncheckedExtrinsic.
|
||||
//! This is used in codegen for runtime API calls.
|
||||
//!
|
||||
//! The inner bytes represent the encoded extrinsic expected by the
|
||||
//! runtime APIs. Deriving `EncodeAsType` would lead to the inner
|
||||
//! bytes to be re-encoded (length prefixed).
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use scale_decode::{visitor::DecodeAsTypeResult, DecodeAsType, IntoVisitor, TypeResolver, Visitor};
|
||||
|
||||
use super::{Encoded, Static};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
/// The unchecked extrinsic from substrate.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Encode)]
|
||||
pub struct UncheckedExtrinsic<Address, Call, Signature, Extra>(
|
||||
Static<Encoded>,
|
||||
#[codec(skip)] PhantomData<(Address, Call, Signature, Extra)>,
|
||||
);
|
||||
|
||||
impl<Address, Call, Signature, Extra> UncheckedExtrinsic<Address, Call, Signature, Extra> {
|
||||
/// Construct a new [`UncheckedExtrinsic`].
|
||||
pub fn new(bytes: Vec<u8>) -> Self {
|
||||
Self(Static(Encoded(bytes)), PhantomData)
|
||||
}
|
||||
|
||||
/// Get the bytes of the encoded extrinsic.
|
||||
pub fn bytes(&self) -> &[u8] {
|
||||
self.0 .0 .0.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, Call, Signature, Extra> Decode
|
||||
for UncheckedExtrinsic<Address, Call, Signature, Extra>
|
||||
{
|
||||
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
|
||||
// The bytes for an UncheckedExtrinsic are first a compact
|
||||
// encoded length, and then the bytes following. This is the
|
||||
// same encoding as a Vec, so easiest ATM is just to decode
|
||||
// into that, and then encode the vec bytes to get our extrinsic
|
||||
// bytes, which we save into an `Encoded` to preserve as-is.
|
||||
let xt_vec: Vec<u8> = Decode::decode(input)?;
|
||||
Ok(UncheckedExtrinsic::new(xt_vec))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, Call, Signature, Extra> scale_encode::EncodeAsType
|
||||
for UncheckedExtrinsic<Address, Call, Signature, Extra>
|
||||
{
|
||||
fn encode_as_type_to<R: TypeResolver>(
|
||||
&self,
|
||||
type_id: &R::TypeId,
|
||||
types: &R,
|
||||
out: &mut Vec<u8>,
|
||||
) -> Result<(), scale_encode::Error> {
|
||||
self.0.encode_as_type_to(type_id, types, out)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, Call, Signature, Extra> From<Vec<u8>>
|
||||
for UncheckedExtrinsic<Address, Call, Signature, Extra>
|
||||
{
|
||||
fn from(bytes: Vec<u8>) -> Self {
|
||||
UncheckedExtrinsic::new(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, Call, Signature, Extra> From<UncheckedExtrinsic<Address, Call, Signature, Extra>>
|
||||
for Vec<u8>
|
||||
{
|
||||
fn from(bytes: UncheckedExtrinsic<Address, Call, Signature, Extra>) -> Self {
|
||||
bytes.0 .0 .0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra, R: TypeResolver>(
|
||||
PhantomData<(Address, Call, Signature, Extra, R)>,
|
||||
);
|
||||
|
||||
impl<Address, Call, Signature, Extra, R: TypeResolver> Visitor
|
||||
for UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra, R>
|
||||
{
|
||||
type Value<'scale, 'info> = UncheckedExtrinsic<Address, Call, Signature, Extra>;
|
||||
type Error = scale_decode::Error;
|
||||
type TypeResolver = R;
|
||||
|
||||
fn unchecked_decode_as_type<'scale, 'info>(
|
||||
self,
|
||||
input: &mut &'scale [u8],
|
||||
type_id: &R::TypeId,
|
||||
types: &'info R,
|
||||
) -> DecodeAsTypeResult<Self, Result<Self::Value<'scale, 'info>, Self::Error>> {
|
||||
DecodeAsTypeResult::Decoded(Self::Value::decode_as_type(input, type_id, types))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, Call, Signature, Extra> IntoVisitor
|
||||
for UncheckedExtrinsic<Address, Call, Signature, Extra>
|
||||
{
|
||||
type AnyVisitor<R: TypeResolver> =
|
||||
UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra, R>;
|
||||
|
||||
fn into_visitor<R: TypeResolver>(
|
||||
) -> UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra, R> {
|
||||
UncheckedExtrinsicDecodeAsTypeVisitor(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
|
||||
use alloc::vec;
|
||||
|
||||
#[test]
|
||||
fn unchecked_extrinsic_encoding() {
|
||||
// A tx is basically some bytes with a compact length prefix; ie an encoded vec:
|
||||
let tx_bytes = vec![1u8, 2, 3].encode();
|
||||
|
||||
let unchecked_extrinsic = UncheckedExtrinsic::<(), (), (), ()>::new(tx_bytes.clone());
|
||||
let encoded_tx_bytes = unchecked_extrinsic.encode();
|
||||
|
||||
// The encoded representation must not alter the provided bytes.
|
||||
assert_eq!(tx_bytes, encoded_tx_bytes);
|
||||
|
||||
// However, for decoding we expect to be able to read the extrinsic from the wire
|
||||
// which would be length prefixed.
|
||||
let decoded_tx = UncheckedExtrinsic::<(), (), (), ()>::decode(&mut &tx_bytes[..]).unwrap();
|
||||
let decoded_tx_bytes = decoded_tx.bytes();
|
||||
let encoded_tx_bytes = decoded_tx.encode();
|
||||
|
||||
assert_eq!(decoded_tx_bytes, encoded_tx_bytes);
|
||||
// Ensure we can decode the tx and fetch only the tx bytes.
|
||||
assert_eq!(vec![1, 2, 3], encoded_tx_bytes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use super::PhantomDataSendSync;
|
||||
use codec::{Compact, Decode, DecodeAll, Encode};
|
||||
use derive_where::derive_where;
|
||||
use scale_decode::{ext::scale_type_resolver::visitor, IntoVisitor, TypeResolver, Visitor};
|
||||
use scale_encode::EncodeAsType;
|
||||
|
||||
use alloc::format;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
/// A wrapper for any type `T` which implement encode/decode in a way compatible with `Vec<u8>`.
|
||||
/// [`WrapperKeepOpaque`] stores the type only in its opaque format, aka as a `Vec<u8>`. To
|
||||
/// access the real type `T` [`Self::try_decode`] needs to be used.
|
||||
// Dev notes:
|
||||
//
|
||||
// - This is adapted from [here](https://github.com/paritytech/substrate/blob/master/frame/support/src/traits/misc.rs).
|
||||
// - The encoded bytes will be a compact encoded length followed by that number of bytes.
|
||||
// - However, the TypeInfo describes the type as a composite with first a compact encoded length and next the type itself.
|
||||
// [`Encode`] and [`Decode`] impls will "just work" to take this into a `Vec<u8>`, but we need a custom [`EncodeAsType`]
|
||||
// and [`Visitor`] implementation to encode and decode based on TypeInfo.
|
||||
#[derive(Encode, Decode)]
|
||||
#[derive_where(Debug, Clone, PartialEq, Eq, Default, Hash)]
|
||||
pub struct WrapperKeepOpaque<T> {
|
||||
data: Vec<u8>,
|
||||
_phantom: PhantomDataSendSync<T>,
|
||||
}
|
||||
|
||||
impl<T> WrapperKeepOpaque<T> {
|
||||
/// Try to decode the wrapped type from the inner `data`.
|
||||
///
|
||||
/// Returns `None` if the decoding failed.
|
||||
pub fn try_decode(&self) -> Option<T>
|
||||
where
|
||||
T: Decode,
|
||||
{
|
||||
T::decode_all(&mut &self.data[..]).ok()
|
||||
}
|
||||
|
||||
/// Returns the length of the encoded `T`.
|
||||
pub fn encoded_len(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
/// Returns the encoded data.
|
||||
pub fn encoded(&self) -> &[u8] {
|
||||
&self.data
|
||||
}
|
||||
|
||||
/// Create from the given encoded `data`.
|
||||
pub fn from_encoded(data: Vec<u8>) -> Self {
|
||||
Self {
|
||||
data,
|
||||
_phantom: PhantomDataSendSync::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create from some raw value by encoding it.
|
||||
pub fn from_value(value: T) -> Self
|
||||
where
|
||||
T: Encode,
|
||||
{
|
||||
Self {
|
||||
data: value.encode(),
|
||||
_phantom: PhantomDataSendSync::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EncodeAsType for WrapperKeepOpaque<T> {
|
||||
fn encode_as_type_to<R: TypeResolver>(
|
||||
&self,
|
||||
type_id: &R::TypeId,
|
||||
types: &R,
|
||||
out: &mut Vec<u8>,
|
||||
) -> Result<(), scale_encode::Error> {
|
||||
use scale_encode::error::{Error, ErrorKind, Kind};
|
||||
|
||||
let visitor = visitor::new(out, |_, _| {
|
||||
// Check that the target shape lines up: any other shape but composite is wrong.
|
||||
Err(Error::new(ErrorKind::WrongShape {
|
||||
actual: Kind::Struct,
|
||||
expected_id: format!("{:?}", type_id),
|
||||
}))
|
||||
})
|
||||
.visit_composite(|out, _fields| {
|
||||
self.data.encode_to(out);
|
||||
Ok(())
|
||||
});
|
||||
|
||||
types
|
||||
.resolve_type(type_id, visitor)
|
||||
.map_err(|_| Error::new(ErrorKind::TypeNotFound(format!("{:?}", type_id))))?
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WrapperKeepOpaqueVisitor<T, R>(core::marker::PhantomData<(T, R)>);
|
||||
impl<T, R: TypeResolver> Visitor for WrapperKeepOpaqueVisitor<T, R> {
|
||||
type Value<'scale, 'info> = WrapperKeepOpaque<T>;
|
||||
type Error = scale_decode::Error;
|
||||
type TypeResolver = R;
|
||||
|
||||
fn visit_composite<'scale, 'info>(
|
||||
self,
|
||||
value: &mut scale_decode::visitor::types::Composite<'scale, 'info, R>,
|
||||
_type_id: &R::TypeId,
|
||||
) -> Result<Self::Value<'scale, 'info>, Self::Error> {
|
||||
use scale_decode::error::{Error, ErrorKind};
|
||||
|
||||
// TODO: When `scale-type-resolver` [provides struct names](https://github.com/paritytech/scale-type-resolver/issues/4), check that this struct name is `WrapperKeepOpaque`
|
||||
|
||||
if value.remaining() != 2 {
|
||||
return Err(Error::new(ErrorKind::WrongLength {
|
||||
actual_len: value.remaining(),
|
||||
expected_len: 2,
|
||||
}));
|
||||
}
|
||||
|
||||
// The field to decode is a compact len followed by bytes. Decode the length, then grab the bytes.
|
||||
let Compact(len) = value
|
||||
.decode_item(Compact::<u32>::into_visitor())
|
||||
.expect("length checked")?;
|
||||
let field = value.next().expect("length checked")?;
|
||||
|
||||
// Sanity check that the compact length we decoded lines up with the number of bytes encoded in the next field.
|
||||
if field.bytes().len() != len as usize {
|
||||
return Err(Error::custom_str("WrapperTypeKeepOpaque compact encoded length doesn't line up with encoded byte len"));
|
||||
}
|
||||
|
||||
Ok(WrapperKeepOpaque {
|
||||
data: field.bytes().to_vec(),
|
||||
_phantom: PhantomDataSendSync::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoVisitor for WrapperKeepOpaque<T> {
|
||||
type AnyVisitor<R: TypeResolver> = WrapperKeepOpaqueVisitor<T, R>;
|
||||
fn into_visitor<R: TypeResolver>() -> WrapperKeepOpaqueVisitor<T, R> {
|
||||
WrapperKeepOpaqueVisitor(core::marker::PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use scale_decode::DecodeAsType;
|
||||
|
||||
use alloc::vec;
|
||||
|
||||
use super::*;
|
||||
|
||||
// Copied from https://github.com/paritytech/substrate/blob/master/frame/support/src/traits/misc.rs
|
||||
// and used for tests to check that we can work with the expected TypeInfo without needing to import
|
||||
// the frame_support crate, which has quite a lot of dependencies.
|
||||
impl<T: scale_info::TypeInfo + 'static> scale_info::TypeInfo for WrapperKeepOpaque<T> {
|
||||
type Identity = Self;
|
||||
fn type_info() -> scale_info::Type {
|
||||
use scale_info::{build::Fields, meta_type, Path, Type, TypeParameter};
|
||||
|
||||
Type::builder()
|
||||
.path(Path::new("WrapperKeepOpaque", module_path!()))
|
||||
.type_params(vec![TypeParameter::new("T", Some(meta_type::<T>()))])
|
||||
.composite(
|
||||
Fields::unnamed()
|
||||
.field(|f| f.compact::<u32>())
|
||||
.field(|f| f.ty::<T>().type_name("T")),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a type definition, return type ID and registry representing it.
|
||||
fn make_type<T: scale_info::TypeInfo + 'static>() -> (u32, scale_info::PortableRegistry) {
|
||||
let m = scale_info::MetaType::new::<T>();
|
||||
let mut types = scale_info::Registry::new();
|
||||
let id = types.register_type(&m);
|
||||
let portable_registry: scale_info::PortableRegistry = types.into();
|
||||
(id.id, portable_registry)
|
||||
}
|
||||
|
||||
fn roundtrips_like_scale_codec<T>(t: T)
|
||||
where
|
||||
T: EncodeAsType
|
||||
+ DecodeAsType
|
||||
+ Encode
|
||||
+ Decode
|
||||
+ PartialEq
|
||||
+ core::fmt::Debug
|
||||
+ scale_info::TypeInfo
|
||||
+ 'static,
|
||||
{
|
||||
let (type_id, types) = make_type::<T>();
|
||||
|
||||
let scale_codec_encoded = t.encode();
|
||||
let encode_as_type_encoded = t.encode_as_type(&type_id, &types).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
scale_codec_encoded, encode_as_type_encoded,
|
||||
"encoded bytes should match"
|
||||
);
|
||||
|
||||
let decode_as_type_bytes = &mut &*scale_codec_encoded;
|
||||
let decoded_as_type = T::decode_as_type(decode_as_type_bytes, &type_id, &types)
|
||||
.expect("decode-as-type decodes");
|
||||
|
||||
let decode_scale_codec_bytes = &mut &*scale_codec_encoded;
|
||||
let decoded_scale_codec = T::decode(decode_scale_codec_bytes).expect("scale-codec decodes");
|
||||
|
||||
assert!(
|
||||
decode_as_type_bytes.is_empty(),
|
||||
"no bytes should remain in decode-as-type impl"
|
||||
);
|
||||
assert!(
|
||||
decode_scale_codec_bytes.is_empty(),
|
||||
"no bytes should remain in codec-decode impl"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
decoded_as_type, decoded_scale_codec,
|
||||
"decoded values should match"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrapper_keep_opaque_roundtrips_ok() {
|
||||
roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(123u64));
|
||||
roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(true));
|
||||
roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(vec![1u8, 2, 3, 4]));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user