mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 14:01:06 +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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user