use crate::{error::Error, metadata::PalletMetadata, Metadata, StaticEvent};
use alloc::{sync::Arc, vec::Vec};
use codec::{Compact, Decode, Encode};
mod event_details;
mod raw_event_details;
pub use event_details::EventDetails;
pub use raw_event_details::RawEventDetails;
#[derive(Clone)]
pub struct Events<Hash> {
metadata: Metadata,
block_hash: Hash,
event_bytes: Arc<[u8]>,
start_idx: usize,
num_events: u32,
}
impl<Hash: core::fmt::Debug> core::fmt::Debug for Events<Hash> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Events")
.field("block_hash", &self.block_hash)
.field("event_bytes", &self.event_bytes)
.field("start_idx", &self.start_idx)
.field("num_events", &self.num_events)
.finish()
}
}
impl<Hash: Copy + Encode + Decode> Events<Hash> {
pub fn new(metadata: Metadata, block_hash: Hash, event_bytes: Vec<u8>) -> Self {
let cursor = &mut &*event_bytes;
let num_events = <Compact<u32>>::decode(cursor).unwrap_or(Compact(0)).0;
let start_idx = event_bytes.len() - cursor.len();
Self { metadata, block_hash, event_bytes: event_bytes.into(), start_idx, num_events }
}
pub fn len(&self) -> u32 {
self.num_events
}
pub fn is_empty(&self) -> bool {
self.num_events == 0
}
pub fn block_hash(&self) -> Hash {
self.block_hash
}
pub fn event_bytes(&self) -> Arc<[u8]> {
self.event_bytes.clone()
}
pub fn iter(
&self,
) -> impl Iterator<Item = Result<EventDetails<Hash>, Error>> + Send + Sync + 'static {
let event_bytes = self.event_bytes.clone();
let metadata = self.metadata.clone();
let num_events = self.num_events;
let mut pos = self.start_idx;
let mut index = 0;
core::iter::from_fn(move || {
if event_bytes.len() <= pos || num_events == index {
None
} else {
match EventDetails::decode_from(metadata.clone(), event_bytes.clone(), pos, index) {
Ok(event_details) => {
pos += event_details.bytes().len();
index += 1;
Some(Ok(event_details))
},
Err(e) => {
pos = event_bytes.len();
Some(Err(e))
},
}
}
})
}
pub fn find<Ev: StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> + '_ {
self.iter().filter_map(|ev| ev.and_then(|ev| ev.as_event::<Ev>()).transpose())
}
pub fn find_first<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
self.find::<Ev>().next().transpose()
}
pub fn find_last<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
self.find::<Ev>().last().transpose()
}
pub fn has<Ev: StaticEvent>(&self) -> Result<bool, Error> {
Ok(self.find::<Ev>().next().transpose()?.is_some())
}
}
#[doc(hidden)]
pub trait RootEvent: Sized {
fn root_event(
pallet_bytes: &[u8],
pallet_name: &str,
pallet_event_ty: u32,
metadata: &Metadata,
) -> Result<Self, Error>;
}
#[derive(Clone)]
pub struct EventMetadataDetails<'a> {
pub pallet: PalletMetadata<'a>,
pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
test_utils::{
event_record, events, events_raw, metadata_with_version, SupportedMetadataVersions,
},
Phase,
};
use codec::Encode;
use scale_info::TypeInfo;
use scale_value::Value;
use sp_core::H256;
use test_case::test_case;
#[derive(Debug, PartialEq, Clone)]
pub struct TestRawEventDetails {
pub phase: Phase,
pub index: u32,
pub pallet: String,
pub pallet_index: u8,
pub variant: String,
pub variant_index: u8,
pub fields: Vec<Value>,
}
pub fn assert_raw_events_match(actual: EventDetails<H256>, expected: TestRawEventDetails) {
let actual_fields_no_context: Vec<_> = actual
.field_values()
.expect("can decode field values (2)")
.into_values()
.map(|value| value.remove_context())
.collect();
assert_eq!(actual.phase(), expected.phase);
assert_eq!(actual.index(), expected.index);
assert_eq!(actual.pallet_name(), expected.pallet);
assert_eq!(actual.pallet_index(), expected.pallet_index);
assert_eq!(actual.variant_name(), expected.variant);
assert_eq!(actual.variant_index(), expected.variant_index);
assert_eq!(actual_fields_no_context, expected.fields);
}
#[test_case(SupportedMetadataVersions::V14)]
#[test_case(SupportedMetadataVersions::V15)]
fn dynamically_decode_single_event(metadata_version: SupportedMetadataVersions) {
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
enum Event {
A(u8, bool, Vec<String>),
}
let metadata = metadata_with_version::<Event>(metadata_version);
let event = Event::A(1, true, vec!["Hi".into()]);
let events = events::<Event>(
metadata.clone(),
vec![event_record(Phase::ApplyExtrinsic(123), event)],
);
let mut event_details = events.iter();
assert_raw_events_match(
event_details.next().unwrap().unwrap(),
TestRawEventDetails {
phase: Phase::ApplyExtrinsic(123),
index: 0,
pallet: "Test".to_string(),
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![
Value::u128(1u128),
Value::bool(true),
Value::unnamed_composite(vec![Value::string("Hi")]),
],
},
);
assert!(event_details.next().is_none());
}
#[test_case(SupportedMetadataVersions::V14)]
#[test_case(SupportedMetadataVersions::V15)]
fn dynamically_decode_multiple_events(metadata_version: SupportedMetadataVersions) {
#[derive(Clone, Copy, Debug, PartialEq, Decode, Encode, TypeInfo)]
enum Event {
A(u8),
B(bool),
}
let metadata = metadata_with_version::<Event>(metadata_version);
let event1 = Event::A(1);
let event2 = Event::B(true);
let event3 = Event::A(234);
let events = events::<Event>(
metadata.clone(),
vec![
event_record(Phase::Initialization, event1),
event_record(Phase::ApplyExtrinsic(123), event2),
event_record(Phase::Finalization, event3),
],
);
let mut event_details = events.iter();
assert_raw_events_match(
event_details.next().unwrap().unwrap(),
TestRawEventDetails {
index: 0,
phase: Phase::Initialization,
pallet: "Test".to_string(),
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::u128(1u128)],
},
);
assert_raw_events_match(
event_details.next().unwrap().unwrap(),
TestRawEventDetails {
index: 1,
phase: Phase::ApplyExtrinsic(123),
pallet: "Test".to_string(),
pallet_index: 0,
variant: "B".to_string(),
variant_index: 1,
fields: vec![Value::bool(true)],
},
);
assert_raw_events_match(
event_details.next().unwrap().unwrap(),
TestRawEventDetails {
index: 2,
phase: Phase::Finalization,
pallet: "Test".to_string(),
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::u128(234u128)],
},
);
assert!(event_details.next().is_none());
}
#[test_case(SupportedMetadataVersions::V14)]
#[test_case(SupportedMetadataVersions::V15)]
fn dynamically_decode_multiple_events_until_error(metadata_version: SupportedMetadataVersions) {
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
enum Event {
A(u8),
B(bool),
}
let metadata = metadata_with_version::<Event>(metadata_version);
let mut event_bytes = vec![];
event_record(Phase::Initialization, Event::A(1)).encode_to(&mut event_bytes);
event_record(Phase::ApplyExtrinsic(123), Event::B(true)).encode_to(&mut event_bytes);
event_bytes.extend_from_slice(&[3, 127, 45, 0, 2]);
let events = events_raw(
metadata.clone(),
event_bytes,
3, );
let mut events_iter = events.iter();
assert_raw_events_match(
events_iter.next().unwrap().unwrap(),
TestRawEventDetails {
index: 0,
phase: Phase::Initialization,
pallet: "Test".to_string(),
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::u128(1u128)],
},
);
assert_raw_events_match(
events_iter.next().unwrap().unwrap(),
TestRawEventDetails {
index: 1,
phase: Phase::ApplyExtrinsic(123),
pallet: "Test".to_string(),
pallet_index: 0,
variant: "B".to_string(),
variant_index: 1,
fields: vec![Value::bool(true)],
},
);
assert!(events_iter.next().unwrap().is_err());
assert!(events_iter.next().is_none());
assert!(events_iter.next().is_none());
}
#[test_case(SupportedMetadataVersions::V14)]
#[test_case(SupportedMetadataVersions::V15)]
fn compact_event_field(metadata_version: SupportedMetadataVersions) {
#[derive(Clone, Debug, PartialEq, Encode, Decode, TypeInfo)]
enum Event {
A(#[codec(compact)] u32),
}
let metadata = metadata_with_version::<Event>(metadata_version);
let events =
events::<Event>(metadata.clone(), vec![event_record(Phase::Finalization, Event::A(1))]);
let mut event_details = events.iter();
assert_raw_events_match(
event_details.next().unwrap().unwrap(),
TestRawEventDetails {
index: 0,
phase: Phase::Finalization,
pallet: "Test".to_string(),
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::u128(1u128)],
},
);
assert!(event_details.next().is_none());
}
#[test_case(SupportedMetadataVersions::V14)]
#[test_case(SupportedMetadataVersions::V15)]
fn compact_wrapper_struct_field(metadata_version: SupportedMetadataVersions) {
#[derive(Clone, Decode, Debug, PartialEq, Encode, TypeInfo)]
enum Event {
A(#[codec(compact)] CompactWrapper),
}
#[derive(Clone, Decode, Debug, PartialEq, codec::CompactAs, Encode, TypeInfo)]
struct CompactWrapper(u64);
let metadata = metadata_with_version::<Event>(metadata_version);
let events = events::<Event>(
metadata.clone(),
vec![event_record(Phase::Finalization, Event::A(CompactWrapper(1)))],
);
let mut event_details = events.iter();
assert_raw_events_match(
event_details.next().unwrap().unwrap(),
TestRawEventDetails {
index: 0,
phase: Phase::Finalization,
pallet: "Test".to_string(),
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::unnamed_composite(vec![Value::u128(1)])],
},
);
assert!(event_details.next().is_none());
}
#[test_case(SupportedMetadataVersions::V14)]
#[test_case(SupportedMetadataVersions::V15)]
fn event_containing_explicit_index(metadata_version: SupportedMetadataVersions) {
#[derive(Clone, Debug, PartialEq, Eq, Decode, Encode, TypeInfo)]
#[repr(u8)]
#[allow(trivial_numeric_casts, clippy::unnecessary_cast)] pub enum MyType {
B = 10u8,
}
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
enum Event {
A(MyType),
}
let metadata = metadata_with_version::<Event>(metadata_version);
let events = events::<Event>(
metadata.clone(),
vec![event_record(Phase::Finalization, Event::A(MyType::B))],
);
let mut event_details = events.iter();
assert_raw_events_match(
event_details.next().unwrap().unwrap(),
TestRawEventDetails {
index: 0,
phase: Phase::Finalization,
pallet: "Test".to_string(),
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::unnamed_variant("B", vec![])],
},
);
assert!(event_details.next().is_none());
}
}