diff --git a/docs/src/builtin_methods.md b/docs/src/builtin_methods.md index d7010be..a6050d4 100644 --- a/docs/src/builtin_methods.md +++ b/docs/src/builtin_methods.md @@ -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` diff --git a/docs/src/interacting_with_contracts.md b/docs/src/interacting_with_contracts.md index 13786a8..a86d340 100644 --- a/docs/src/interacting_with_contracts.md +++ b/docs/src/interacting_with_contracts.md @@ -88,3 +88,10 @@ The `events.fetch` method accepts the following options: 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 } } +``` diff --git a/src/interpreter/builtins/event.rs b/src/interpreter/builtins/event.rs new file mode 100644 index 0000000..648697a --- /dev/null +++ b/src/interpreter/builtins/event.rs @@ -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 { + 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 = + SyncProperty::arc("selector", event_selector); +} diff --git a/src/interpreter/builtins/mod.rs b/src/interpreter/builtins/mod.rs index 3714dc9..5fa4882 100644 --- a/src/interpreter/builtins/mod.rs +++ b/src/interpreter/builtins/mod.rs @@ -9,6 +9,7 @@ mod address; mod block; mod concat; mod console; +mod event; mod events; mod format; mod iterable; @@ -135,8 +136,12 @@ lazy_static! { m.insert(NonParametricType::Console, console_methods); let mut event_methods = HashMap::new(); - event_methods.insert("fetch".to_string(), events::FETCH_EVENTS.clone()); - m.insert(NonParametricType::Events, event_methods); + 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()); diff --git a/src/interpreter/types.rs b/src/interpreter/types.rs index a1074d9..41759b1 100644 --- a/src/interpreter/types.rs +++ b/src/interpreter/types.rs @@ -6,7 +6,7 @@ use alloy::{ json_abi::JsonAbi, primitives::{Address, B256, I256, U160, U256}, }; -use anyhow::{bail, Result}; +use anyhow::{anyhow, bail, Result}; use indexmap::IndexMap; use itertools::Itertools; use solang_parser::pt as parser; @@ -61,12 +61,28 @@ impl ContractInfo { let _func = self .1 .function(name) - .ok_or_else(|| anyhow::anyhow!("function {} not found in contract {}", name, self.0))?; + .ok_or_else(|| anyhow!("function {} not found in contract {}", name, self.0))?; Ok(Function::new( ContractFunction::arc(name), Some(&Value::Contract(self.clone(), addr)), )) } + + pub fn member_access(&self, name: &str) -> Result { + if let Some(event) = self.1.events.get(name).and_then(|v| v.first()) { + return Ok(Value::TypeObject(Type::Event(event.clone()))); + } + let func = STATIC_METHODS + .get(&NonParametricType::Contract) + .unwrap() + .get(name) + .ok_or(anyhow!("{} not found in contract {}", name, self.0))?; + Ok(Function::method( + func.clone(), + &Value::TypeObject(Type::Contract(self.clone())), + ) + .into()) + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -86,6 +102,7 @@ pub enum NonParametricType { Tuple, Mapping, Contract, + Event, Transaction, TransactionReceipt, Function, @@ -114,6 +131,7 @@ pub enum Type { Tuple(Vec), Mapping(Box, Box), Contract(ContractInfo), + Event(alloy::json_abi::Event), Transaction, TransactionReceipt, Function, @@ -149,6 +167,7 @@ impl Display for Type { } Type::Mapping(k, v) => write!(f, "mapping({} => {})", k, v), Type::Contract(ContractInfo(name, _)) => write!(f, "{}", name), + Type::Event(event) => write!(f, "{}", event.full_signature()), Type::Function => write!(f, "function"), Type::Transaction => write!(f, "Transaction"), @@ -182,6 +201,7 @@ impl> From for NonParametricType { Type::Tuple(_) => NonParametricType::Tuple, Type::Mapping(..) => NonParametricType::Mapping, Type::Contract(..) => NonParametricType::Contract, + Type::Event(..) => NonParametricType::Event, Type::Function => NonParametricType::Function, Type::Transaction => NonParametricType::Transaction, Type::TransactionReceipt => NonParametricType::TransactionReceipt, @@ -494,9 +514,17 @@ impl Type { abi.functions.keys().map(|s| s.to_string()).collect() } Type::NamedTuple(_, fields) => fields.0.keys().map(|s| s.to_string()).collect(), - Type::Type(type_) => STATIC_METHODS - .get(&type_.into()) - .map_or(vec![], |m| m.keys().cloned().collect()), + Type::Type(type_) => { + let mut static_methods = STATIC_METHODS + .get(&type_.into()) + .map_or(vec![], |m| m.keys().cloned().collect()); + + if let Type::Contract(ContractInfo(_, abi)) = type_.as_ref() { + static_methods.extend(abi.events.keys().map(|s| s.to_string())); + } + + static_methods + } _ => INSTANCE_METHODS .get(&self.into()) .map_or(vec![], |m| m.keys().cloned().collect()), diff --git a/src/interpreter/value.rs b/src/interpreter/value.rs index bb5268c..1be2ab6 100644 --- a/src/interpreter/value.rs +++ b/src/interpreter/value.rs @@ -521,6 +521,7 @@ impl Value { } Value::Contract(c, addr) => c.make_function(member, *addr).map(Into::into), Value::Func(f) => f.member_access(member), + Value::TypeObject(Type::Contract(c)) => c.member_access(member), _ => { let (type_, methods) = match self { Value::TypeObject(Type::Type(type_)) => {