Use scale-encode and scale-decode to encode and decode based on metadata (#842)

* WIP EncodeAsType and DecodeAsType

* remove silly cli experiment code

* Get things finally compiling with EncodeAsType and DecodeAsType

* update codegen test and WrapperKeepOpaque proper impl (in case it shows up in codegen)

* fix tests

* accomodate scale-value changes

* starting to migrate to EncodeAsType/DecodeAsType

* static event decoding and tx encoding to use DecodeAsFields/EncodeAsFields

* some tidy up and add decode(skip) attrs where needed

* fix root event decoding

* #[codec(skip)] will do, and combine map_key stuff into storage_address since it's all specific to that

* fmt and clippy

* update Cargo.lock

* remove patched scale-encode

* bump scale-encode to 0.1 and remove unused dep in testing crate

* update deps and use released scale-decode

* update scale-value to latest to remove git branch

* Apply suggestions from code review

Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>

* remove sorting in derives/attr generation; spit them out in order given

* re-add derive sorting; it's a hashmap

* StaticTxPayload and DynamicTxPayload rolled into single Payload struct

* StaticStorageAddress and DynamicStorageAddress into single Address struct

* Fix storage address byte retrieval

* StaticConstantAddress and DynamicConstantAddress => Address

* Simplify storage codegen to fix test

* Add comments

* Alias to RuntimeEvent rather than making another, and prep for substituting call type

* remove unnecessary clone

* Fix docs and failing UI test

* root_bytes -> to_root_bytes

* document error case in StorageClient::address_bytes()

---------

Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
This commit is contained in:
James Wilson
2023-03-21 15:31:13 +00:00
committed by GitHub
parent c9527abaa8
commit c63ff6ec6d
50 changed files with 9965 additions and 6262 deletions
+3 -3
View File
@@ -23,9 +23,9 @@ use serde::{
};
pub use crate::utils::{
account_id::AccountId32,
multi_address::MultiAddress,
multi_signature::MultiSignature,
AccountId32,
MultiAddress,
MultiSignature,
};
pub use primitive_types::{
H256,
+36 -43
View File
@@ -28,25 +28,39 @@ pub trait ConstantAddress {
}
}
/// This represents a statically generated constant lookup address.
pub struct StaticConstantAddress<ReturnTy> {
pallet_name: &'static str,
constant_name: &'static str,
/// This represents the address of a constant.
pub struct Address<ReturnTy> {
pallet_name: Cow<'static, str>,
constant_name: Cow<'static, str>,
constant_hash: Option<[u8; 32]>,
_marker: std::marker::PhantomData<ReturnTy>,
}
impl<ReturnTy> StaticConstantAddress<ReturnTy> {
/// Create a new [`StaticConstantAddress`] that will be validated
/// The type of address typically used to return dynamic constant values.
pub type DynamicAddress = Address<DecodedValueThunk>;
impl<ReturnTy> Address<ReturnTy> {
/// Create a new [`Address`] to use to look up a constant.
pub fn new(pallet_name: impl Into<String>, constant_name: impl Into<String>) -> Self {
Self {
pallet_name: Cow::Owned(pallet_name.into()),
constant_name: Cow::Owned(constant_name.into()),
constant_hash: None,
_marker: std::marker::PhantomData,
}
}
/// Create a new [`Address`] that will be validated
/// against node metadata using the hash given.
pub fn new(
#[doc(hidden)]
pub fn new_static(
pallet_name: &'static str,
constant_name: &'static str,
hash: [u8; 32],
) -> Self {
Self {
pallet_name,
constant_name,
pallet_name: Cow::Borrowed(pallet_name),
constant_name: Cow::Borrowed(constant_name),
constant_hash: Some(hash),
_marker: std::marker::PhantomData,
}
@@ -63,42 +77,9 @@ impl<ReturnTy> StaticConstantAddress<ReturnTy> {
}
}
impl<ReturnTy: DecodeWithMetadata> ConstantAddress for StaticConstantAddress<ReturnTy> {
impl<ReturnTy: DecodeWithMetadata> ConstantAddress for Address<ReturnTy> {
type Target = ReturnTy;
fn pallet_name(&self) -> &str {
self.pallet_name
}
fn constant_name(&self) -> &str {
self.constant_name
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.constant_hash
}
}
/// This represents a dynamically generated constant address.
pub struct DynamicConstantAddress<'a> {
pallet_name: Cow<'a, str>,
constant_name: Cow<'a, str>,
}
/// Construct a new dynamic constant lookup.
pub fn dynamic<'a>(
pallet_name: impl Into<Cow<'a, str>>,
constant_name: impl Into<Cow<'a, str>>,
) -> DynamicConstantAddress<'a> {
DynamicConstantAddress {
pallet_name: pallet_name.into(),
constant_name: constant_name.into(),
}
}
impl<'a> ConstantAddress for DynamicConstantAddress<'a> {
type Target = DecodedValueThunk;
fn pallet_name(&self) -> &str {
&self.pallet_name
}
@@ -106,4 +87,16 @@ impl<'a> ConstantAddress for DynamicConstantAddress<'a> {
fn constant_name(&self) -> &str {
&self.constant_name
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.constant_hash
}
}
/// Construct a new dynamic constant lookup.
pub fn dynamic(
pallet_name: impl Into<String>,
constant_name: impl Into<String>,
) -> DynamicAddress {
DynamicAddress::new(pallet_name, constant_name)
}
+2 -2
View File
@@ -63,7 +63,7 @@ impl<T: Config, Client: OfflineClientT<T>> ConstantsClient<T, Client> {
pub fn at<Address: ConstantAddress>(
&self,
address: &Address,
) -> Result<<Address::Target as DecodeWithMetadata>::Target, Error> {
) -> Result<Address::Target, Error> {
let metadata = self.client.metadata();
// 1. Validate constant shape if hash given:
@@ -72,7 +72,7 @@ impl<T: Config, Client: OfflineClientT<T>> ConstantsClient<T, Client> {
// 2. Attempt to decode the constant into the type given:
let pallet = metadata.pallet(address.pallet_name())?;
let constant = pallet.constant(address.constant_name())?;
let value = Address::Target::decode_with_metadata(
let value = <Address::Target as DecodeWithMetadata>::decode_with_metadata(
&mut &*constant.value,
constant.ty.id(),
&metadata,
+2 -2
View File
@@ -9,8 +9,8 @@ mod constants_client;
pub use constant_address::{
dynamic,
Address,
ConstantAddress,
DynamicConstantAddress,
StaticConstantAddress,
DynamicAddress,
};
pub use constants_client::ConstantsClient;
+6 -6
View File
@@ -12,6 +12,7 @@ use crate::{
Metadata,
},
};
use scale_decode::DecodeAsType;
pub use scale_value::Value;
@@ -43,13 +44,11 @@ pub struct DecodedValueThunk {
}
impl DecodeWithMetadata for DecodedValueThunk {
type Target = Self;
fn decode_with_metadata(
bytes: &mut &[u8],
type_id: u32,
metadata: &Metadata,
) -> Result<Self::Target, Error> {
) -> Result<Self, Error> {
let mut v = Vec::with_capacity(bytes.len());
v.extend_from_slice(bytes);
*bytes = &[];
@@ -72,10 +71,11 @@ impl DecodedValueThunk {
}
/// Decode the SCALE encoded storage entry into a dynamic [`DecodedValue`] type.
pub fn to_value(&self) -> Result<DecodedValue, Error> {
DecodedValue::decode_with_metadata(
let val = DecodedValue::decode_as_type(
&mut &*self.scale_bytes,
self.type_id,
&self.metadata,
)
self.metadata.types(),
)?;
Ok(val)
}
}
+4 -6
View File
@@ -15,10 +15,8 @@ pub use crate::metadata::{
InvalidMetadataError,
MetadataError,
};
pub use scale_value::scale::{
DecodeError,
EncodeError,
};
pub use scale_decode::Error as DecodeError;
pub use scale_encode::Error as EncodeError;
/// The underlying error enum, generic over the type held by the `Runtime`
/// variant. Prefer to use the [`Error<E>`] and [`Error`] aliases over
@@ -48,10 +46,10 @@ pub enum Error {
Runtime(DispatchError),
/// Error decoding to a [`crate::dynamic::Value`].
#[error("Error decoding into dynamic value: {0}")]
DecodeValue(#[from] DecodeError),
Decode(#[from] DecodeError),
/// Error encoding from a [`crate::dynamic::Value`].
#[error("Error encoding from dynamic value: {0}")]
EncodeValue(#[from] EncodeError<()>),
Encode(#[from] EncodeError),
/// Transaction progress error.
#[error("Transaction error: {0}")]
Transaction(#[from] TransactionError),
+154 -48
View File
@@ -12,14 +12,16 @@ use crate::{
client::OnlineClientT,
error::Error,
events::events_client::get_event_bytes,
metadata::EventMetadata,
metadata::{
DecodeWithMetadata,
EventMetadata,
},
Config,
Metadata,
};
use codec::{
Compact,
Decode,
Error as CodecError,
};
use derivative::Derivative;
use std::sync::Arc;
@@ -203,6 +205,7 @@ impl<T: Config> Events<T> {
#[derive(Debug, Clone)]
pub struct EventDetails {
phase: Phase,
/// The index of the event in the list of events in a given block.
index: u32,
all_bytes: Arc<[u8]>,
// start of the bytes (phase, pallet/variant index and then fields and then topic to follow).
@@ -248,12 +251,13 @@ impl EventDetails {
// Skip over the bytes belonging to this event.
for field_metadata in event_metadata.fields() {
// Skip over the bytes for this field:
scale_decode::decode(
scale_decode::visitor::decode_with_visitor(
input,
field_metadata.type_id(),
field_metadata.ty().id(),
&metadata.runtime_metadata().types,
scale_decode::visitor::IgnoreVisitor,
)?;
)
.map_err(scale_decode::Error::from)?;
}
// the end of the field bytes.
@@ -343,63 +347,98 @@ impl EventDetails {
let bytes = &mut self.field_bytes();
let event_metadata = self.event_metadata();
// If the first field has a name, we assume that the rest do too (it'll either
// be a named struct or a tuple type). If no fields, assume unnamed.
let is_named = event_metadata
.fields()
.get(0)
.map(|fm| fm.name().is_some())
.unwrap_or(false);
use scale_decode::DecodeAsFields;
let decoded =
<scale_value::Composite<scale_value::scale::TypeId>>::decode_as_fields(
bytes,
event_metadata.fields(),
&self.metadata.runtime_metadata().types,
)?;
if !is_named {
let mut event_values = vec![];
for field_metadata in event_metadata.fields() {
let value = scale_value::scale::decode_as_type(
bytes,
field_metadata.type_id(),
&self.metadata.runtime_metadata().types,
)?;
event_values.push(value);
}
Ok(scale_value::Composite::Unnamed(event_values))
} else {
let mut event_values = vec![];
for field_metadata in event_metadata.fields() {
let value = scale_value::scale::decode_as_type(
bytes,
field_metadata.type_id(),
&self.metadata.runtime_metadata().types,
)?;
event_values
.push((field_metadata.name().unwrap_or_default().to_string(), value));
}
Ok(scale_value::Composite::Named(event_values))
}
Ok(decoded)
}
/// Attempt to decode these [`EventDetails`] into a specific static event.
/// This targets the fields within the event directly. You can also attempt to
/// decode the entirety of the event type (including the pallet and event
/// variants) using [`EventDetails::as_root_event()`].
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, CodecError> {
/// Attempt to statically decode these [`EventDetails`] into a type representing the event
/// fields. This leans directly on [`codec::Decode`]. You can also attempt to decode the entirety
/// of the event using [`EventDetails::as_root_event()`], which is more lenient because it's able
/// to lean on [`scale_decode::DecodeAsType`].
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, Error> {
let ev_metadata = self.event_metadata();
if ev_metadata.pallet() == E::PALLET && ev_metadata.event() == E::EVENT {
Ok(Some(E::decode(&mut self.field_bytes())?))
let decoded = E::decode_as_fields(
&mut self.field_bytes(),
ev_metadata.fields(),
self.metadata.types(),
)?;
Ok(Some(decoded))
} else {
Ok(None)
}
}
/// Attempt to decode these [`EventDetails`] into a pallet event type (which includes
/// the pallet enum variants as well as the event fields). These events can be found in
/// the static codegen under a path like `pallet_name::Event`.
pub fn as_pallet_event<E: DecodeWithMetadata>(&self) -> Result<E, Error> {
let pallet = self.metadata.pallet(self.pallet_name())?;
let event_ty = pallet.event_ty_id().ok_or_else(|| {
Error::Metadata(crate::metadata::MetadataError::EventNotFound(
pallet.index(),
self.variant_index(),
))
})?;
// Ignore the root enum index, so start 1 byte after that:
let start_idx = self.event_start_idx + 1;
let decoded = E::decode_with_metadata(
&mut &self.all_bytes[start_idx..self.event_fields_end_idx],
event_ty,
&self.metadata,
)?;
Ok(decoded)
}
/// Attempt to decode these [`EventDetails`] into a root event type (which includes
/// the pallet and event enum variants as well as the event fields). A compatible
/// type for this is exposed via static codegen as a root level `Event` type.
pub fn as_root_event<E: Decode>(&self) -> Result<E, CodecError> {
E::decode(&mut &self.all_bytes[self.event_start_idx..self.event_fields_end_idx])
pub fn as_root_event<E: RootEvent>(&self) -> Result<E, Error> {
let pallet_bytes =
&self.all_bytes[self.event_start_idx + 1..self.event_fields_end_idx];
let pallet = self.metadata.pallet(self.pallet_name())?;
let pallet_event_ty = pallet.event_ty_id().ok_or_else(|| {
Error::Metadata(crate::metadata::MetadataError::EventNotFound(
pallet.index(),
self.variant_index(),
))
})?;
E::root_event(
pallet_bytes,
self.pallet_name(),
pallet_event_ty,
&self.metadata,
)
}
}
/// This trait is implemented on the statically generated root event type, so that we're able
/// to decode it properly via a pallet event that impls `DecodeAsMetadata`. This is necessary
/// becasue the "root event" type is generated using pallet info but doesn't actually exist in the
/// metadata types, so we have no easy way to decode things into it via type information and need a
/// little help via codegen.
#[doc(hidden)]
pub trait RootEvent: Sized {
/// Given details of the pallet event we want to decode, and the name of the pallet, try to hand
/// back a "root event".
fn root_event(
pallet_bytes: &[u8],
pallet_name: &str,
pallet_event_ty: u32,
metadata: &Metadata,
) -> Result<Self, Error>;
}
/// Event related test utilities used outside this module.
#[cfg(test)]
pub(crate) mod test_utils {
@@ -425,11 +464,40 @@ pub(crate) mod test_utils {
use std::convert::TryFrom;
/// An "outer" events enum containing exactly one event.
#[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq, Eq)]
#[derive(
Encode,
Decode,
TypeInfo,
Clone,
Debug,
PartialEq,
Eq,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
)]
pub enum AllEvents<Ev> {
Test(Ev),
}
// We need this in order to be able to decode into a root event type:
impl<Ev: DecodeWithMetadata> RootEvent for AllEvents<Ev> {
fn root_event(
mut bytes: &[u8],
pallet_name: &str,
pallet_event_ty: u32,
metadata: &Metadata,
) -> Result<Self, Error> {
if pallet_name == "Test" {
return Ok(AllEvents::Test(Ev::decode_with_metadata(
&mut bytes,
pallet_event_ty,
metadata,
)?))
}
panic!("Asked for pallet name '{pallet_name}', which isn't in our test AllEvents type")
}
}
/// This encodes to the same format an event is expected to encode to
/// in node System.Events storage.
#[derive(Encode)]
@@ -556,6 +624,7 @@ mod tests {
// Make sure that the bytes handed back line up with the fields handed back;
// encode the fields back into bytes and they should be equal.
let actual_fields = actual.field_values().expect("can decode field values (1)");
let mut actual_bytes = vec![];
for field in actual_fields.into_values() {
scale_value::scale::encode_as_type(
@@ -587,7 +656,9 @@ mod tests {
#[test]
fn statically_decode_single_root_event() {
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
#[derive(
Clone, Debug, PartialEq, Decode, Encode, TypeInfo, scale_decode::DecodeAsType,
)]
enum Event {
A(u8, bool, Vec<String>),
}
@@ -618,6 +689,41 @@ mod tests {
assert_eq!(decoded_event, AllEvents::Test(event));
}
#[test]
fn statically_decode_single_pallet_event() {
#[derive(
Clone, Debug, PartialEq, Decode, Encode, TypeInfo, scale_decode::DecodeAsType,
)]
enum Event {
A(u8, bool, Vec<String>),
}
// Create fake metadata that knows about our single event, above:
let metadata = metadata::<Event>();
// Encode our events in the format we expect back from a node, and
// construst an Events object to iterate them:
let event = Event::A(1, true, vec!["Hi".into()]);
let events = events::<Event>(
metadata,
vec![event_record(Phase::ApplyExtrinsic(123), event.clone())],
);
let ev = events
.iter()
.next()
.expect("one event expected")
.expect("event should be extracted OK");
// This is the line we're testing; decode into our "pallet event" enum.
let decoded_event = ev
.as_pallet_event::<Event>()
.expect("can decode event into root enum again");
// It should equal the event we put in:
assert_eq!(decoded_event, event);
}
#[test]
fn dynamically_decode_single_event() {
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
+9 -7
View File
@@ -9,16 +9,18 @@
mod events_client;
mod events_type;
pub use events_client::EventsClient;
pub use events_type::{
EventDetails,
Events,
};
use codec::{
Decode,
Encode,
};
pub use events_client::EventsClient;
pub use events_type::{
EventDetails,
Events,
// Used in codegen but hidden from docs:
RootEvent,
};
use scale_decode::DecodeAsFields;
/// Trait to uniquely identify the events's identity from the runtime metadata.
///
@@ -26,7 +28,7 @@ use codec::{
///
/// The trait is utilized to decode emitted events from a block, via obtaining the
/// form of the `Event` from the metadata.
pub trait StaticEvent: Decode {
pub trait StaticEvent: DecodeAsFields {
/// Pallet name.
const PALLET: &'static str;
/// Event name.
+2
View File
@@ -181,6 +181,8 @@ pub mod ext {
pub use codec;
pub use frame_metadata;
pub use scale_bits;
pub use scale_decode;
pub use scale_encode;
pub use scale_value;
#[cfg(feature = "substrate-compat")]
pub use sp_core;
@@ -0,0 +1,51 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::Metadata;
use crate::error::Error;
/// This trait is implemented for all types that also implement [`scale_decode::DecodeAsType`].
pub trait DecodeWithMetadata: Sized {
/// Given some metadata and a type ID, attempt to SCALE decode the provided bytes into `Self`.
fn decode_with_metadata(
bytes: &mut &[u8],
type_id: u32,
metadata: &Metadata,
) -> Result<Self, Error>;
}
impl<T: scale_decode::DecodeAsType> DecodeWithMetadata for T {
fn decode_with_metadata(
bytes: &mut &[u8],
type_id: u32,
metadata: &Metadata,
) -> Result<T, Error> {
let val = T::decode_as_type(bytes, type_id, metadata.types())?;
Ok(val)
}
}
/// This trait is implemented for all types that also implement [`scale_encode::EncodeAsType`].
pub trait EncodeWithMetadata {
/// SCALE encode this type to bytes, possibly with the help of metadata.
fn encode_with_metadata(
&self,
type_id: u32,
metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error>;
}
impl<T: scale_encode::EncodeAsType> EncodeWithMetadata for T {
/// SCALE encode this type to bytes, possibly with the help of metadata.
fn encode_with_metadata(
&self,
type_id: u32,
metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error> {
self.encode_as_type_to(type_id, metadata.types(), bytes)?;
Ok(())
}
}
@@ -1,79 +0,0 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::Metadata;
use crate::{
dynamic::DecodedValue,
error::Error,
};
use codec::Decode;
use frame_metadata::StorageEntryType;
/// This trait is implemented for types which can be decoded with the help of metadata.
pub trait DecodeWithMetadata {
/// The type that we'll get back from decoding.
type Target;
/// Given some metadata and a type ID, attempt to SCALE decode the provided bytes into `Self`.
fn decode_with_metadata(
bytes: &mut &[u8],
type_id: u32,
metadata: &Metadata,
) -> Result<Self::Target, Error>;
/// Decode a storage item using metadata. By default, this uses the metadata to
/// work out the type ID to use, but for static items we can short circuit this
/// lookup.
fn decode_storage_with_metadata(
bytes: &mut &[u8],
pallet_name: &str,
storage_entry: &str,
metadata: &Metadata,
) -> Result<Self::Target, Error> {
let ty = &metadata.pallet(pallet_name)?.storage(storage_entry)?.ty;
let id = match ty {
StorageEntryType::Plain(ty) => ty.id(),
StorageEntryType::Map { value, .. } => value.id(),
};
Self::decode_with_metadata(bytes, id, metadata)
}
}
// Things can be dynamically decoded to our Value type:
impl DecodeWithMetadata for DecodedValue {
type Target = Self;
fn decode_with_metadata(
bytes: &mut &[u8],
type_id: u32,
metadata: &Metadata,
) -> Result<Self::Target, Error> {
let res = scale_value::scale::decode_as_type(bytes, type_id, metadata.types())?;
Ok(res)
}
}
/// Any type implementing [`Decode`] can also be decoded with the help of metadata.
pub struct DecodeStaticType<T>(std::marker::PhantomData<T>);
impl<T: Decode> DecodeWithMetadata for DecodeStaticType<T> {
type Target = T;
fn decode_with_metadata(
bytes: &mut &[u8],
_type_id: u32,
_metadata: &Metadata,
) -> Result<Self::Target, Error> {
T::decode(bytes).map_err(|e| e.into())
}
fn decode_storage_with_metadata(
bytes: &mut &[u8],
_pallet_name: &str,
_storage_entry: &str,
_metadata: &Metadata,
) -> Result<Self::Target, Error> {
T::decode(bytes).map_err(|e| e.into())
}
}
@@ -1,67 +0,0 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{
dynamic::Value,
error::Error,
metadata::Metadata,
};
use codec::Encode;
/// This trait is implemented for types which can be encoded with the help of metadata.
pub trait EncodeWithMetadata {
/// SCALE encode this type to bytes, possibly with the help of metadata.
fn encode_with_metadata(
&self,
type_id: u32,
metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error>;
}
impl EncodeWithMetadata for Value<()> {
fn encode_with_metadata(
&self,
type_id: u32,
metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error> {
scale_value::scale::encode_as_type(self, type_id, metadata.types(), bytes)
.map_err(|e| e.into())
}
}
/// Any type implementing [`Encode`] can also be encoded with the help of metadata.
pub struct EncodeStaticType<T>(pub T);
impl<T: Encode> EncodeWithMetadata for EncodeStaticType<T> {
fn encode_with_metadata(
&self,
_type_id: u32,
_metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error> {
self.0.encode_to(bytes);
Ok(())
}
}
// We can transparently Encode anything wrapped in EncodeStaticType, too.
impl<E: Encode> Encode for EncodeStaticType<E> {
fn size_hint(&self) -> usize {
self.0.size_hint()
}
fn encode_to<T: codec::Output + ?Sized>(&self, dest: &mut T) {
self.0.encode_to(dest)
}
fn encode(&self) -> Vec<u8> {
self.0.encode()
}
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
self.0.using_encoded(f)
}
fn encoded_size(&self) -> usize {
self.0.encoded_size()
}
}
+45 -48
View File
@@ -78,13 +78,19 @@ pub enum MetadataError {
#[derive(Debug)]
struct MetadataInner {
metadata: RuntimeMetadataV14,
pallets: HashMap<String, PalletMetadata>,
// Events are hashed by pallet an error index (decode oriented)
events: HashMap<(u8, u8), EventMetadata>,
// Errors are hashed by pallet index.
// Errors are hashed by pallet and error index (decode oriented)
errors: HashMap<(u8, u8), ErrorMetadata>,
// Other pallet details are hashed by pallet name.
pallets: HashMap<String, PalletMetadata>,
// Type of the DispatchError type, which is what comes back if
// an extrinsic fails.
dispatch_error_ty: Option<u32>,
// The hashes uniquely identify parts of the metadata; different
// hashes mean some type difference exists between static and runtime
// versions. We cache them here to avoid recalculating:
@@ -245,8 +251,9 @@ impl Metadata {
pub struct PalletMetadata {
index: u8,
name: String,
call_indexes: HashMap<String, u8>,
call_metadata: HashMap<String, CallMetadata>,
call_ty_id: Option<u32>,
event_ty_id: Option<u32>,
storage: HashMap<String, StorageEntryMetadata<PortableForm>>,
constants: HashMap<String, PalletConstantMetadata<PortableForm>>,
}
@@ -268,11 +275,17 @@ impl PalletMetadata {
self.call_ty_id
}
/// If events exist for this pallet, this returns the type ID of the variant
/// representing the different possible events.
pub fn event_ty_id(&self) -> Option<u32> {
self.event_ty_id
}
/// Attempt to resolve a call into an index in this pallet, failing
/// if the call is not found in this pallet.
pub fn call_index(&self, function: &str) -> Result<u8, MetadataError> {
let fn_index = *self
.call_indexes
pub fn call(&self, function: &str) -> Result<&CallMetadata, MetadataError> {
let fn_index = self
.call_metadata
.get(function)
.ok_or(MetadataError::CallNotFound)?;
Ok(fn_index)
@@ -297,37 +310,21 @@ impl PalletMetadata {
}
}
/// Metadata for specific field.
#[derive(Clone, Debug)]
pub struct EventFieldMetadata {
name: Option<String>,
type_name: Option<String>,
type_id: u32,
pub struct CallMetadata {
call_index: u8,
fields: Vec<scale_info::Field<scale_info::form::PortableForm>>,
}
impl EventFieldMetadata {
/// Construct a new [`EventFieldMetadata`]
pub fn new(name: Option<String>, type_name: Option<String>, type_id: u32) -> Self {
EventFieldMetadata {
name,
type_name,
type_id,
}
impl CallMetadata {
/// Index of this call.
pub fn index(&self) -> u8 {
self.call_index
}
/// Get the name of the field.
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
/// Get the type name of the field as it appears in the code
pub fn type_name(&self) -> Option<&str> {
self.type_name.as_deref()
}
/// Get the id of a type
pub fn type_id(&self) -> u32 {
self.type_id
/// The names, type names & types of each field in the call data.
pub fn fields(&self) -> &[scale_info::Field<scale_info::form::PortableForm>] {
&self.fields
}
}
@@ -338,7 +335,7 @@ pub struct EventMetadata {
// behind an Arc to avoid lots of needless clones of it existing.
pallet: Arc<str>,
event: String,
fields: Vec<EventFieldMetadata>,
fields: Vec<scale_info::Field<scale_info::form::PortableForm>>,
docs: Vec<String>,
}
@@ -354,7 +351,7 @@ impl EventMetadata {
}
/// The names, type names & types of each field in the event.
pub fn fields(&self) -> &[EventFieldMetadata] {
pub fn fields(&self) -> &[scale_info::Field<scale_info::form::PortableForm>] {
&self.fields
}
@@ -437,14 +434,23 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
.iter()
.map(|pallet| {
let call_ty_id = pallet.calls.as_ref().map(|c| c.ty.id());
let event_ty_id = pallet.event.as_ref().map(|e| e.ty.id());
let call_indexes =
let call_metadata =
pallet.calls.as_ref().map_or(Ok(HashMap::new()), |call| {
let type_def_variant = get_type_def_variant(call.ty.id())?;
let call_indexes = type_def_variant
.variants()
.iter()
.map(|v| (v.name().clone(), v.index()))
.map(|v| {
(
v.name().clone(),
CallMetadata {
call_index: v.index(),
fields: v.fields().to_vec(),
},
)
})
.collect();
Ok(call_indexes)
})?;
@@ -466,8 +472,9 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
let pallet_metadata = PalletMetadata {
index: pallet.index,
name: pallet.name.to_string(),
call_indexes,
call_metadata,
call_ty_id,
event_ty_id,
storage,
constants,
};
@@ -488,17 +495,7 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
EventMetadata {
pallet: pallet_name.clone(),
event: variant.name().to_owned(),
fields: variant
.fields()
.iter()
.map(|f| {
EventFieldMetadata::new(
f.name().map(|n| n.to_owned()),
f.type_name().map(|n| n.to_owned()),
f.ty().id(),
)
})
.collect(),
fields: variant.fields().to_vec(),
docs: variant.docs().to_vec(),
},
);
+2 -9
View File
@@ -4,8 +4,7 @@
//! Types representing the metadata obtained from a node.
mod decode_with_metadata;
mod encode_with_metadata;
mod decode_encode_traits;
mod hash_cache;
mod metadata_location;
mod metadata_type;
@@ -14,7 +13,6 @@ pub use metadata_location::MetadataLocation;
pub use metadata_type::{
ErrorMetadata,
EventFieldMetadata,
EventMetadata,
InvalidMetadataError,
Metadata,
@@ -22,12 +20,7 @@ pub use metadata_type::{
PalletMetadata,
};
pub use decode_with_metadata::{
DecodeStaticType,
pub use decode_encode_traits::{
DecodeWithMetadata,
};
pub use encode_with_metadata::{
EncodeStaticType,
EncodeWithMetadata,
};
+5 -13
View File
@@ -21,30 +21,22 @@
//! Fetching storage keys
//!
//! ```no_run
//! # #[tokio::main]
//! # async fn main() {
//! use subxt::{ PolkadotConfig, OnlineClient, storage::StorageKey };
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! # #[tokio::main]
//! # async fn main() {
//! let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
//!
//! let key = polkadot::storage()
//! .xcm_pallet()
//! .version_notifiers_root()
//! .to_bytes();
//!
//! // Fetch up to 10 keys.
//! let keys = api
//! let genesis_hash = api
//! .rpc()
//! .storage_keys_paged(&key, 10, None, None)
//! .genesis_hash()
//! .await
//! .unwrap();
//!
//! for key in keys.iter() {
//! println!("Key: 0x{}", hex::encode(&key));
//! }
//! println!("{genesis_hash}");
//! # }
//! ```
+6 -14
View File
@@ -9,33 +9,25 @@
//!
//! # Example
//!
//! Fetching storage keys
//! Fetching the chain genesis hash.
//!
//! ```no_run
//! # #[tokio::main]
//! # async fn main() {
//! use subxt::{ PolkadotConfig, OnlineClient, storage::StorageKey };
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! # #[tokio::main]
//! # async fn main() {
//! let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
//!
//! let key = polkadot::storage()
//! .xcm_pallet()
//! .version_notifiers_root()
//! .to_bytes();
//!
//! // Fetch up to 10 keys.
//! let keys = api
//! let genesis_hash = api
//! .rpc()
//! .storage_keys_paged(&key, 10, None, None)
//! .genesis_hash()
//! .await
//! .unwrap();
//!
//! for key in keys.iter() {
//! println!("Key: 0x{}", hex::encode(&key));
//! }
//! println!("{genesis_hash}");
//! # }
//! ```
+10 -16
View File
@@ -6,7 +6,6 @@
mod storage_address;
mod storage_client;
mod storage_map_key;
mod storage_type;
pub mod utils;
@@ -24,19 +23,14 @@ pub use crate::rpc::types::StorageKey;
/// Types representing an address which describes where a storage
/// entry lives and how to properly decode it.
pub mod address {
pub use super::{
storage_address::{
dynamic,
dynamic_root,
DynamicStorageAddress,
StaticStorageAddress,
StorageAddress,
Yes,
},
storage_map_key::{
StorageHasher,
StorageMapKey,
},
pub use super::storage_address::{
dynamic,
dynamic_root,
Address,
DynamicAddress,
StaticStorageMapKey,
StorageAddress,
Yes,
};
}
@@ -45,7 +39,7 @@ pub mod address {
pub use storage_address::{
dynamic,
dynamic_root,
DynamicStorageAddress,
StaticStorageAddress,
Address,
DynamicAddress,
StorageAddress,
};
+118 -112
View File
@@ -2,7 +2,6 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::storage_map_key::StorageMapKey;
use crate::{
dynamic::{
DecodedValueThunk,
@@ -18,13 +17,13 @@ use crate::{
Metadata,
},
};
use frame_metadata::StorageEntryType;
use frame_metadata::{
StorageEntryType,
StorageHasher,
};
use scale_info::TypeDef;
use std::borrow::Cow;
// We use this type a bunch, so export it from here.
pub use frame_metadata::StorageHasher;
/// This represents a storage address. Anything implementing this trait
/// can be used to fetch and iterate over storage entries.
pub trait StorageAddress {
@@ -66,34 +65,55 @@ pub trait StorageAddress {
/// fetched and returned with a default value in the type system.
pub struct Yes;
/// This represents a statically generated storage lookup address.
pub struct StaticStorageAddress<ReturnTy, Fetchable, Defaultable, Iterable> {
pallet_name: &'static str,
entry_name: &'static str,
// How to access the specific value at that storage address.
storage_entry_keys: Vec<StorageMapKey>,
// Hash provided from static code for validation.
/// A concrete storage address. This can be created from static values (ie those generated
/// via the `subxt` macro) or dynamic values via [`dynamic`] and [`dynamic_root`].
pub struct Address<StorageKey, ReturnTy, Fetchable, Defaultable, Iterable> {
pallet_name: Cow<'static, str>,
entry_name: Cow<'static, str>,
storage_entry_keys: Vec<StorageKey>,
validation_hash: Option<[u8; 32]>,
_marker: std::marker::PhantomData<(ReturnTy, Fetchable, Defaultable, Iterable)>,
}
impl<ReturnTy, Fetchable, Defaultable, Iterable>
StaticStorageAddress<ReturnTy, Fetchable, Defaultable, Iterable>
/// A typical storage address constructed at runtime rather than via the `subxt` macro; this
/// has no restriction on what it can be used for (since we don't statically know).
pub type DynamicAddress<StorageKey> =
Address<StorageKey, DecodedValueThunk, Yes, Yes, Yes>;
impl<StorageKey, ReturnTy, Fetchable, Defaultable, Iterable>
Address<StorageKey, ReturnTy, Fetchable, Defaultable, Iterable>
where
StorageKey: EncodeWithMetadata,
ReturnTy: DecodeWithMetadata,
{
/// Create a new [`StaticStorageAddress`] that will be validated
/// against node metadata using the hash given.
/// Create a new [`Address`] to use to access a storage entry.
pub fn new(
pallet_name: impl Into<String>,
entry_name: impl Into<String>,
storage_entry_keys: Vec<StorageKey>,
) -> Self {
Self {
pallet_name: Cow::Owned(pallet_name.into()),
entry_name: Cow::Owned(entry_name.into()),
storage_entry_keys: storage_entry_keys.into_iter().collect(),
validation_hash: None,
_marker: std::marker::PhantomData,
}
}
/// Create a new [`Address`] using static strings for the pallet and call name.
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
pallet_name: &'static str,
entry_name: &'static str,
storage_entry_keys: Vec<StorageMapKey>,
storage_entry_keys: Vec<StorageKey>,
hash: [u8; 32],
) -> Self {
Self {
pallet_name,
entry_name,
storage_entry_keys,
pallet_name: Cow::Borrowed(pallet_name),
entry_name: Cow::Borrowed(entry_name),
storage_entry_keys: storage_entry_keys.into_iter().collect(),
validation_hash: Some(hash),
_marker: std::marker::PhantomData,
}
@@ -107,100 +127,24 @@ where
}
}
/// Return bytes representing this storage entry.
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
super::utils::write_storage_address_root_bytes(self, &mut bytes);
for entry in &self.storage_entry_keys {
entry.to_bytes(&mut bytes);
}
bytes
}
/// Return bytes representing the root of this storage entry (ie a hash of
/// the pallet and entry name).
/// the pallet and entry name). Use [`crate::storage::StorageClient::address_bytes()`]
/// to obtain the bytes representing the entire address.
pub fn to_root_bytes(&self) -> Vec<u8> {
super::utils::storage_address_root_bytes(self)
}
}
impl<ReturnTy, Fetchable, Defaultable, Iterable> StorageAddress
for StaticStorageAddress<ReturnTy, Fetchable, Defaultable, Iterable>
impl<StorageKey, ReturnTy, Fetchable, Defaultable, Iterable> StorageAddress
for Address<StorageKey, ReturnTy, Fetchable, Defaultable, Iterable>
where
StorageKey: EncodeWithMetadata,
ReturnTy: DecodeWithMetadata,
{
type Target = ReturnTy;
type IsFetchable = Fetchable;
type IsDefaultable = Defaultable;
type IsIterable = Iterable;
type IsFetchable = Fetchable;
fn pallet_name(&self) -> &str {
self.pallet_name
}
fn entry_name(&self) -> &str {
self.entry_name
}
fn append_entry_bytes(
&self,
_metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error> {
for entry in &self.storage_entry_keys {
entry.to_bytes(bytes);
}
Ok(())
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
/// This represents a dynamically generated storage address.
pub struct DynamicStorageAddress<'a, Encodable> {
pallet_name: Cow<'a, str>,
entry_name: Cow<'a, str>,
storage_entry_keys: Vec<Encodable>,
}
/// Construct a new dynamic storage lookup to the root of some entry.
pub fn dynamic_root<'a>(
pallet_name: impl Into<Cow<'a, str>>,
entry_name: impl Into<Cow<'a, str>>,
) -> DynamicStorageAddress<'a, Value> {
DynamicStorageAddress {
pallet_name: pallet_name.into(),
entry_name: entry_name.into(),
storage_entry_keys: vec![],
}
}
/// Construct a new dynamic storage lookup.
pub fn dynamic<'a, Encodable: EncodeWithMetadata>(
pallet_name: impl Into<Cow<'a, str>>,
entry_name: impl Into<Cow<'a, str>>,
storage_entry_keys: Vec<Encodable>,
) -> DynamicStorageAddress<'a, Encodable> {
DynamicStorageAddress {
pallet_name: pallet_name.into(),
entry_name: entry_name.into(),
storage_entry_keys,
}
}
impl<'a, Encodable> StorageAddress for DynamicStorageAddress<'a, Encodable>
where
Encodable: EncodeWithMetadata,
{
type Target = DecodedValueThunk;
// For dynamic types, we have no static guarantees about any of
// this stuff, so we just allow it and let it fail at runtime:
type IsFetchable = Yes;
type IsDefaultable = Yes;
type IsIterable = Yes;
fn pallet_name(&self) -> &str {
&self.pallet_name
@@ -239,11 +183,9 @@ where
// If the key is not a tuple, encode a single value to the key type.
let type_ids = match ty.type_def() {
TypeDef::Tuple(tuple) => {
tuple.fields().iter().map(|f| f.id()).collect()
}
_other => {
vec![key.id()]
either::Either::Left(tuple.fields().iter().map(|f| f.id()))
}
_other => either::Either::Right(std::iter::once(key.id())),
};
if type_ids.len() != self.storage_entry_keys.len() {
@@ -257,19 +199,19 @@ where
if hashers.len() == 1 {
// One hasher; hash a tuple of all SCALE encoded bytes with the one hash function.
let mut input = Vec::new();
for (key, type_id) in self.storage_entry_keys.iter().zip(type_ids) {
let iter = self.storage_entry_keys.iter().zip(type_ids);
for (key, type_id) in iter {
key.encode_with_metadata(type_id, metadata, &mut input)?;
}
super::storage_map_key::hash_bytes(&input, &hashers[0], bytes);
hash_bytes(&input, &hashers[0], bytes);
Ok(())
} else if hashers.len() == type_ids.len() {
let iter = self.storage_entry_keys.iter().zip(type_ids).zip(hashers);
// A hasher per field; encode and hash each field independently.
for ((key, type_id), hasher) in
self.storage_entry_keys.iter().zip(type_ids).zip(hashers)
{
for ((key, type_id), hasher) in iter {
let mut input = Vec::new();
key.encode_with_metadata(type_id, metadata, &mut input)?;
super::storage_map_key::hash_bytes(&input, hasher, bytes);
hash_bytes(&input, hasher, bytes);
}
Ok(())
} else {
@@ -283,4 +225,68 @@ where
}
}
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
/// A static storage key; this is some pre-encoded bytes
/// likely provided by the generated interface.
pub struct StaticStorageMapKey(pub Vec<u8>);
impl StaticStorageMapKey {
/// Create a new [`StaticStorageMapKey`] by pre-encoding static data.
pub fn new<Encodable: codec::Encode>(value: Encodable) -> StaticStorageMapKey {
Self(value.encode())
}
}
impl EncodeWithMetadata for StaticStorageMapKey {
fn encode_with_metadata(
&self,
_type_id: u32,
_metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error> {
// We just use the already-encoded bytes for a static storage key:
bytes.extend(&self.0);
Ok(())
}
}
/// Construct a new dynamic storage lookup to the root of some entry.
pub fn dynamic_root(
pallet_name: impl Into<String>,
entry_name: impl Into<String>,
) -> DynamicAddress<Value> {
DynamicAddress::new(pallet_name, entry_name, vec![])
}
/// Construct a new dynamic storage lookup.
pub fn dynamic<StorageKey: EncodeWithMetadata>(
pallet_name: impl Into<String>,
entry_name: impl Into<String>,
storage_entry_keys: Vec<StorageKey>,
) -> DynamicAddress<StorageKey> {
DynamicAddress::new(pallet_name, entry_name, storage_entry_keys)
}
/// Take some SCALE encoded bytes and a [`StorageHasher`] and hash the bytes accordingly.
fn hash_bytes(input: &[u8], hasher: &StorageHasher, bytes: &mut Vec<u8>) {
match hasher {
StorageHasher::Identity => bytes.extend(input),
StorageHasher::Blake2_128 => bytes.extend(sp_core_hashing::blake2_128(input)),
StorageHasher::Blake2_128Concat => {
bytes.extend(sp_core_hashing::blake2_128(input));
bytes.extend(input);
}
StorageHasher::Blake2_256 => bytes.extend(sp_core_hashing::blake2_256(input)),
StorageHasher::Twox128 => bytes.extend(sp_core_hashing::twox_128(input)),
StorageHasher::Twox256 => bytes.extend(sp_core_hashing::twox_256(input)),
StorageHasher::Twox64Concat => {
bytes.extend(sp_core_hashing::twox_64(input));
bytes.extend(input);
}
}
}
+22
View File
@@ -7,6 +7,7 @@ use super::{
validate_storage_address,
Storage,
},
utils,
StorageAddress,
};
@@ -57,6 +58,27 @@ where
) -> Result<(), Error> {
validate_storage_address(address, &self.client.metadata())
}
/// Convert some storage address into the raw bytes that would be submitted to the node in order
/// to retrieve the entries at the root of the associated address.
pub fn address_root_bytes<Address: StorageAddress>(
&self,
address: &Address,
) -> Vec<u8> {
utils::storage_address_root_bytes(address)
}
/// Convert some storage address into the raw bytes that would be submitted to the node in order
/// to retrieve an entry. This fails if [`StorageAddress::append_entry_bytes`] does; in the built-in
/// implementation this would be if the pallet and storage entry being asked for is not available on the
/// node you're communicating with, or if the metadata is missing some type information (which should not
/// happen).
pub fn address_bytes<Address: StorageAddress>(
&self,
address: &Address,
) -> Result<Vec<u8>, Error> {
utils::storage_address_bytes(address, &self.client.metadata())
}
}
impl<T, Client> StorageClient<T, Client>
-52
View File
@@ -1,52 +0,0 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use codec::Encode;
// We use this type a bunch, so export it from here.
pub use frame_metadata::StorageHasher;
/// Storage key for a Map.
#[derive(Clone)]
pub struct StorageMapKey {
value: Vec<u8>,
hasher: StorageHasher,
}
impl StorageMapKey {
/// Create a new [`StorageMapKey`] by pre-encoding static data and pairing it with a hasher.
pub fn new<Encodable: Encode>(
value: Encodable,
hasher: StorageHasher,
) -> StorageMapKey {
Self {
value: value.encode(),
hasher,
}
}
/// Convert this [`StorageMapKey`] into bytes and append them to some existing bytes.
pub fn to_bytes(&self, bytes: &mut Vec<u8>) {
hash_bytes(&self.value, &self.hasher, bytes)
}
}
/// Take some SCALE encoded bytes and a [`StorageHasher`] and hash the bytes accordingly.
pub(super) fn hash_bytes(input: &[u8], hasher: &StorageHasher, bytes: &mut Vec<u8>) {
match hasher {
StorageHasher::Identity => bytes.extend(input),
StorageHasher::Blake2_128 => bytes.extend(sp_core_hashing::blake2_128(input)),
StorageHasher::Blake2_128Concat => {
bytes.extend(sp_core_hashing::blake2_128(input));
bytes.extend(input);
}
StorageHasher::Blake2_256 => bytes.extend(sp_core_hashing::blake2_256(input)),
StorageHasher::Twox128 => bytes.extend(sp_core_hashing::twox_128(input)),
StorageHasher::Twox256 => bytes.extend(sp_core_hashing::twox_256(input)),
StorageHasher::Twox64Concat => {
bytes.extend(sp_core_hashing::twox_64(input));
bytes.extend(input);
}
}
}
+39 -45
View File
@@ -7,10 +7,7 @@ use super::storage_address::{
Yes,
};
use crate::{
client::{
OfflineClientT,
OnlineClientT,
},
client::OnlineClientT,
error::Error,
metadata::{
DecodeWithMetadata,
@@ -50,32 +47,16 @@ impl<T: Config, Client> Storage<T, Client> {
}
}
impl<T, Client> Storage<T, Client>
where
T: Config,
Client: OfflineClientT<T>,
{
/// Run the validation logic against some storage address you'd like to access.
///
/// Method has the same meaning as [`StorageClient::validate`](super::storage_client::StorageClient::validate).
pub fn validate<Address: StorageAddress>(
&self,
address: &Address,
) -> Result<(), Error> {
validate_storage_address(address, &self.client.metadata())
}
}
impl<T, Client> Storage<T, Client>
where
T: Config,
Client: OnlineClientT<T>,
{
/// Fetch the raw encoded value at the address/key given.
pub fn fetch_raw<'a>(
pub fn fetch_raw<'address>(
&self,
key: &'a [u8],
) -> impl Future<Output = Result<Option<Vec<u8>>, Error>> + 'a {
key: &'address [u8],
) -> impl Future<Output = Result<Option<Vec<u8>>, Error>> + 'address {
let client = self.client.clone();
let block_hash = self.block_hash;
// Ensure that the returned future doesn't have a lifetime tied to api.storage(),
@@ -116,14 +97,12 @@ where
/// println!("Value: {:?}", value);
/// # }
/// ```
pub fn fetch<'a, Address>(
pub fn fetch<'address, Address>(
&self,
address: &'a Address,
) -> impl Future<
Output = Result<Option<<Address::Target as DecodeWithMetadata>::Target>, Error>,
> + 'a
address: &'address Address,
) -> impl Future<Output = Result<Option<Address::Target>, Error>> + 'address
where
Address: StorageAddress<IsFetchable = Yes> + 'a,
Address: StorageAddress<IsFetchable = Yes> + 'address,
{
let client = self.clone();
async move {
@@ -131,13 +110,13 @@ where
// is likely to actually correspond to a real storage entry or not.
// if not, it means static codegen doesn't line up with runtime
// metadata.
client.validate(address)?;
validate_storage_address(address, &client.client.metadata())?;
// Look up the return type ID to enable DecodeWithMetadata:
let metadata = client.client.metadata();
let lookup_bytes = super::utils::storage_address_bytes(address, &metadata)?;
if let Some(data) = client.fetch_raw(&lookup_bytes).await? {
let val = <Address::Target as DecodeWithMetadata>::decode_storage_with_metadata(
let val = decode_storage_with_metadata::<Address::Target>(
&mut &*data,
address.pallet_name(),
address.entry_name(),
@@ -151,13 +130,12 @@ where
}
/// Fetch a StorageKey that has a default value with an optional block hash.
pub fn fetch_or_default<'a, Address>(
pub fn fetch_or_default<'address, Address>(
&self,
address: &'a Address,
) -> impl Future<Output = Result<<Address::Target as DecodeWithMetadata>::Target, Error>>
+ 'a
address: &'address Address,
) -> impl Future<Output = Result<Address::Target, Error>> + 'address
where
Address: StorageAddress<IsFetchable = Yes, IsDefaultable = Yes> + 'a,
Address: StorageAddress<IsFetchable = Yes, IsDefaultable = Yes> + 'address,
{
let client = self.clone();
async move {
@@ -176,7 +154,7 @@ where
return_type_from_storage_entry_type(&storage_metadata.ty);
let bytes = &mut &storage_metadata.default[..];
let val = <Address::Target as DecodeWithMetadata>::decode_with_metadata(
let val = Address::Target::decode_with_metadata(
bytes,
return_ty_id,
&metadata,
@@ -189,12 +167,12 @@ where
/// Fetch up to `count` keys for a storage map in lexicographic order.
///
/// Supports pagination by passing a value to `start_key`.
pub fn fetch_keys<'a>(
pub fn fetch_keys<'address>(
&self,
key: &'a [u8],
key: &'address [u8],
count: u32,
start_key: Option<&'a [u8]>,
) -> impl Future<Output = Result<Vec<StorageKey>, Error>> + 'a {
start_key: Option<&'address [u8]>,
) -> impl Future<Output = Result<Vec<StorageKey>, Error>> + 'address {
let client = self.client.clone();
let block_hash = self.block_hash;
async move {
@@ -252,7 +230,7 @@ where
// is likely to actually correspond to a real storage entry or not.
// if not, it means static codegen doesn't line up with runtime
// metadata.
client.validate(&address)?;
validate_storage_address(&address, &client.client.metadata())?;
let metadata = client.client.metadata();
@@ -303,9 +281,7 @@ where
ReturnTy: DecodeWithMetadata,
{
/// Returns the next key value pair from a map.
pub async fn next(
&mut self,
) -> Result<Option<(StorageKey, ReturnTy::Target)>, Error> {
pub async fn next(&mut self) -> Result<Option<(StorageKey, ReturnTy)>, Error> {
loop {
if let Some((k, v)) = self.buffer.pop() {
let val = ReturnTy::decode_with_metadata(
@@ -402,3 +378,21 @@ fn return_type_from_storage_entry_type(entry: &StorageEntryType<PortableForm>) -
StorageEntryType::Map { value, .. } => value.id(),
}
}
/// Given some bytes, a pallet and storage name, decode the response.
fn decode_storage_with_metadata<T: DecodeWithMetadata>(
bytes: &mut &[u8],
pallet_name: &str,
storage_entry: &str,
metadata: &Metadata,
) -> Result<T, Error> {
let ty = &metadata.pallet(pallet_name)?.storage(storage_entry)?.ty;
let id = match ty {
StorageEntryType::Plain(ty) => ty.id(),
StorageEntryType::Map { value, .. } => value.id(),
};
let val = T::decode_with_metadata(bytes, id, metadata)?;
Ok(val)
}
+5 -3
View File
@@ -14,7 +14,7 @@ use crate::{
/// Return the root of a given [`StorageAddress`]: hash the pallet name and entry name
/// and append those bytes to the output.
pub fn write_storage_address_root_bytes<Address: StorageAddress>(
pub(crate) fn write_storage_address_root_bytes<Address: StorageAddress>(
addr: &Address,
out: &mut Vec<u8>,
) {
@@ -24,7 +24,7 @@ pub fn write_storage_address_root_bytes<Address: StorageAddress>(
/// Outputs the [`storage_address_root_bytes`] as well as any additional bytes that represent
/// a lookup in a storage map at that location.
pub fn storage_address_bytes<Address: StorageAddress>(
pub(crate) fn storage_address_bytes<Address: StorageAddress>(
addr: &Address,
metadata: &Metadata,
) -> Result<Vec<u8>, Error> {
@@ -35,7 +35,9 @@ pub fn storage_address_bytes<Address: StorageAddress>(
}
/// Outputs a vector containing the bytes written by [`write_storage_address_root_bytes`].
pub fn storage_address_root_bytes<Address: StorageAddress>(addr: &Address) -> Vec<u8> {
pub(crate) fn storage_address_root_bytes<Address: StorageAddress>(
addr: &Address,
) -> Vec<u8> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
bytes
+3 -2
View File
@@ -27,8 +27,9 @@ pub use self::{
},
tx_payload::{
dynamic,
DynamicTxPayload,
StaticTxPayload,
BoxedPayload,
DynamicPayload,
Payload,
TxPayload,
},
tx_progress::{
+86 -87
View File
@@ -7,19 +7,20 @@
use crate::{
dynamic::Value,
error::{
Error,
MetadataError,
},
error::Error,
metadata::Metadata,
};
use codec::Encode;
use scale_encode::EncodeAsFields;
use scale_value::{
Composite,
ValueDef,
Variant,
};
use std::borrow::Cow;
use std::{
borrow::Cow,
sync::Arc,
};
/// This represents a transaction payload that can be submitted
/// to a node.
@@ -57,31 +58,67 @@ pub struct ValidationDetails<'a> {
pub hash: [u8; 32],
}
/// This represents a statically generated transaction payload.
/// A transaction payload containing some generic `CallData`.
#[derive(Clone, Debug)]
pub struct StaticTxPayload<CallData> {
pallet_name: &'static str,
call_name: &'static str,
pub struct Payload<CallData> {
pallet_name: Cow<'static, str>,
call_name: Cow<'static, str>,
call_data: CallData,
validation_hash: Option<[u8; 32]>,
}
impl<CallData> StaticTxPayload<CallData> {
/// Create a new [`StaticTxPayload`] from static data.
/// A boxed transaction payload.
// Dev Note: Arc used to enable easy cloning (given that we can't have dyn Clone).
pub type BoxedPayload = Payload<Arc<dyn EncodeAsFields + Send + Sync + 'static>>;
/// The type of a payload typically used for dynamic transaction payloads.
pub type DynamicPayload = Payload<Composite<()>>;
impl<CallData> Payload<CallData> {
/// Create a new [`Payload`].
pub fn new(
pallet_name: impl Into<String>,
call_name: impl Into<String>,
call_data: CallData,
) -> Self {
Payload {
pallet_name: Cow::Owned(pallet_name.into()),
call_name: Cow::Owned(call_name.into()),
call_data,
validation_hash: None,
}
}
/// Create a new [`Payload`] using static strings for the pallet and call name.
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
pallet_name: &'static str,
call_name: &'static str,
call_data: CallData,
validation_hash: [u8; 32],
) -> Self {
StaticTxPayload {
pallet_name,
call_name,
Payload {
pallet_name: Cow::Borrowed(pallet_name),
call_name: Cow::Borrowed(call_name),
call_data,
validation_hash: Some(validation_hash),
}
}
/// Box the payload.
pub fn boxed(self) -> BoxedPayload
where
CallData: EncodeAsFields + Send + Sync + 'static,
{
BoxedPayload {
pallet_name: self.pallet_name,
call_name: self.call_name,
call_data: Arc::new(self.call_data),
validation_hash: self.validation_hash,
}
}
/// Do not validate this call prior to submitting it.
pub fn unvalidated(self) -> Self {
Self {
@@ -96,60 +133,16 @@ impl<CallData> StaticTxPayload<CallData> {
}
}
impl<CallData: Encode> TxPayload for StaticTxPayload<CallData> {
fn encode_call_data_to(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), Error> {
let pallet = metadata.pallet(self.pallet_name)?;
let pallet_index = pallet.index();
let call_index = pallet.call_index(self.call_name)?;
pallet_index.encode_to(out);
call_index.encode_to(out);
self.call_data.encode_to(out);
Ok(())
}
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
self.validation_hash.map(|hash| {
ValidationDetails {
pallet_name: self.pallet_name,
call_name: self.call_name,
hash,
}
})
}
}
/// This represents a dynamically generated transaction payload.
#[derive(Clone, Debug)]
pub struct DynamicTxPayload<'a> {
pallet_name: Cow<'a, str>,
call_name: Cow<'a, str>,
fields: Composite<()>,
}
impl<'a> DynamicTxPayload<'a> {
/// Return the pallet name.
pub fn pallet_name(&self) -> &str {
&self.pallet_name
}
/// Return the call name.
pub fn call_name(&self) -> &str {
&self.call_name
}
/// Convert the dynamic payload into a [`Value`]. This is useful
/// if you need to submit this as part of a larger call.
impl Payload<Composite<()>> {
/// Convert the dynamic `Composite` payload into a [`Value`].
/// This is useful if you want to use this as an argument for a
/// larger dynamic call that wants to use this as a nested call.
pub fn into_value(self) -> Value<()> {
let call = Value {
context: (),
value: ValueDef::Variant(Variant {
name: self.call_name.into_owned(),
values: self.fields,
values: self.call_data,
}),
};
@@ -157,37 +150,43 @@ impl<'a> DynamicTxPayload<'a> {
}
}
/// Construct a new dynamic transaction payload to submit to a node.
pub fn dynamic<'a>(
pallet_name: impl Into<Cow<'a, str>>,
call_name: impl Into<Cow<'a, str>>,
fields: impl Into<Composite<()>>,
) -> DynamicTxPayload<'a> {
DynamicTxPayload {
pallet_name: pallet_name.into(),
call_name: call_name.into(),
fields: fields.into(),
}
}
impl<'a> TxPayload for DynamicTxPayload<'a> {
impl<CallData: EncodeAsFields> TxPayload for Payload<CallData> {
fn encode_call_data_to(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), Error> {
let pallet = metadata.pallet(&self.pallet_name)?;
let call_id = pallet.call_ty_id().ok_or(MetadataError::CallNotFound)?;
let call_value = Value {
context: (),
value: ValueDef::Variant(Variant {
name: self.call_name.to_string(),
values: self.fields.clone(),
}),
};
let call = pallet.call(&self.call_name)?;
pallet.index().encode_to(out);
scale_value::scale::encode_as_type(&call_value, call_id, metadata.types(), out)?;
let pallet_index = pallet.index();
let call_index = call.index();
pallet_index.encode_to(out);
call_index.encode_to(out);
self.call_data
.encode_as_fields_to(call.fields(), metadata.types(), out)?;
Ok(())
}
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
self.validation_hash.map(|hash| {
ValidationDetails {
pallet_name: &self.pallet_name,
call_name: &self.call_name,
hash,
}
})
}
}
/// Construct a transaction at runtime; essentially an alias to [`Payload::new()`]
/// which provides a [`Composite`] value for the call data.
pub fn dynamic(
pallet_name: impl Into<String>,
call_name: impl Into<String>,
call_data: impl Into<Composite<()>>,
) -> DynamicPayload {
Payload::new(pallet_name, call_name, call_data.into())
}
+12 -1
View File
@@ -18,7 +18,18 @@ use serde::{
/// 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)]
#[derive(
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
Debug,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
)]
pub struct AccountId32(pub [u8; 32]);
impl AsRef<[u8]> for AccountId32 {
+111 -62
View File
@@ -16,55 +16,53 @@ use scale_bits::{
},
Bits,
};
use scale_decode::IntoVisitor;
use std::marker::PhantomData;
macro_rules! store {
($ident: ident; $(($ty: ident, $wrapped: ty)),*) => {
/// 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 $ident {
/// Corresponding `scale_bits::StoreFormat` value.
const FORMAT: StoreFormat;
/// Number of bits that the backing store types holds.
const BITS: u32;
}
/// 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);
$(
impl $ident for $wrapped {
const FORMAT: StoreFormat = StoreFormat::$ty;
const BITS: u32 = <$wrapped>::BITS;
}
)*
};
}
macro_rules! order {
($ident: ident; $($ty: ident),*) => {
/// 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 $ident {
/// Corresponding `scale_bits::OrderFormat` value.
const FORMAT: OrderFormat;
}
$(
#[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 $ident for $ty {
const FORMAT: OrderFormat = OrderFormat::$ty;
}
)*
};
}
store!(BitStore; (U8, u8), (U16, u16), (U32, u32), (U64, u64));
order!(BitOrder; Lsb0, Msb0);
/// 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 {
@@ -77,29 +75,29 @@ fn bit_format<Store: BitStore, Order: BitOrder>() -> 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: BitStore, Order: BitOrder>(
Bits,
PhantomData<Store>,
PhantomData<Order>,
);
pub struct DecodedBits<Store, Order> {
bits: Bits,
_marker: PhantomData<(Store, Order)>,
}
impl<Store: BitStore, Order: BitOrder> DecodedBits<Store, Order> {
impl<Store, Order> DecodedBits<Store, Order> {
/// Extracts the underlying `scale_bits::Bits` value.
pub fn into_bits(self) -> Bits {
self.0
self.bits
}
/// References the underlying `scale_bits::Bits` value.
pub fn as_bits(&self) -> &Bits {
&self.0
&self.bits
}
}
impl<Store: BitStore, Order: BitOrder> core::iter::FromIterator<bool>
for DecodedBits<Store, Order>
{
impl<Store, Order> core::iter::FromIterator<bool> for DecodedBits<Store, Order> {
fn from_iter<T: IntoIterator<Item = bool>>(iter: T) -> Self {
DecodedBits(Bits::from_iter(iter), PhantomData, PhantomData)
DecodedBits {
bits: Bits::from_iter(iter),
_marker: PhantomData,
}
}
}
@@ -132,21 +130,72 @@ impl<Store: BitStore, Order: BitOrder> codec::Decode for DecodedBits<Store, Orde
let bits = decoder.collect::<Result<Vec<_>, _>>()?;
let bits = Bits::from_iter(bits);
Ok(DecodedBits(bits, PhantomData, PhantomData))
Ok(DecodedBits {
bits,
_marker: PhantomData,
})
}
}
impl<Store: BitStore, Order: BitOrder> codec::Encode for DecodedBits<Store, Order> {
fn size_hint(&self) -> usize {
self.0.size_hint()
self.bits.size_hint()
}
fn encoded_size(&self) -> usize {
self.0.encoded_size()
self.bits.encoded_size()
}
fn encode(&self) -> Vec<u8> {
scale_bits::encode_using_format(self.0.iter(), bit_format::<Store, Order>())
scale_bits::encode_using_format(self.bits.iter(), bit_format::<Store, Order>())
}
}
#[doc(hidden)]
pub struct DecodedBitsVisitor<S, O>(std::marker::PhantomData<(S, O)>);
impl<Store, Order> scale_decode::Visitor for DecodedBitsVisitor<Store, Order> {
type Value<'scale, 'info> = DecodedBits<Store, Order>;
type Error = scale_decode::Error;
fn unchecked_decode_as_type<'scale, 'info>(
self,
input: &mut &'scale [u8],
type_id: scale_decode::visitor::TypeId,
types: &'info scale_info::PortableRegistry,
) -> scale_decode::visitor::DecodeAsTypeResult<
Self,
Result<Self::Value<'scale, 'info>, Self::Error>,
> {
let res = scale_decode::visitor::decode_with_visitor(
input,
type_id.0,
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 Visitor = DecodedBitsVisitor<Store, Order>;
fn into_visitor() -> Self::Visitor {
DecodedBitsVisitor(PhantomData)
}
}
impl<Store, Order> scale_encode::EncodeAsType for DecodedBits<Store, Order> {
fn encode_as_type_to(
&self,
type_id: u32,
types: &scale_info::PortableRegistry,
out: &mut Vec<u8>,
) -> Result<(), scale_encode::Error> {
self.bits.encode_as_type_to(type_id, types, out)
}
}
+5 -49
View File
@@ -4,14 +4,14 @@
//! Miscellaneous utility helpers.
pub mod account_id;
mod account_id;
pub mod bits;
pub mod multi_address;
pub mod multi_signature;
mod multi_address;
mod multi_signature;
mod wrapper_opaque;
use codec::{
Decode,
DecodeAll,
Encode,
};
use derivative::Derivative;
@@ -19,6 +19,7 @@ use derivative::Derivative;
pub use account_id::AccountId32;
pub use multi_address::MultiAddress;
pub use multi_signature::MultiSignature;
pub use wrapper_opaque::WrapperKeepOpaque;
// Used in codegen
#[doc(hidden)]
@@ -39,51 +40,6 @@ impl codec::Encode for Encoded {
}
}
/// 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.
#[derive(Derivative, Encode, Decode)]
#[derivative(
Debug(bound = ""),
Clone(bound = ""),
PartialEq(bound = ""),
Eq(bound = ""),
Default(bound = ""),
Hash(bound = "")
)]
pub struct WrapperKeepOpaque<T> {
data: Vec<u8>,
_phantom: PhantomDataSendSync<T>,
}
impl<T: Decode> 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> {
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(),
}
}
}
/// A version of [`std::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).
+12 -1
View File
@@ -14,7 +14,18 @@ use codec::{
/// 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)]
#[derive(
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
Debug,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
)]
pub enum MultiAddress<AccountId, AccountIndex> {
/// It's an account ID (pubkey).
Id(AccountId),
+266
View File
@@ -0,0 +1,266 @@
// Copyright 2019-2022 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 derivative::Derivative;
use scale_decode::{
IntoVisitor,
Visitor,
};
use scale_encode::EncodeAsType;
/// 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(Derivative, Encode, Decode)]
#[derivative(
Debug(bound = ""),
Clone(bound = ""),
PartialEq(bound = ""),
Eq(bound = ""),
Default(bound = ""),
Hash(bound = "")
)]
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(
&self,
type_id: u32,
types: &scale_info::PortableRegistry,
out: &mut Vec<u8>,
) -> Result<(), scale_encode::Error> {
use scale_encode::error::{
Error,
ErrorKind,
Kind,
};
let Some(ty) = types.resolve(type_id) else {
return Err(Error::new(ErrorKind::TypeNotFound(type_id)))
};
// Do a basic check that the target shape lines up.
let scale_info::TypeDef::Composite(_) = ty.type_def() else {
return Err(Error::new(ErrorKind::WrongShape {
actual: Kind::Struct,
expected: type_id,
}))
};
// Check that the name also lines up.
if ty.path().ident().as_deref() != Some("WrapperKeepOpaque") {
return Err(Error::new(ErrorKind::WrongShape {
actual: Kind::Struct,
expected: type_id,
}))
}
// Just blat the bytes out.
self.data.encode_to(out);
Ok(())
}
}
pub struct WrapperKeepOpaqueVisitor<T>(std::marker::PhantomData<T>);
impl<T> Visitor for WrapperKeepOpaqueVisitor<T> {
type Value<'scale, 'info> = WrapperKeepOpaque<T>;
type Error = scale_decode::Error;
fn visit_composite<'scale, 'info>(
self,
value: &mut scale_decode::visitor::types::Composite<'scale, 'info>,
_type_id: scale_decode::visitor::TypeId,
) -> Result<Self::Value<'scale, 'info>, Self::Error> {
use scale_decode::error::{
Error,
ErrorKind,
};
if value.path().ident().as_deref() != Some("WrapperKeepOpaque") {
return Err(Error::new(ErrorKind::Custom(
"Type to decode is not 'WrapperTypeKeepOpaque'".into(),
)))
}
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::new(ErrorKind::Custom("WrapperTypeKeepOpaque compact encoded length doesn't line up with encoded byte len".into())));
}
Ok(WrapperKeepOpaque {
data: field.bytes().to_vec(),
_phantom: PhantomDataSendSync::new(),
})
}
}
impl<T> IntoVisitor for WrapperKeepOpaque<T> {
type Visitor = WrapperKeepOpaqueVisitor<T>;
fn into_visitor() -> Self::Visitor {
WrapperKeepOpaqueVisitor(std::marker::PhantomData)
}
}
#[cfg(test)]
mod test {
use scale_decode::DecodeAsType;
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
+ std::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]));
}
}