Skip to content

Commit

Permalink
Merge pull request #17 from danhper/events
Browse files Browse the repository at this point in the history
Events
  • Loading branch information
danhper authored Aug 9, 2024
2 parents 35a73d5 + 301450c commit c299275
Show file tree
Hide file tree
Showing 9 changed files with 289 additions and 19 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* Add `array.filter` function
* Add support for bitwise operators
* [EXPERIMENTAL] Add support for anonymous functions
* [EXPERIMENTAL] Add support for fetching events

### Bug fixes

Expand Down
6 changes: 6 additions & 0 deletions docs/src/builtin_methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ The first element of the tuple is the function signature, and the second element
("transfer(address,uint256)", (0x789f8F7B547183Ab8E99A5e0E6D567E90e0EB03B, 100000000000000000000))
```

## `Event` static methods

### `Event.selector -> bytes32`

Returns the selector (aka topic0) of the given event

## `num` (`uint*` and `int*`) static methods

### `type(num).max -> num`
Expand Down
7 changes: 7 additions & 0 deletions docs/src/builtin_values.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,10 @@ Returns the current block base fee.
### `block.chainid -> uint256`

Returns the current chain ID.

## `events` functions

### `events.fetch{options}(address target) -> Log[] | events.fetch{options}(address[] targets) -> Log[]`

Fetches the events emitted by the contract(s) at the given address(es).
For more information, see [events](./interacting_with_contracts.md#events).
37 changes: 36 additions & 1 deletion docs/src/interacting_with_contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,39 @@ TransactionReceipt { tx_hash: 0x248ad948d1e4eefc6ccb271cac2001ebbdb2346beddc7656
[Log { address: 0x6B175474E89094C44Da98b954EedeAC495271d0F, topics: [0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925, 0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266, 0x00000000000000000000000083f20f44975d03b1b09e64809b757c47f942beea], data: 0x0000000000000000000000000000000000000000000000000de0b6b3a7640000 }]
```

Note: Event decoding is not implemented yet
If the ABI of the contract emitting the log is loaded, the logs will automatically be decoded and the decoded arguments will be available in the `args` property of each log.

## Events

Eclair provides a way to fetch events emitted by a contract using the `events.fetch` method.

```javascript
>> events.fetch{fromBlock: 20490506, toBlock: 20490512}(0xe07F9D810a48ab5c3c914BA3cA53AF14E4491e8A)[0]
Log { address: 0xe07F9D810a48ab5c3c914BA3cA53AF14E4491e8A, topics: [0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000ba12222222228d8ba445958a75a0704d566bf2c8, 0x000000000000000000000000f081470f5c6fbccf48cc4e5b82dd926409dcdd67], data: 0x00000000000000000000000000000000000000000000000e8bd6d724bc4c7886, args: Transfer { from: 0xBA12222222228d8Ba445958a75a0704d566BF2C8, to: 0xf081470f5C6FBCCF48cC4e5B82Dd926409DcdD67, value: 268330894800999708806 } }
```

The `events.fetch` accepts either a single address or a list of addresses as the first argument, as well as some options
to filter the logs returned.
It returns a list of logs that match the given criteria, and automatically decodes each log if the ABI is loaded.

### Options

The `events.fetch` method accepts the following options:

* `fromBlock`: the block number to start fetching events from
* `toBlock`: the block number to stop fetching events at
* `topic0`: topic0 of the event
* `topic1`: topic1 of the event
* `topic2`: topic2 of the event
* `topic3`: topic3 of the event

By default, it will try to fetch from the first ever block to the latest block.
In many cases, the RPC provider will reject the request because too much data would be returned, in which case
options above will need to be added to restrict the size of the response.

To only get one type of event, e.g. `Transfer`, you can filter using `topic0` and the selector of the desired event.

```javascript
>> events.fetch{fromBlock: 20490506, toBlock: 20490512, topic0: ERC20.Approval.selector}(0xe07F9D810a48ab5c3c914BA3cA53AF14E4491e8A)[0]
Log { address: 0xe07F9D810a48ab5c3c914BA3cA53AF14E4491e8A, topics: [0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925, 0x0000000000000000000000008149dc18d39fdba137e43c871e7801e7cf566d41, 0x000000000000000000000000ea50f402653c41cadbafd1f788341db7b7f37816], data: 0x000000000000000000000000000000000000000000000025f273933db5700000, args: Approval { owner: 0x8149DC18D39FDBa137E43C871e7801E7CF566D41, spender: 0xeA50f402653c41cAdbaFD1f788341dB7B7F37816, value: 700000000000000000000 } }
```
22 changes: 22 additions & 0 deletions src/interpreter/builtins/event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use std::sync::Arc;

use crate::interpreter::{
functions::{FunctionDef, SyncProperty},
Env, Type, Value,
};
use anyhow::{bail, Result};
use lazy_static::lazy_static;

pub fn event_selector(_env: &Env, receiver: &Value) -> Result<Value> {
let event_abi = match receiver {
Value::TypeObject(Type::Event(event)) => event,
_ => bail!("selector function expects receiver to be an event"),
};

Ok(event_abi.selector().into())
}

lazy_static! {
pub static ref EVENT_SELECTOR: Arc<dyn FunctionDef> =
SyncProperty::arc("selector", event_selector);
}
136 changes: 136 additions & 0 deletions src/interpreter/builtins/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use std::sync::Arc;

