mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 19:01:08 +00:00
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:
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -9,8 +9,8 @@ mod constants_client;
|
||||
|
||||
pub use constant_address::{
|
||||
dynamic,
|
||||
Address,
|
||||
ConstantAddress,
|
||||
DynamicConstantAddress,
|
||||
StaticConstantAddress,
|
||||
DynamicAddress,
|
||||
};
|
||||
pub use constants_client::ConstantsClient;
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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.
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -27,8 +27,9 @@ pub use self::{
|
||||
},
|
||||
tx_payload::{
|
||||
dynamic,
|
||||
DynamicTxPayload,
|
||||
StaticTxPayload,
|
||||
BoxedPayload,
|
||||
DynamicPayload,
|
||||
Payload,
|
||||
TxPayload,
|
||||
},
|
||||
tx_progress::{
|
||||
|
||||
+86
-87
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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).
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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]));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user