diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 80f445e..84b63f4 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -58,6 +58,9 @@ impl ResultHandler { ExpressionResult::Log(query_res) => { println!("{}", to_table(query_res)?); } + ExpressionResult::Sum(query_res) => { + println!("{}", to_table(query_res)?); + } } } diff --git a/crates/cli/src/repl.rs b/crates/cli/src/repl.rs index e314310..26406d3 100644 --- a/crates/cli/src/repl.rs +++ b/crates/cli/src/repl.rs @@ -241,6 +241,12 @@ impl Repl { queue!(stdout(), MoveToNextLine(1), Print(line.cyan())).unwrap(); }); } + ExpressionResult::Sum(query_res) => { + let table = to_table(query_res)?; + table.to_string().split("\n").for_each(|line| { + queue!(stdout(), MoveToNextLine(1), Print(line.green())).unwrap(); + }); + } } } diff --git a/crates/core/src/common/logs.rs b/crates/core/src/common/logs.rs index 019b115..9af0fc9 100644 --- a/crates/core/src/common/logs.rs +++ b/crates/core/src/common/logs.rs @@ -142,7 +142,8 @@ impl TryFrom> for LogFilter { match pair.as_rule() { Rule::address_filter_type => extract_value(pair, |s| { Ok(LogFilter::EmitterAddress(Address::parse_checksummed( - Address::to_checksum(&Address::from_str(s)?, None), None, + Address::to_checksum(&Address::from_str(s)?, None), + None, )?)) }), Rule::blockrange_filter => parse_block_range(pair), diff --git a/crates/core/src/common/query_result.rs b/crates/core/src/common/query_result.rs index 73d6f06..e35bbb4 100644 --- a/crates/core/src/common/query_result.rs +++ b/crates/core/src/common/query_result.rs @@ -25,6 +25,20 @@ pub enum ExpressionResult { Transaction(Vec), #[serde(rename = "log")] Log(Vec), + #[serde(rename = "sum")] + Sum(Vec), +} + +#[serde_with::skip_serializing_none] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] +pub struct SumQueryRes { + pub sum: Option, +} + +impl Default for SumQueryRes { + fn default() -> Self { + Self { sum: None } + } } // TODO: should this be replaced with Alloy's Block? diff --git a/crates/core/src/common/serializer.rs b/crates/core/src/common/serializer.rs index 92b135c..dbf066f 100644 --- a/crates/core/src/common/serializer.rs +++ b/crates/core/src/common/serializer.rs @@ -25,6 +25,7 @@ pub(crate) fn dump_results(result: &ExpressionResult, dump: &Dump) -> Result<(), ExpressionResult::Block(blocks) => serialize_csv(blocks)?, ExpressionResult::Transaction(txs) => serialize_csv(txs)?, ExpressionResult::Log(logs) => serialize_csv(logs)?, + ExpressionResult::Sum(sums) => serialize_csv(sums)?, }; std::fs::write(dump.path(), content)?; @@ -59,6 +60,7 @@ fn serialize_parquet(result: &ExpressionResult) -> Result, Box create_parquet_schema_and_data(logs)?, + ExpressionResult::Sum(sums) => create_parquet_schema_and_data(sums)?, }; let batch = RecordBatch::try_new(Arc::new(schema), data)?; diff --git a/crates/core/src/common/types.rs b/crates/core/src/common/types.rs index 2ab0980..03b8ea4 100644 --- a/crates/core/src/common/types.rs +++ b/crates/core/src/common/types.rs @@ -10,6 +10,18 @@ use pest::iterators::Pairs; #[derive(Debug, PartialEq)] pub enum Expression { Get(GetExpression), + Sum(SumExpression), +} + +#[derive(Debug, PartialEq)] +pub struct SumExpression { + pub query: GetExpression, +} + +impl SumExpression { + fn new(query: GetExpression) -> Self { + Self { query } + } } #[derive(Debug, PartialEq)] diff --git a/crates/core/src/interpreter/backend/execution_engine.rs b/crates/core/src/interpreter/backend/execution_engine.rs index b699e9f..94a7ae0 100644 --- a/crates/core/src/interpreter/backend/execution_engine.rs +++ b/crates/core/src/interpreter/backend/execution_engine.rs @@ -1,11 +1,13 @@ use super::{ - resolve_account::resolve_account_query, - resolve_block::resolve_block_query, - resolve_logs::resolve_log_query, + resolve_account::resolve_account_query, resolve_block::resolve_block_query, + resolve_logs::resolve_log_query, resolve_sum_query::resolve_sum_query, resolve_transaction::resolve_transaction_query, }; use crate::common::{ - entity::Entity, query_result::{ExpressionResult, QueryResult}, serializer::dump_results, types::{Expression, GetExpression} + entity::Entity, + query_result::{ExpressionResult, QueryResult}, + serializer::dump_results, + types::{Expression, GetExpression, SumExpression}, }; use anyhow::Result; @@ -24,10 +26,7 @@ impl ExecutionEngine { ExecutionEngine } - pub async fn run( - &self, - expressions: Vec, - ) -> Result> { + pub async fn run(&self, expressions: Vec) -> Result> { let mut query_results = vec![]; for expression in expressions { @@ -36,21 +35,30 @@ impl ExecutionEngine { let result = self.run_get_expr(&get_expr).await?; query_results.push(QueryResult::new(result)); } + Expression::Sum(sum_expr) => { + let result = self.run_sum_expr(&sum_expr).await?; + query_results.push(QueryResult::new(result)); + } } } Ok(query_results) } - async fn run_get_expr( - &self, - expr: &GetExpression, - ) -> Result { + async fn run_get_expr(&self, expr: &GetExpression) -> Result { let result = match &expr.entity { - Entity::Block(block) => ExpressionResult::Block(resolve_block_query(block, &expr.chains).await?), - Entity::Account(account) => ExpressionResult::Account(resolve_account_query(account, &expr.chains).await?), - Entity::Transaction(transaction) => ExpressionResult::Transaction(resolve_transaction_query(transaction, &expr.chains).await?), - Entity::Logs(logs) => ExpressionResult::Log(resolve_log_query(logs, &expr.chains).await?), + Entity::Block(block) => { + ExpressionResult::Block(resolve_block_query(block, &expr.chains).await?) + } + Entity::Account(account) => { + ExpressionResult::Account(resolve_account_query(account, &expr.chains).await?) + } + Entity::Transaction(transaction) => ExpressionResult::Transaction( + resolve_transaction_query(transaction, &expr.chains).await?, + ), + Entity::Logs(logs) => { + ExpressionResult::Log(resolve_log_query(logs, &expr.chains).await?) + } }; if let Some(dump) = &expr.dump { @@ -59,20 +67,28 @@ impl ExecutionEngine { Ok(result) } + + async fn run_sum_expr(&self, expr: &SumExpression) -> Result { + let query_result = self.run_get_expr(&expr.query).await?; + let result = resolve_sum_query(&query_result)?; + Ok(result) + } } #[cfg(test)] mod test { use super::*; use crate::common::{ - account::{Account, AccountField}, + account::{Account, AccountField, AccountFilter}, block::{Block, BlockField, BlockId, BlockRange}, chain::{Chain, ChainOrRpc}, dump::{Dump, DumpFormat}, ens::NameOrAddress, logs::{LogField, LogFilter, Logs}, - query_result::{AccountQueryRes, BlockQueryRes, LogQueryRes, TransactionQueryRes}, - transaction::{Transaction, TransactionField}, + query_result::{ + AccountQueryRes, BlockQueryRes, LogQueryRes, SumQueryRes, TransactionQueryRes, + }, + transaction::{Transaction, TransactionField, TransactionFilter}, types::{Expression, GetExpression}, }; use alloy::{ @@ -82,6 +98,74 @@ mod test { use pretty_assertions::assert_eq; use std::str::FromStr; + #[tokio::test] + async fn test_sum_balance_of_mutiple_chain() { + let execution_engine = ExecutionEngine::new(); + let expressions = vec![Expression::Sum(SumExpression { + query: GetExpression { + entity: Entity::Account(Account::new( + Some(vec![NameOrAddress::Name(String::from("vitalik.eth"))]), + None, + vec![AccountField::Balance], + )), + chains: vec![ + ChainOrRpc::Chain(Chain::Ethereum), + ChainOrRpc::Chain(Chain::Base), + ChainOrRpc::Chain(Chain::Arbitrum), + ], + dump: None, + }, + })]; + + let execution_result = execution_engine.run(expressions).await; + let expected = vec![SumQueryRes { + sum: Some(U256::from(0)), + }]; + + match execution_result { + Ok(results) => { + assert_eq!(results[0].result, ExpressionResult::Sum(expected)); + } + Err(_) => panic!("Error"), + } + } + + #[tokio::test] + async fn test_sum_value_of_mutile_block_for_mutiple_chain() { + let execution_engine = ExecutionEngine::new(); + let expressions = vec![Expression::Sum(SumExpression { + query: GetExpression { + entity: Entity::Transaction(Transaction::new( + Some(vec![ + b256!("8cfc4f5f4729423f59dd1d263ead2f824b3f133b02b9e27383964c7d50cd47cb"), + b256!("72546b3ca8ef0dfb85fe66d19645e44cb519858c72fbcad0e1c1699256fed890"), + ]), + None, + vec![TransactionField::Value], + )), + chains: vec![ + ChainOrRpc::Chain(Chain::Ethereum), + ChainOrRpc::Chain(Chain::Base), + ChainOrRpc::Chain(Chain::Arbitrum), + ], + dump: None, + }, + })]; + println!("{:?}", expressions); + + let execution_result = execution_engine.run(expressions).await; + let expected = vec![SumQueryRes { + sum: Some(U256::from(0)), + }]; + + match execution_result { + Ok(results) => { + assert_eq!(results[0].result, ExpressionResult::Sum(expected)); + } + Err(_) => panic!("Error"), + } + } + #[tokio::test] async fn test_get_logs() { let execution_engine = ExecutionEngine::new(); @@ -92,9 +176,7 @@ mod test { BlockNumberOrTag::Number(4638757), Some(BlockNumberOrTag::Number(4638758)), )), - LogFilter::EmitterAddress(address!( - "dac17f958d2ee523a2206206994597c13d831ec7" - )), + LogFilter::EmitterAddress(address!("dac17f958d2ee523a2206206994597c13d831ec7")), LogFilter::Topic0(b256!( "cb8241adb0c3fdb35b70c24ce35c5eb0c17af7431c99f827d44a445ca624176a" )), @@ -143,18 +225,14 @@ mod test { async fn test_get_block_fields() { let execution_engine = ExecutionEngine::new(); let expressions = vec![Expression::Get(GetExpression { - entity: Entity::Block( - Block::new( - Some(vec![ - BlockId::Range(BlockRange::new( - BlockNumberOrTag::Number(1), - None, - )), - ]), + entity: Entity::Block(Block::new( + Some(vec![BlockId::Range(BlockRange::new( + BlockNumberOrTag::Number(1), None, - BlockField::all_variants().to_vec(), - ) - ), + ))]), + None, + BlockField::all_variants().to_vec(), + )), dump: None, chains: vec![ChainOrRpc::Chain(Chain::Ethereum)], })]; @@ -205,13 +283,13 @@ mod test { async fn test_get_account_fields_using_invalid_ens() { let execution_engine = ExecutionEngine::new(); let expressions = vec![Expression::Get(GetExpression { - entity: Entity::Account( - Account::new( - Some(vec![NameOrAddress::Name(String::from("thisisinvalid235790123801.eth"))]), - None, - vec![AccountField::Balance], - ) - ), + entity: Entity::Account(Account::new( + Some(vec![NameOrAddress::Name(String::from( + "thisisinvalid235790123801.eth", + ))]), + None, + vec![AccountField::Balance], + )), chains: vec![ChainOrRpc::Chain(Chain::Ethereum)], dump: None, })]; @@ -223,16 +301,14 @@ mod test { async fn test_get_transaction_fields() { let execution_engine = ExecutionEngine::new(); let expressions = vec![Expression::Get(GetExpression { - entity: Entity::Transaction( - Transaction::new( - Some(vec![ - b256!("72546b3ca8ef0dfb85fe66d19645e44cb519858c72fbcad0e1c1699256fed890"), - b256!("72546b3ca8ef0dfb85fe66d19645e44cb519858c72fbcad0e1c1699256fed890") - ]), - None, - TransactionField::all_variants().to_vec(), - ) - ), + entity: Entity::Transaction(Transaction::new( + Some(vec![ + b256!("72546b3ca8ef0dfb85fe66d19645e44cb519858c72fbcad0e1c1699256fed890"), + b256!("72546b3ca8ef0dfb85fe66d19645e44cb519858c72fbcad0e1c1699256fed890"), + ]), + None, + TransactionField::all_variants().to_vec(), + )), chains: vec![ChainOrRpc::Chain(Chain::Ethereum)], dump: None, })]; @@ -285,7 +361,7 @@ mod test { chain: Some(Chain::Ethereum), authorization_list: None, }]) - ]; + ]; let result = execution_engine.run(expressions).await; match result { @@ -300,15 +376,13 @@ mod test { async fn test_get_inexistent_transaction() { let execution_engine = ExecutionEngine::new(); let expressions = vec![Expression::Get(GetExpression { - entity: Entity::Transaction( - Transaction::new( - Some(vec![b256!( - "0000000000000000000000000000000000000000000000000000000000000000" - )]), - None, - TransactionField::all_variants().to_vec(), - ) - ), + entity: Entity::Transaction(Transaction::new( + Some(vec![b256!( + "0000000000000000000000000000000000000000000000000000000000000000" + )]), + None, + TransactionField::all_variants().to_vec(), + )), chains: vec![ChainOrRpc::Chain(Chain::Ethereum)], dump: None, })]; @@ -321,18 +395,11 @@ mod test { async fn test_dump_results() { let execution_engine = ExecutionEngine::new(); let expressions = vec![Expression::Get(GetExpression { - entity: Entity::Block( - Block::new( - Some(vec![ - BlockId::Range(BlockRange::new( - 1.into(), - None, - )) - ]), - None, - vec![BlockField::Timestamp], - ) - ), + entity: Entity::Block(Block::new( + Some(vec![BlockId::Range(BlockRange::new(1.into(), None))]), + None, + vec![BlockField::Timestamp], + )), chains: vec![ChainOrRpc::Chain(Chain::Ethereum)], dump: Some(Dump::new(String::from("test"), DumpFormat::Json)), })]; @@ -382,7 +449,9 @@ mod test { ( Expression::Get(GetExpression { entity: Entity::Account(Account::new( - Some(vec![NameOrAddress::Address(address!("dac17f958d2ee523a2206206994597c13d831ec7"))]), + Some(vec![NameOrAddress::Address(address!( + "dac17f958d2ee523a2206206994597c13d831ec7" + ))]), None, vec![AccountField::Chain], )), @@ -397,7 +466,9 @@ mod test { ( Expression::Get(GetExpression { entity: Entity::Transaction(Transaction::new( - Some(vec![b256!("72546b3ca8ef0dfb85fe66d19645e44cb519858c72fbcad0e1c1699256fed890")]), + Some(vec![b256!( + "72546b3ca8ef0dfb85fe66d19645e44cb519858c72fbcad0e1c1699256fed890" + )]), None, vec![TransactionField::Chain], )), diff --git a/crates/core/src/interpreter/backend/mod.rs b/crates/core/src/interpreter/backend/mod.rs index 4f02a10..40ff549 100644 --- a/crates/core/src/interpreter/backend/mod.rs +++ b/crates/core/src/interpreter/backend/mod.rs @@ -1,5 +1,6 @@ +pub mod execution_engine; mod resolve_account; mod resolve_block; mod resolve_logs; +mod resolve_sum_query; mod resolve_transaction; -pub mod execution_engine; diff --git a/crates/core/src/interpreter/backend/resolve_sum_query.rs b/crates/core/src/interpreter/backend/resolve_sum_query.rs new file mode 100644 index 0000000..2b53e5c --- /dev/null +++ b/crates/core/src/interpreter/backend/resolve_sum_query.rs @@ -0,0 +1,28 @@ +use crate::common::{ + account::{Account, AccountField}, + chain::{Chain, ChainOrRpc}, + ens::NameOrAddress, + query_result::{ExpressionResult, SumQueryRes}, +}; +use alloy::{ + primitives::Address, + providers::{Provider, ProviderBuilder, RootProvider}, + transports::http::{Client, Http}, +}; +use anyhow::Result; +use futures::future::try_join_all; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +#[derive(Debug, Serialize, Deserialize, thiserror::Error)] +pub enum AccountResolverErrors { + #[error("Mismatch between Entity and EntityId, {0} can't be resolved as a account id")] + MismatchEntityAndEntityId(String), +} + +/// Resolve the query to get accounts after receiving an account entity expression +/// Iterate through entity_ids and map them to a futures list. Execute all futures concurrently and collect the results. +pub fn resolve_sum_query(exp: &ExpressionResult) -> Result { + let res = ExpressionResult::Sum(vec![SumQueryRes::default()]); + Ok(res) +} diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 41eefed..30fa6ef 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -27,4 +27,4 @@ pub fn enum_variants_derive(input: TokenStream) -> TokenStream { }; TokenStream::from(expanded) -} \ No newline at end of file +}