use alloy::{primitives::B256, rpc::types::Filter};
use anyhow::{bail, Result};
use futures::{future::BoxFuture, FutureExt};
use lazy_static::lazy_static;

use crate::interpreter::{functions::FunctionDef, types::LOG_TYPE, utils, Env, Type, Value};

#[derive(Debug)]
struct EventOptions {
topic0: Option<B256>,
topic1: Option<B256>,
topic2: Option<B256>,
topic3: Option<B256>,
from_block: Option<u64>,
to_block: Option<u64>,
}

impl TryFrom<&crate::interpreter::types::HashableIndexMap<String, Value>> for EventOptions {
type Error = anyhow::Error;

fn try_from(map: &crate::interpreter::types::HashableIndexMap<String, Value>) -> Result<Self> {
let topic0 = map.0.get("topic0").map(|v| v.as_b256()).transpose()?;
let topic1 = map.0.get("topic1").map(|v| v.as_b256()).transpose()?;
let topic2 = map.0.get("topic2").map(|v| v.as_b256()).transpose()?;
let topic3 = map.0.get("topic3").map(|v| v.as_b256()).transpose()?;
let from_block = map.0.get("fromBlock").map(|v| v.as_u64()).transpose()?;
let to_block = map.0.get("toBlock").map(|v| v.as_u64()).transpose()?;

Ok(EventOptions {
topic0,
topic1,
topic2,
topic3,
from_block,
to_block,
})
}
}

fn fetch_events<'a>(
env: &'a mut Env,
args: &'a [Value],
options: EventOptions,
) -> BoxFuture<'a, Result<Value>> {
async move {
let mut filter = Filter::new();
if let Some(topic0) = options.topic0 {
filter = filter.event_signature(topic0);
}
if let Some(topic1) = options.topic1 {
filter = filter.topic1(topic1);
}
if let Some(topic2) = options.topic2 {
filter = filter.topic2(topic2);
}
if let Some(topic3) = options.topic3 {
filter = filter.topic3(topic3);
}
if let Some(from_block) = options.from_block {
filter = filter.from_block(from_block);
} else {
filter = filter.from_block(0);
}
if let Some(to_block) = options.to_block {
filter = filter.to_block(to_block);
}

match args {
[Value::Addr(addr)] => filter = filter.address(*addr),
[Value::Array(addrs, ty_)] if ty_.as_ref() == &Type::Address => {
let addresses = addrs
.iter()
.map(|a| a.as_address())
.collect::<Result<Vec<_>>>()?;
filter = filter.address(addresses)
}
_ => bail!("events.fetch: invalid arguments"),
}

let logs = env.get_provider().get_logs(&filter).await?;
let parsed_logs = logs
.into_iter()
.map(|log| utils::log_to_value(env, log))
.collect::<Result<Vec<Value>>>()?;
Ok(Value::Array(parsed_logs, Box::new(LOG_TYPE.clone())))
}
.boxed()
}

#[derive(Debug)]
struct FetchEvents;

impl FunctionDef for FetchEvents {
fn name(&self) -> String {
"fetch".to_string()
}

fn get_valid_args(
&self,
_receiver: &Option<Value>,
) -> Vec<Vec<crate::interpreter::functions::FunctionParam>> {
vec![
vec![crate::interpreter::functions::FunctionParam::new(
"address",
Type::Address,
)],
vec![crate::interpreter::functions::FunctionParam::new(
"addresses",
Type::Array(Box::new(Type::Address)),
)],
]
}

fn is_property(&self) -> bool {
false
}

fn execute<'a>(
&'a self,
env: &'a mut Env,
values: &'a [Value],
options: &'a crate::interpreter::types::HashableIndexMap<String, Value>,
) -> BoxFuture<'a, Result<Value>> {
async move {
let parsed_opts = options.try_into()?;
fetch_events(env, &values[1..], parsed_opts).await
}
.boxed()
}
}

lazy_static! {
pub static ref FETCH_EVENTS: Arc<dyn FunctionDef> = Arc::new(FetchEvents);
}
11 changes: 11 additions & 0 deletions src/interpreter/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ mod address;
mod block;
mod concat;
mod console;
mod event;
mod events;
mod format;
mod iterable;
mod misc;
Expand All @@ -29,6 +31,7 @@ lazy_static! {
m.insert("repl".to_string(), Value::TypeObject(Type::Repl));
m.insert("console".to_string(), Value::TypeObject(Type::Console));
m.insert("block".to_string(), Value::TypeObject(Type::Block));
m.insert("events".to_string(), Value::TypeObject(Type::Events));
m.insert(
"Transaction".to_string(),
Value::TypeObject(Type::Transaction),
Expand Down Expand Up @@ -132,6 +135,14 @@ lazy_static! {
console_methods.insert("log".to_string(), console::CONSOLE_LOG.clone());
m.insert(NonParametricType::Console, console_methods);

let mut event_methods = HashMap::new();
event_methods.insert("selector".to_string(), event::EVENT_SELECTOR.clone());
m.insert(NonParametricType::Event, event_methods);

let mut events_methods = HashMap::new();
events_methods.insert("fetch".to_string(), events::FETCH_EVENTS.clone());
m.insert(NonParametricType::Events, events_methods);

let mut repl_methods = HashMap::new();
repl_methods.insert("vars".to_string(), repl::REPL_LIST_VARS.clone());
repl_methods.insert("types".to_string(), repl::REPL_LIST_TYPES.clone());
Expand Down
Loading

0 comments on commit c299275

Please sign in to comment.