Skip to content

Commit

Permalink
Fixed generated ABI code and decoding when dealing with indexed dynam…
Browse files Browse the repository at this point in the history
…ic event like `event ContractDeployed(string indexed value)`

We introduced `substreams_ethereum::IndexedDynamicValue<T>` to hold the hash value which is equivalent to topic.

Fixes #24
  • Loading branch information
maoueh committed Dec 19, 2023
1 parent 62a71ed commit e2f8a4c
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 9 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.9.9](https://github.com/streamingfast/substreams-ethereum/releases/tag/v0.9.9)

* Fixed generated ABI code and decoding when dealing with indexed dynamic event like `event ContractDeployed(string indexed value)`. We introduced `substreams_ethereum::IndexedDynamicValue<T>` to hold the hash value which is equivalent to topic.

## [0.9.8](https://github.com/streamingfast/substreams-ethereum/releases/tag/v0.9.8)
* Fix bug where Int was not encoded properly in ABI generator

Expand All @@ -13,7 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [0.9.6](https://github.com/streamingfast/substreams-ethereum/releases/tag/v0.9.6)
* Update `block_view` with updated ethereum block at `https://github.com/streamingfast/firehose-ethereum/releases/download/v2.0.0/ethereum-v1.1.0.spkg` with added field `DetailLevel`
> **_IMPORTANT:_**: Using blocks with `DetailLevel` set to `Extended` (the default level), `block.transactions()` returns only successful transactions.
> Blocks with `DetailLevel` set to `Base` does not have information about transaction successfulness. Note that a failed transaction will never have logs attached to it.
> Blocks with `DetailLevel` set to `Base` does not have information about transaction successfulness. Note that a failed transaction will never have logs attached to it.
## [0.9.5](https://github.com/streamingfast/substreams-ethereum/releases/tag/v0.9.5)

Expand Down
13 changes: 13 additions & 0 deletions abigen-tests/abi/tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@
],
"name": "EventInt256Idx",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "string",
"name": "param0",
"type": "string"
}
],
"name": "EventStringIdx",
"type": "event"
},
{
"anonymous": false,
Expand Down
91 changes: 87 additions & 4 deletions abigen-tests/src/abi/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2446,6 +2446,88 @@
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct EventStringIdx {
pub param0: substreams_ethereum::IndexedDynamicValue<String>,
}
impl EventStringIdx {
const TOPIC_ID: [u8; 32] = [
182u8,
232u8,
97u8,
99u8,
105u8,
96u8,
60u8,
20u8,
18u8,
111u8,
47u8,
131u8,
13u8,
66u8,
43u8,
85u8,
145u8,
12u8,
113u8,
210u8,
189u8,
165u8,
20u8,
93u8,
177u8,
69u8,
227u8,
61u8,
184u8,
203u8,
81u8,
221u8,
];
pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool {
if log.topics.len() != 2usize {
return false;
}
if log.data.len() != 0usize {
return false;
}
return log.topics.get(0).expect("bounds already checked").as_ref()
== Self::TOPIC_ID;
}
pub fn decode(
log: &substreams_ethereum::pb::eth::v2::Log,
) -> Result<Self, String> {
Ok(Self {
param0: ethabi::decode(
&[ethabi::ParamType::FixedBytes(32)],
log.topics[1usize].as_ref(),
)
.map_err(|e| {
format!(
"unable to decode param 'param0' from topic of type 'string': {:?}",
e
)
})?
.pop()
.expect(INTERNAL_ERR)
.into_fixed_bytes()
.expect(INTERNAL_ERR)
.into(),
})
}
}
impl substreams_ethereum::Event for EventStringIdx {
const NAME: &'static str = "EventStringIdx";
fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool {
Self::match_log(log)
}
fn decode(
log: &substreams_ethereum::pb::eth::v2::Log,
) -> Result<Self, String> {
Self::decode(log)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct EventUArrayBool {
pub param0: Vec<bool>,
}
Expand Down Expand Up @@ -3185,7 +3267,7 @@
}
#[derive(Debug, Clone, PartialEq)]
pub struct EventWithOverloads2 {
pub second: String,
pub second: substreams_ethereum::IndexedDynamicValue<String>,
}
impl EventWithOverloads2 {
const TOPIC_ID: [u8; 32] = [
Expand Down Expand Up @@ -3237,7 +3319,7 @@
) -> Result<Self, String> {
Ok(Self {
second: ethabi::decode(
&[ethabi::ParamType::String],
&[ethabi::ParamType::FixedBytes(32)],
log.topics[1usize].as_ref(),
)
.map_err(|e| {
Expand All @@ -3248,8 +3330,9 @@
})?
.pop()
.expect(INTERNAL_ERR)
.into_string()
.expect(INTERNAL_ERR),
.into_fixed_bytes()
.expect(INTERNAL_ERR)
.into(),
})
}
}
Expand Down
28 changes: 27 additions & 1 deletion abigen-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ mod tests {
use pretty_assertions::assert_eq;
use substreams::scalar::BigInt;
use substreams::{hex, Hex};
use substreams_ethereum::pb;
use substreams_ethereum::{pb, IndexedDynamicValue};

#[macro_export]
macro_rules! assert_bytes {
Expand Down Expand Up @@ -91,6 +91,32 @@ mod tests {
);
}

#[test]
fn it_decode_event_string_idx() {
use tests::events::EventStringIdx as Event;

let log = pb::eth::v2::Log {
address: hex!("0000000000000000000000000000000000000000").to_vec(),
topics: vec![
hex!("b6e8616369603c14126f2f830d422b55910c71d2bda5145db145e33db8cb51dd").to_vec(),
hex!("fffffffffffffffffffffffffffffffffffffffffffffffffffff713f526b11d").to_vec(),
],
..Default::default()
};

assert_eq!(Event::match_log(&log), true);

let event = Event::decode(&log);
assert_eq!(
event,
Ok(Event {
param0: IndexedDynamicValue::<String>::new(
hex!("fffffffffffffffffffffffffffffffffffffffffffffffffffff713f526b11d").to_vec(),
),
}),
);
}

#[test]
fn it_decode_event_array_bool() {
use tests::events::EventUArrayBool as Event;
Expand Down
7 changes: 5 additions & 2 deletions abigen/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use heck::{ToSnakeCase, ToUpperCamelCase};
use proc_macro2::{Span, TokenStream};
use quote::quote;

use crate::{decode_topic, fixed_data_size, min_data_size};
use crate::{decode_topic, fixed_data_size, min_data_size, rust_type_indexed};

use super::{from_token, rust_type, to_syntax_string};

Expand Down Expand Up @@ -61,7 +61,10 @@ impl<'a> From<(&'a String, &'a ethabi::Event)> for Event {
let kinds: Vec<_> = e
.inputs
.iter()
.map(|param| rust_type(&param.kind))
.map(|param| match param.indexed {
true => rust_type_indexed(&param.kind),
false => rust_type(&param.kind),
})
.collect();

let log_fields = names
Expand Down
25 changes: 24 additions & 1 deletion abigen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ fn to_syntax_string(param_type: &ethabi::ParamType) -> proc_macro2::TokenStream
// quote! { vec![ #(#p),* ] }
// }

fn rust_type_indexed(input: &ParamType) -> proc_macro2::TokenStream {
match input.is_dynamic() {
true => {
let t = rust_type(input);
return quote! { substreams_ethereum::IndexedDynamicValue<#t> };
}
false => rust_type(input),
}
}

fn rust_type(input: &ParamType) -> proc_macro2::TokenStream {
match *input {
ParamType::Address => quote! { Vec<u8> },
Expand Down Expand Up @@ -399,7 +409,6 @@ fn decode_topic(
kind: &ParamType,
data_token: &proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let syntax_type = to_syntax_string(kind);
let error_msg = format!(
"unable to decode param '{}' from topic of type '{}': {{:?}}",
name, kind
Expand All @@ -411,7 +420,21 @@ fn decode_topic(
substreams::scalar::BigInt::from_signed_bytes_be(#data_token)
}
}
_ if kind.is_dynamic() => {
let syntax_type = quote! { ethabi::ParamType::FixedBytes(32) };

quote! {
ethabi::decode(&[#syntax_type], #data_token)
.map_err(|e| format!(#error_msg, e))?
.pop()
.expect(INTERNAL_ERR)
.into_fixed_bytes()
.expect(INTERNAL_ERR)
.into()
}
}
_ => {
let syntax_type = to_syntax_string(kind);
let decode_topic = quote! {
ethabi::decode(&[#syntax_type], #data_token)
.map_err(|e| format!(#error_msg, e))?
Expand Down
44 changes: 44 additions & 0 deletions core/src/event.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::marker::PhantomData;

use crate::pb::eth::v2::Log;

pub trait Event: Sized {
Expand Down Expand Up @@ -35,3 +37,45 @@ impl AsRef<Log> for Log {
self
}
}

/// Ethereum events with indexed parameters that are of dynamic types like a 'string',
/// 'bytes' or array of value do not contain the actual value in the log. Instead, they
/// contain a hash of the value. This struct is used to represent such values in the
/// decoded event.
///
/// The hash value read can be retrieved from the `hash` field, the original value
/// cannot be retrieved (unless you know it already, in which case you can validate
/// it fits the current hash).
///
/// You can access the hash (also equivalent to the topic in this case) directly
/// on the struct:
///
/// ```ignore
/// # use substreams_ethereum::IndexedDynamicValue;
/// let value = IndexedDynamicValue::<String>::new("0x1234".into());
/// assert_eq!(value.hash, "0x1234".into());
/// ```
#[derive(Debug, Clone, PartialEq)]
pub struct IndexedDynamicValue<T> {
phantom: PhantomData<T>,

/// The hash of the value that was indexed, **not** the real value
/// that was actually indexed. The original real value cannot be
/// retrieved.
pub hash: Vec<u8>,
}

impl<T> IndexedDynamicValue<T> {
pub fn new(topic: Vec<u8>) -> Self {
Self {
phantom: PhantomData,
hash: topic,
}
}
}

impl<T> From<Vec<u8>> for IndexedDynamicValue<T> {
fn from(topic: Vec<u8>) -> Self {
Self::new(topic)
}
}
3 changes: 3 additions & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ pub use event::Event;
pub use function::Function;
pub mod scalar;

/// Dependencies needed by 'substreams-abigen' to generate bindings.
pub use event::IndexedDynamicValue;

mod event;
mod externs;
mod function;
Expand Down
4 changes: 4 additions & 0 deletions substreams-ethereum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ pub use substreams_ethereum_core::scalar;
pub use substreams_ethereum_core::{block_view, pb, rpc, Event, Function, NULL_ADDRESS};
pub use substreams_ethereum_derive::EthabiContract;

// Those are dependencies that needs to be exported for `substreams-abigen` to work. Must not
// be removed.
pub use substreams_ethereum_core::IndexedDynamicValue;

#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
pub use getrandom;

Expand Down

0 comments on commit e2f8a4c

Please sign in to comment.