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

* WIP EncodeAsType and DecodeAsType

* remove silly cli experiment code

* Get things finally compiling with EncodeAsType and DecodeAsType

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

* fix tests

* accomodate scale-value changes

* starting to migrate to EncodeAsType/DecodeAsType

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

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

* fix root event decoding

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

* fmt and clippy

* update Cargo.lock

* remove patched scale-encode

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

* update deps and use released scale-decode

* update scale-value to latest to remove git branch

* Apply suggestions from code review

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

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

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

* StaticTxPayload and DynamicTxPayload rolled into single Payload struct

* StaticStorageAddress and DynamicStorageAddress into single Address struct

* Fix storage address byte retrieval

* StaticConstantAddress and DynamicConstantAddress => Address

* Simplify storage codegen to fix test

* Add comments

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

* remove unnecessary clone

* Fix docs and failing UI test

* root_bytes -> to_root_bytes

* document error case in StorageClient::address_bytes()

---------

Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
This commit is contained in:
James Wilson
2023-03-21 15:31:13 +00:00
committed by GitHub
parent c9527abaa8
commit c63ff6ec6d
50 changed files with 9965 additions and 6262 deletions
+154 -48
View File
@@ -12,14 +12,16 @@ use crate::{
client::OnlineClientT,
error::Error,
events::events_client::get_event_bytes,
metadata::EventMetadata,
metadata::{
DecodeWithMetadata,
EventMetadata,
},
Config,
Metadata,
};
use codec::{
Compact,
Decode,
Error as CodecError,
};
use derivative::Derivative;
use std::sync::Arc;
@@ -203,6 +205,7 @@ impl<T: Config> Events<T> {
#[derive(Debug, Clone)]
pub struct EventDetails {
phase: Phase,
/// The index of the event in the list of events in a given block.
index: u32,
all_bytes: Arc<[u8]>,
// start of the bytes (phase, pallet/variant index and then fields and then topic to follow).
@@ -248,12 +251,13 @@ impl EventDetails {
// Skip over the bytes belonging to this event.
for field_metadata in event_metadata.fields() {
// Skip over the bytes for this field:
scale_decode::decode(
scale_decode::visitor::decode_with_visitor(
input,
field_metadata.type_id(),
field_metadata.ty().id(),
&metadata.runtime_metadata().types,
scale_decode::visitor::IgnoreVisitor,
)?;
)
.map_err(scale_decode::Error::from)?;
}
// the end of the field bytes.
@@ -343,63 +347,98 @@ impl EventDetails {
let bytes = &mut self.field_bytes();
let event_metadata = self.event_metadata();
// If the first field has a name, we assume that the rest do too (it'll either
// be a named struct or a tuple type). If no fields, assume unnamed.
let is_named = event_metadata
.fields()
.get(0)
.map(|fm| fm.name().is_some())
.unwrap_or(false);
use scale_decode::DecodeAsFields;
let decoded =
<scale_value::Composite<scale_value::scale::TypeId>>::decode_as_fields(
bytes,
event_metadata.fields(),
&self.metadata.runtime_metadata().types,
)?;
if !is_named {
let mut event_values = vec![];
for field_metadata in event_metadata.fields() {
let value = scale_value::scale::decode_as_type(
bytes,
field_metadata.type_id(),
&self.metadata.runtime_metadata().types,
)?;
event_values.push(value);
}
Ok(scale_value::Composite::Unnamed(event_values))
} else {
let mut event_values = vec![];
for field_metadata in event_metadata.fields() {
let value = scale_value::scale::decode_as_type(
bytes,
field_metadata.type_id(),
&self.metadata.runtime_metadata().types,
)?;
event_values
.push((field_metadata.name().unwrap_or_default().to_string(), value));
}
Ok(scale_value::Composite::Named(event_values))
}
Ok(decoded)
}
/// Attempt to decode these [`EventDetails`] into a specific static event.
/// This targets the fields within the event directly. You can also attempt to
/// decode the entirety of the event type (including the pallet and event
/// variants) using [`EventDetails::as_root_event()`].
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, CodecError> {
/// Attempt to statically decode these [`EventDetails`] into a type representing the event
/// fields. This leans directly on [`codec::Decode`]. You can also attempt to decode the entirety
/// of the event using [`EventDetails::as_root_event()`], which is more lenient because it's able
/// to lean on [`scale_decode::DecodeAsType`].
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, Error> {
let ev_metadata = self.event_metadata();
if ev_metadata.pallet() == E::PALLET && ev_metadata.event() == E::EVENT {
Ok(Some(E::decode(&mut self.field_bytes())?))
let decoded = E::decode_as_fields(
&mut self.field_bytes(),
ev_metadata.fields(),
self.metadata.types(),
)?;
Ok(Some(decoded))
} else {
Ok(None)
}
}
/// Attempt to decode these [`EventDetails`] into a pallet event type (which includes
/// the pallet enum variants as well as the event fields). These events can be found in
/// the static codegen under a path like `pallet_name::Event`.
pub fn as_pallet_event<E: DecodeWithMetadata>(&self) -> Result<E, Error> {
let pallet = self.metadata.pallet(self.pallet_name())?;
let event_ty = pallet.event_ty_id().ok_or_else(|| {
Error::Metadata(crate::metadata::MetadataError::EventNotFound(
pallet.index(),
self.variant_index(),
))
})?;
// Ignore the root enum index, so start 1 byte after that:
let start_idx = self.event_start_idx + 1;
let decoded = E::decode_with_metadata(
&mut &self.all_bytes[start_idx..self.event_fields_end_idx],
event_ty,
&self.metadata,
)?;
Ok(decoded)
}
/// Attempt to decode these [`EventDetails`] into a root event type (which includes
/// the pallet and event enum variants as well as the event fields). A compatible
/// type for this is exposed via static codegen as a root level `Event` type.
pub fn as_root_event<E: Decode>(&self) -> Result<E, CodecError> {
E::decode(&mut &self.all_bytes[self.event_start_idx..self.event_fields_end_idx])
pub fn as_root_event<E: RootEvent>(&self) -> Result<E, Error> {
let pallet_bytes =
&self.all_bytes[self.event_start_idx + 1..self.event_fields_end_idx];
let pallet = self.metadata.pallet(self.pallet_name())?;
let pallet_event_ty = pallet.event_ty_id().ok_or_else(|| {
Error::Metadata(crate::metadata::MetadataError::EventNotFound(
pallet.index(),
self.variant_index(),
))
})?;
E::root_event(
pallet_bytes,
self.pallet_name(),
pallet_event_ty,
&self.metadata,
)
}
}
/// This trait is implemented on the statically generated root event type, so that we're able
/// to decode it properly via a pallet event that impls `DecodeAsMetadata`. This is necessary
/// becasue the "root event" type is generated using pallet info but doesn't actually exist in the
/// metadata types, so we have no easy way to decode things into it via type information and need a
/// little help via codegen.
#[doc(hidden)]
pub trait RootEvent: Sized {
/// Given details of the pallet event we want to decode, and the name of the pallet, try to hand
/// back a "root event".
fn root_event(
pallet_bytes: &[u8],
pallet_name: &str,
pallet_event_ty: u32,
metadata: &Metadata,
) -> Result<Self, Error>;
}
/// Event related test utilities used outside this module.
#[cfg(test)]
pub(crate) mod test_utils {
@@ -425,11 +464,40 @@ pub(crate) mod test_utils {
use std::convert::TryFrom;
/// An "outer" events enum containing exactly one event.
#[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq, Eq)]
#[derive(
Encode,
Decode,
TypeInfo,
Clone,
Debug,
PartialEq,
Eq,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
)]
pub enum AllEvents<Ev> {
Test(Ev),
}
// We need this in order to be able to decode into a root event type:
impl<Ev: DecodeWithMetadata> RootEvent for AllEvents<Ev> {
fn root_event(
mut bytes: &[u8],
pallet_name: &str,
pallet_event_ty: u32,
metadata: &Metadata,
) -> Result<Self, Error> {
if pallet_name == "Test" {
return Ok(AllEvents::Test(Ev::decode_with_metadata(
&mut bytes,
pallet_event_ty,
metadata,
)?))
}
panic!("Asked for pallet name '{pallet_name}', which isn't in our test AllEvents type")
}
}
/// This encodes to the same format an event is expected to encode to
/// in node System.Events storage.
#[derive(Encode)]
@@ -556,6 +624,7 @@ mod tests {
// Make sure that the bytes handed back line up with the fields handed back;
// encode the fields back into bytes and they should be equal.
let actual_fields = actual.field_values().expect("can decode field values (1)");
let mut actual_bytes = vec![];
for field in actual_fields.into_values() {
scale_value::scale::encode_as_type(
@@ -587,7 +656,9 @@ mod tests {
#[test]
fn statically_decode_single_root_event() {
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
#[derive(
Clone, Debug, PartialEq, Decode, Encode, TypeInfo, scale_decode::DecodeAsType,
)]
enum Event {
A(u8, bool, Vec<String>),
}
@@ -618,6 +689,41 @@ mod tests {
assert_eq!(decoded_event, AllEvents::Test(event));
}
#[test]
fn statically_decode_single_pallet_event() {
#[derive(
Clone, Debug, PartialEq, Decode, Encode, TypeInfo, scale_decode::DecodeAsType,
)]
enum Event {
A(u8, bool, Vec<String>),
}
// Create fake metadata that knows about our single event, above:
let metadata = metadata::<Event>();
// Encode our events in the format we expect back from a node, and
// construst an Events object to iterate them:
let event = Event::A(1, true, vec!["Hi".into()]);
let events = events::<Event>(
metadata,
vec![event_record(Phase::ApplyExtrinsic(123), event.clone())],
);
let ev = events
.iter()
.next()
.expect("one event expected")
.expect("event should be extracted OK");
// This is the line we're testing; decode into our "pallet event" enum.
let decoded_event = ev
.as_pallet_event::<Event>()
.expect("can decode event into root enum again");
// It should equal the event we put in:
assert_eq!(decoded_event, event);
}
#[test]
fn dynamically_decode_single_event() {
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
+9 -7
View File
@@ -9,16 +9,18 @@
mod events_client;
mod events_type;
pub use events_client::EventsClient;
pub use events_type::{
EventDetails,
Events,
};
use codec::{
Decode,
Encode,
};
pub use events_client::EventsClient;
pub use events_type::{
EventDetails,
Events,
// Used in codegen but hidden from docs:
RootEvent,
};
use scale_decode::DecodeAsFields;
/// Trait to uniquely identify the events's identity from the runtime metadata.
///
@@ -26,7 +28,7 @@ use codec::{
///
/// The trait is utilized to decode emitted events from a block, via obtaining the
/// form of the `Event` from the metadata.
pub trait StaticEvent: Decode {
pub trait StaticEvent: DecodeAsFields {
/// Pallet name.
const PALLET: &'static str;
/// Event name.