diff --git a/Cargo.lock b/Cargo.lock index e9389cf3..1eef6946 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4065,6 +4065,7 @@ dependencies = [ "anyhow", "array-bytes 6.1.0", "async-trait", + "chrono", "clap 4.4.3", "dotenv", "ethabi", diff --git a/hyperspace/ethereum/evm-indexer/Cargo.toml b/hyperspace/ethereum/evm-indexer/Cargo.toml index 5d80f1dd..c522e043 100644 --- a/hyperspace/ethereum/evm-indexer/Cargo.toml +++ b/hyperspace/ethereum/evm-indexer/Cargo.toml @@ -4,6 +4,7 @@ version = "2.0.2" edition = "2021" [dependencies] +chrono = "0.4" anyhow = "1" array-bytes = "6.0.0" async-trait = "0.1" diff --git a/hyperspace/ethereum/evm-indexer/README.md b/hyperspace/ethereum/evm-indexer/README.md index 6472845e..0e7b80e4 100644 --- a/hyperspace/ethereum/evm-indexer/README.md +++ b/hyperspace/ethereum/evm-indexer/README.md @@ -69,7 +69,9 @@ docker build . -t indexer ``` 3. Copy the `.env.example` file to `.env` and add your environment variables. - 4. Run the image `TODO: programs and flags.` + +5. Usage + `DATABASE_URL="pg://postgres:password@localhost/postgres" REDIS_URL="redis://localhost:6379" cargo run --package evm-indexer --bin evm-indexer -- --chain=mainnet --start-block=19789739 --rpcs=https://geth-execution-0.ethereum-mainnet.sre-scratchpad-349209.composablenodes.tech:443 --contract-addresses=0xd856f0f9efa054896fe3596e05978bbe686de131 --ibc-core-address=0xd856f0f9efa054896fe3596e05978bbe686de131 --relayer-address=0x6c57e54C379b5716999ae9AAE4917c9B35AC2CB9 --fee-collector-address=0x6f11fb4d4178c5d5e3ee4bc7820d8cfed0b59ce7 --ics20-transfer-bank-address=0x148acd3cd4d6a17cd2abbecd0745b09b62c64f84 --coinmarketcap-api-key=449a42d7-b66f-4b85-b957-cc6599b8c1e6` diff --git a/hyperspace/ethereum/evm-indexer/bin/indexer.rs b/hyperspace/ethereum/evm-indexer/bin/indexer.rs index ec5085fd..760d2595 100644 --- a/hyperspace/ethereum/evm-indexer/bin/indexer.rs +++ b/hyperspace/ethereum/evm-indexer/bin/indexer.rs @@ -3,18 +3,68 @@ use evm_indexer::{ configs::indexer_config::EVMIndexerConfig, db::db::Database, indexer, rpc::rpc::Rpc, }; use log::*; +use serde::Deserialize; use simple_logger::SimpleLogger; use std::time::Duration; use tokio::time::sleep; +use std::collections::HashMap; +use reqwest::{Client, Url}; +use chrono::Utc; + + +async fn fetch_cryptocurrency_data(api_key: &str) -> Result { + //price-performance-stats is not allowed for free plan + // let mut url = Url::parse("https://pro-api.coinmarketcap.com/v2/cryptocurrency/price-performance-stats/latest").unwrap(); + //only quotes/latest is allowed for free plan + let mut url = Url::parse("https://pro-api.coinmarketcap.com/v2/cryptocurrency/quotes/latest").unwrap(); + + let mut query_params = HashMap::new(); + query_params.insert("symbol", "ETH"); + + url.set_query(Some(&query_params.iter().map(|(k, v)| format!("{}={}", k, v)).collect::>().join("&"))); + + let client = Client::new(); + let resp = client.get(url) + .header("X-CMC_PRO_API_KEY", api_key) + .header("Accepts", "application/json") + .send().await.expect("Failed to send request") + .text().await?; + + Ok(resp) +} + +async fn price_from_cmc_data(cmc_api_key : &str) -> Option { + let str_cmc_data = fetch_cryptocurrency_data(cmc_api_key).await.unwrap(); + let data: serde_json::Value = serde_json::from_str(&str_cmc_data).unwrap(); + data["data"]["ETH"][0]["quote"]["USD"]["price"].as_f64() +} + +#[derive(Debug)] +struct Cryptocurrency { + name: String, + price: f64, + last_timestamp: i64, +} + #[tokio::main()] async fn main() { dotenv().ok(); + + let log = SimpleLogger::new().with_level(LevelFilter::Info); let mut config = EVMIndexerConfig::new(); + let mut eth_price = Cryptocurrency { + name: "ETH".to_string(), + price: price_from_cmc_data(&config.coinmarketcap_api_key).await.unwrap(), + last_timestamp: Utc::now().timestamp(), + }; + + println!("{:?}", eth_price); + if config.debug { log.with_level(LevelFilter::Debug).init().unwrap(); } else { @@ -46,7 +96,13 @@ async fn main() { None => rpc.get_last_block().await.unwrap(), }; loop { - indexer::sync_chain(&rpc, &db, &mut config, &mut indexed_blocks, from_block).await; + //check that timestamp is not older than 60 mins + if eth_price.last_timestamp < Utc::now().timestamp() - 60 * 60 { + eth_price.price = price_from_cmc_data(&config.coinmarketcap_api_key).await.unwrap(); + eth_price.last_timestamp = Utc::now().timestamp(); + println!("new eth price {:?}", eth_price); + } + indexer::sync_chain(&rpc, &db, &mut config, &mut indexed_blocks, from_block, eth_price.price).await; sleep(Duration::from_millis(50)).await; } } else { diff --git a/hyperspace/ethereum/evm-indexer/migrations/20240508201309_create_metrics.sql b/hyperspace/ethereum/evm-indexer/migrations/20240508201309_create_metrics.sql new file mode 100644 index 00000000..d011fcaf --- /dev/null +++ b/hyperspace/ethereum/evm-indexer/migrations/20240508201309_create_metrics.sql @@ -0,0 +1,86 @@ +CREATE TABLE relayertransactions ( + block_hash TEXT NOT NULL, + block_number BIGINT NOT NULL, + chain TEXT NOT NULL, + from_address TEXT NOT NULL, + gas TEXT NOT NULL, + gas_price TEXT NOT NULL, + fee_usd TEXT NOT NULL, + hash TEXT NOT NULL, + input TEXT NOT NULL, + max_fee_per_gas TEXT, + max_priority_fee_per_gas TEXT, + method TEXT NOT NULL, + call_batch_methods TEXT NOT NULL, + nonce TEXT NOT NULL, + timestamp TEXT NOT NULL, + to_address TEXT NOT NULL, + transaction_index BIGINT NOT NULL, + transaction_type BIGINT, + value TEXT NOT NULL, + CONSTRAINT relayertransactions_pkey PRIMARY KEY (hash) +); + +CREATE INDEX IF NOT EXISTS relayertransactions_by_block_number ON relayertransactions (block_number DESC); + +CREATE INDEX IF NOT EXISTS relayertransactions_by_sender ON relayertransactions (from_address); --; -- STORING (to_address); + +CREATE INDEX IF NOT EXISTS relayertransactions_by_receiver ON relayertransactions (to_address); --; -- STORING (from_address); + +CREATE INDEX IF NOT EXISTS relayertransactions_by_chain ON relayertransactions (chain); + +CREATE INDEX IF NOT EXISTS relayertransactions_by_timestamp ON relayertransactions (timestamp DESC); + +CREATE SEQUENCE feecollectors_id; + +CREATE TABLE feecollectors ( + id BIGINT DEFAULT nextval('feecollectors_id') NOT NULL, + token_address TEXT NOT NULL, + transaction_hash TEXT NOT NULL, + address TEXT NOT NULL, + block_number BIGINT NOT NULL, + amount TEXT NOT NULL, + fee_usd TEXT NOT NULL, + timestamp TEXT NOT NULL, + is_withdrawn TEXT NOT NULL, + CONSTRAINT feecollectors_pkey PRIMARY KEY (id) +); + +CREATE INDEX IF NOT EXISTS feecollectors_by_timestamp ON feecollectors (timestamp DESC); + +CREATE INDEX IF NOT EXISTS feecollectors_by_address ON feecollectors (address); + +CREATE INDEX IF NOT EXISTS feecollectors_by_receiver ON feecollectors (token_address); + + +CREATE TABLE sendpacketevents ( + transaction_hash TEXT NOT NULL, + sequence BIGINT NOT NULL, + source_port TEXT NOT NULL, + source_channel TEXT NOT NULL, + source_port_indexed TEXT NOT NULL, + source_channel_indexed TEXT NOT NULL, + timeout_revision_number BIGINT NOT NULL, + timeout_revision_height BIGINT NOT NULL, + timeout_timestamp BIGINT NOT NULL, + data TEXT NOT NULL, + amount TEXT NOT NULL, + denom TEXT NOT NULL, + receiver TEXT NOT NULL, + sender TEXT NOT NULL, + memo TEXT NOT NULL, + CONSTRAINT sendpacketevents_pkey PRIMARY KEY (transaction_hash) +); + +CREATE INDEX IF NOT EXISTS sendpacketevents_by_sequence ON sendpacketevents (sequence); + +CREATE INDEX IF NOT EXISTS sendpacketevents_by_transaction_hash ON sendpacketevents (transaction_hash); + +CREATE INDEX IF NOT EXISTS sendpacketevents_by_receiver ON sendpacketevents (receiver); + +CREATE INDEX IF NOT EXISTS sendpacketevents_by_denom ON sendpacketevents (denom); + +CREATE INDEX IF NOT EXISTS sendpacketevents_by_sender ON sendpacketevents (sender); + +CREATE INDEX IF NOT EXISTS sendpacketevents_by_amount ON sendpacketevents (amount); + diff --git a/hyperspace/ethereum/evm-indexer/src/configs/indexer_config.rs b/hyperspace/ethereum/evm-indexer/src/configs/indexer_config.rs index 6de0ff6a..b3a0f7f0 100644 --- a/hyperspace/ethereum/evm-indexer/src/configs/indexer_config.rs +++ b/hyperspace/ethereum/evm-indexer/src/configs/indexer_config.rs @@ -28,6 +28,21 @@ pub struct EVMIndexerArgs { #[arg(long, help = "Comma separated list of contract addresses to index.")] pub contract_addresses: Vec
, + + #[arg(long, help = "Relayer address to track and index his transactions.")] + pub relayer_address: Address, + + #[arg(long, help = "Fee Collector Address")] + pub fee_collector_address: Address, + + #[arg(long, help = "Ics20 Transfer Bank Address")] + pub ics20_transfer_bank_address: Address, + + #[arg(long, help = "Ibc Core Address")] + pub ibc_core_address: Address, + + #[arg(long, help = "Coinmarketcap Api Key to fetch a currency price and store into database.")] + pub coinmarketcap_api_key: String, } #[derive(Debug, Clone)] @@ -43,6 +58,11 @@ pub struct EVMIndexerConfig { pub recalc_blocks_indexer: bool, pub contract_addresses: Vec
, pub block_confirmation_length: usize, + pub relayer_address: Address, + pub fee_collector_address: Address, + pub ics20_transfer_bank_address: Address, + pub ibc_core_address: Address, + pub coinmarketcap_api_key: String, } impl EVMIndexerConfig { @@ -71,6 +91,11 @@ impl EVMIndexerConfig { recalc_blocks_indexer: args.recalculate_indexed_blocks, contract_addresses: args.contract_addresses, block_confirmation_length: 14, + relayer_address: args.relayer_address, + fee_collector_address: args.fee_collector_address, + ics20_transfer_bank_address: args.ics20_transfer_bank_address, + ibc_core_address: args.ibc_core_address, + coinmarketcap_api_key: args.coinmarketcap_api_key, } } } diff --git a/hyperspace/ethereum/evm-indexer/src/db/db.rs b/hyperspace/ethereum/evm-indexer/src/db/db.rs index 84fb7455..e59c77eb 100644 --- a/hyperspace/ethereum/evm-indexer/src/db/db.rs +++ b/hyperspace/ethereum/evm-indexer/src/db/db.rs @@ -13,8 +13,7 @@ use sqlx::{ use crate::{chains::chains::Chain, db::migration::migrate}; use super::models::models::{ - DatabaseBlock, DatabaseChainIndexedState, DatabaseContract, DatabaseContractInformation, - DatabaseIBCEventData, DatabaseLog, DatabaseMethod, DatabaseReceipt, DatabaseTransaction, + DatabaseBlock, DatabaseChainIndexedState, DatabaseContract, DatabaseContractInformation, DatabaseFeeCollector, DatabaseIBCEventData, DatabaseLog, DatabaseMethod, DatabaseReceipt, DatabaseSendPacketEvent, DatabaseTransaction, DatabaseRelayerTransaction }; pub const MAX_DIESEL_PARAM_SIZE: u16 = u16::MAX; @@ -117,6 +116,9 @@ impl Database { logs: &Vec, contracts: &Vec, ibc_events: &Vec, + store_relayertransactions: &Vec, + store_feecollectors: &Vec, + store_sendpacketevents: &Vec, ) { if contracts.len() > 0 { self.store_contracts(&contracts).await.unwrap(); @@ -142,6 +144,18 @@ impl Database { self.store_ibc_events(&ibc_events).await.unwrap(); } + if store_relayertransactions.len() > 0 { + self.store_relayertransactions(&store_relayertransactions).await.unwrap(); + } + + if store_feecollectors.len() > 0 { + self.store_feecollectors(&store_feecollectors).await.unwrap(); + } + + if store_sendpacketevents.len() > 0 { + self.store_sendpacketevents(&store_sendpacketevents).await.unwrap(); + } + info!( "Inserted: blocks ({}) transactions ({}) receipts ({}) logs ({}) contracts ({}) ibc events ({}) for chain {}", blocks.len(), @@ -235,6 +249,114 @@ impl Database { Ok(()) } + async fn store_relayertransactions(&self, relayertransactions: &Vec) -> Result<()> { + let connection = self.get_connection(); + + let chunks = get_chunks(relayertransactions.len(), DatabaseRelayerTransaction::field_count()); + + for (start, end) in chunks { + let mut query_builder = QueryBuilder::new("INSERT INTO relayertransactions (block_hash, block_number, chain, from_address, gas, gas_price, fee_usd, max_priority_fee_per_gas, max_fee_per_gas, hash, input, method, call_batch_methods, nonce, timestamp, to_address, transaction_index, transaction_type, value) "); + + query_builder.push_values(&relayertransactions[start..end], |mut row, transaction| { + row.push_bind(transaction.block_hash.clone()) + .push_bind(transaction.block_number) + .push_bind(transaction.chain.clone()) + .push_bind(transaction.from_address.clone()) + .push_bind(transaction.gas.clone()) + .push_bind(transaction.gas_price.clone()) + .push_bind(transaction.fee_usd.clone()) + .push_bind(transaction.max_priority_fee_per_gas.clone()) + .push_bind(transaction.max_fee_per_gas.clone()) + .push_bind(transaction.hash.clone()) + .push_bind(transaction.input.clone()) + .push_bind(transaction.method.clone()) + .push_bind(transaction.call_batch_methods.clone()) + .push_bind(transaction.nonce.clone()) + .push_bind(transaction.timestamp.clone()) + .push_bind(transaction.to_address.clone()) + .push_bind(transaction.transaction_index) + .push_bind(transaction.transaction_type) + .push_bind(transaction.value.clone()); + }); + + let query = query_builder.build(); + + query + .execute(connection) + .await + .expect("Unable to store relayertransactions into database"); + } + + Ok(()) + } + + async fn store_feecollectors(&self, fee_collector_events: &Vec) -> Result<()> { + let connection = self.get_connection(); + + let chunks = get_chunks(fee_collector_events.len(), DatabaseFeeCollector::field_count()); + + for (start, end) in chunks { + let mut query_builder = QueryBuilder::new("INSERT INTO feecollectors (token_address, transaction_hash, address, block_number, amount, fee_usd, timestamp, is_withdrawn) "); + + query_builder.push_values(&fee_collector_events[start..end], |mut row, transaction| { + row.push_bind(transaction.token_address.clone()) + .push_bind(transaction.transaction_hash.clone()) + .push_bind(transaction.address.clone()) + .push_bind(transaction.block_number) + .push_bind(transaction.amount.clone()) + .push_bind(transaction.fee_usd.clone()) + .push_bind(transaction.timestamp.clone()) + .push_bind(transaction.is_withdrawn); + }); + + let query = query_builder.build(); + + query + .execute(connection) + .await + .expect("Unable to store feecollectors into database"); + } + + Ok(()) + } + + async fn store_sendpacketevents(&self, sendpacketevents: &Vec) -> Result<()> { + let connection = self.get_connection(); + + let chunks = get_chunks(sendpacketevents.len(), DatabaseSendPacketEvent::field_count()); + + for (start, end) in chunks { + let mut query_builder = QueryBuilder::new("INSERT INTO sendpacketevents (transaction_hash, sequence, source_port, source_channel, source_port_indexed, source_channel_indexed, timeout_revision_number, timeout_revision_height, timeout_timestamp, data, amount, denom, receiver, sender, memo) "); + + query_builder.push_values(&sendpacketevents[start..end], |mut row, transaction| { + row.push_bind(transaction.transaction_hash.clone()) + .push_bind(transaction.sequence) + .push_bind(transaction.source_port.clone()) + .push_bind(transaction.source_channel.clone()) + .push_bind(transaction.source_port_indexed.clone()) + .push_bind(transaction.source_channel_indexed.clone()) + .push_bind(transaction.timeout_revision_number) + .push_bind(transaction.timeout_revision_height) + .push_bind(transaction.timeout_timestamp) + .push_bind(transaction.data.clone()) + .push_bind(transaction.amount.clone()) + .push_bind(transaction.denom.clone()) + .push_bind(transaction.receiver.clone()) + .push_bind(transaction.sender.clone()) + .push_bind(transaction.memo.clone()); + }); + + let query = query_builder.build(); + + query + .execute(connection) + .await + .expect("Unable to store sendpacketevents into database"); + } + + Ok(()) + } + async fn store_transactions_receipts(&self, receipts: &Vec) -> Result<()> { let connection = self.get_connection(); diff --git a/hyperspace/ethereum/evm-indexer/src/db/migration.rs b/hyperspace/ethereum/evm-indexer/src/db/migration.rs index 7e82a23e..7ef4739e 100644 --- a/hyperspace/ethereum/evm-indexer/src/db/migration.rs +++ b/hyperspace/ethereum/evm-indexer/src/db/migration.rs @@ -7,10 +7,12 @@ use std::path::PathBuf; const MIGRATION_PATHS: &[&str] = &[ "../../migrations/20230227201309_create_tables.sql", "../../migrations/20240325142500_token_prices.sql", + "../../migrations/20240508201309_create_metrics.sql", ]; const MIGRATIONS: &[&str] = &[ include_str!("../../migrations/20230227201309_create_tables.sql"), include_str!("../../migrations/20240325142500_token_prices.sql"), + include_str!("../../migrations/20240508201309_create_metrics.sql"), ]; pub async fn migrate(db_conn: &Pool) -> Result<(), sqlx::Error> { @@ -20,6 +22,8 @@ pub async fn migrate(db_conn: &Pool) -> Result<(), sqlx::Error> { .map(|(migration, path)| { let path = PathBuf::from(path); let (version, name) = { + println!("path: {:?}", path); + println!("file_name: {:?}", path.file_name()); let file_name = path.file_name().expect("migration file name should be a valid file name"); let file_name = diff --git a/hyperspace/ethereum/evm-indexer/src/db/models/models.rs b/hyperspace/ethereum/evm-indexer/src/db/models/models.rs index 6ee4fb31..e32a6414 100644 --- a/hyperspace/ethereum/evm-indexer/src/db/models/models.rs +++ b/hyperspace/ethereum/evm-indexer/src/db/models/models.rs @@ -207,6 +207,56 @@ impl DatabaseTransaction { } } +#[derive(Debug, Clone, FieldCount)] +pub struct DatabaseRelayerTransaction { + pub block_hash: String, + pub block_number: i64, + pub chain: String, + pub from_address: String, + pub gas: String, + pub gas_price: String, + pub fee_usd: String, + pub max_priority_fee_per_gas: String, + pub max_fee_per_gas: String, + pub hash: String, + pub input: String, + pub method: String, + pub call_batch_methods: String, + pub nonce: String, + pub timestamp: String, + pub to_address: String, + pub transaction_index: i64, + pub transaction_type: i64, + pub value: String, +} + +impl DatabaseRelayerTransaction{ + pub fn new(db_tx: DatabaseTransaction, fee_usd: String, call_batch_methods: String) -> Self{ + Self{ + block_hash: db_tx.block_hash, + block_number: db_tx.block_number, + chain: db_tx.chain, + from_address: db_tx.from_address, + gas: db_tx.gas, + gas_price: db_tx.gas_price, + fee_usd: fee_usd, + max_priority_fee_per_gas: db_tx.max_priority_fee_per_gas, + max_fee_per_gas: db_tx.max_fee_per_gas, + hash: db_tx.hash, + input: db_tx.input, + method: db_tx.method, + call_batch_methods, + nonce: db_tx.nonce, + timestamp: db_tx.timestamp, + to_address: db_tx.to_address, + transaction_index: db_tx.transaction_index, + transaction_type: db_tx.transaction_type, + value: db_tx.value, + } + + } +} + #[derive(Debug, Clone, FieldCount)] pub struct DatabaseReceipt { pub contract_address: Option, @@ -360,3 +410,36 @@ pub struct DatabaseChainIndexedState { pub chain: String, pub indexed_blocks_amount: i64, } + +#[derive(Debug, Clone, FieldCount)] +pub struct DatabaseFeeCollector { + pub id: i64, + pub token_address: String, + pub transaction_hash: String, + pub address: String, //this address is the address of who paid the fee or who withdraw the fee depends on the is_withdrawn field + pub block_number: i64, + pub amount : String, + pub fee_usd: String, + pub timestamp: String, + pub is_withdrawn: bool, //if true, the address is the one who withdraw the fee, if false, the address is the one who paid the fee +} + + +#[derive(Debug, Clone, FieldCount)] +pub struct DatabaseSendPacketEvent { + pub transaction_hash: String, + pub sequence: i64, + pub source_port: String, + pub source_channel: String, + pub source_port_indexed: String, + pub source_channel_indexed: String, + pub timeout_revision_number: i64, + pub timeout_revision_height: i64, + pub timeout_timestamp: i64, + pub data: String, + pub amount: String, + pub denom: String, + pub receiver: String, + pub sender: String, + pub memo: String, +} diff --git a/hyperspace/ethereum/evm-indexer/src/indexer.rs b/hyperspace/ethereum/evm-indexer/src/indexer.rs index e64bdca7..df06ed42 100644 --- a/hyperspace/ethereum/evm-indexer/src/indexer.rs +++ b/hyperspace/ethereum/evm-indexer/src/indexer.rs @@ -4,16 +4,18 @@ use crate::{ db::{ db::Database, models::models::{ - DatabaseBlock, DatabaseChainIndexedState, DatabaseContract, DatabaseIBCEventData, - DatabaseLog, DatabaseReceipt, DatabaseTransaction, + DatabaseBlock, DatabaseChainIndexedState, DatabaseContract, DatabaseFeeCollector, DatabaseIBCEventData, DatabaseLog, DatabaseReceipt, DatabaseSendPacketEvent, DatabaseTransaction, DatabaseRelayerTransaction }, }, rpc::rpc::Rpc, }; +use crate::utils::CALLBATCHFACETABI_ABI; + use ethabi::ethereum_types::Address; use futures::future::join_all; -use log::{debug, error, info}; -use std::collections::HashSet; +use log::{debug, error, info, warn}; +use serde::de::IntoDeserializer; +use std::collections::{HashMap, HashSet}; pub async fn sync_chain( rpc: &Rpc, @@ -21,6 +23,7 @@ pub async fn sync_chain( config: &EVMIndexerConfig, indexed_blocks: &mut HashSet, from_block: i64, + eth_price_usd: f64, ) { let db_state = DatabaseChainIndexedState { chain: config.chain.name.to_string(), @@ -63,6 +66,8 @@ pub async fn sync_chain( for missing_blocks_chunk in missing_blocks_chunks { let mut work = vec![]; let mut work2 = vec![]; + let mut work3_fee_withdrawn_event = vec![]; + let mut work4_fee_collected_event_from_ics20_transfer_bank = vec![]; for block_number in missing_blocks_chunk { work2.push(fetch_block(&rpc, &block_number, &config.chain)) @@ -70,8 +75,22 @@ pub async fn sync_chain( work.push(fetch_ibc_events(&rpc, &missing_blocks_chunk, &config.contract_addresses)); + // let fee_collector_address : ethabi::Address = "0x6f11fb4d4178c5d5e3ee4bc7820d8cfed0b59ce7".parse().unwrap(); + let fee_collector_address : ethabi::Address = config.fee_collector_address; + let fee_collector_address_vec = Box::new(vec![fee_collector_address]); + let f: &'static mut Vec = Box::leak(fee_collector_address_vec); + work3_fee_withdrawn_event.push(fetch_fee_withdrawn_from_collector_events(&rpc, &missing_blocks_chunk, f)); + + // let ics20_transfer_bank_address : ethabi::Address = "0x148acd3cd4d6a17cd2abbecd0745b09b62c64f84".parse().unwrap(); + let ics20_transfer_bank_address : ethabi::Address = config.ics20_transfer_bank_address; + let ics20_transfer_bank_address_vec = Box::new(vec![ics20_transfer_bank_address]); + let f: &'static mut Vec = Box::leak(ics20_transfer_bank_address_vec); + work4_fee_collected_event_from_ics20_transfer_bank.push(fetch_fee_collected_events_from_cs20_transfer_bank(&rpc, &missing_blocks_chunk, f)); + let results = join_all(work).await; let results2 = join_all(work2).await; + let results3_fee_collector = join_all(work3_fee_withdrawn_event).await; + let results4_fee_collected_from_ics20_transfer_bank = join_all(work4_fee_collected_event_from_ics20_transfer_bank).await; let mut db_blocks: Vec = Vec::new(); let db_transactions: Vec = Vec::new(); @@ -80,6 +99,9 @@ pub async fn sync_chain( let db_contracts: Vec = Vec::new(); let mut db_ibc_events: Vec = Vec::new(); + let mut db_send_packet_events: Vec = Vec::new(); + + let mut txs_hash_set = HashSet::new(); for result in results { match result { Some( @@ -95,16 +117,36 @@ pub async fn sync_chain( // db_receipts.append(&mut receipts); // db_logs.append(&mut logs); // db_contracts.append(&mut contracts); - db_ibc_events.append(&mut ibc_events); + db_ibc_events.append(&mut ibc_events.0); + db_send_packet_events.append(&mut ibc_events.1); + txs_hash_set = ibc_events.2; }, None => continue, } } + + let mut relayer_txs = vec![]; + let mut all_txs = HashMap::new(); for result in results2 { match result { - Some((block, _transactions, _receipts, _logs, _contracts, _ibc_events)) => { + Some((block, transactions, _receipts, _logs, _contracts, _ibc_events)) => { db_blocks.push(block); + + for i in transactions{ + all_txs.insert(i.hash.clone().to_lowercase(), i.clone()); + // let relayer_address : ethabi::Address = "0x6c57e54C379b5716999ae9AAE4917c9B35AC2CB9".parse().unwrap(); + let relayer_address : ethabi::Address = config.relayer_address; + // let contract_address : ethabi::Address = "0xd856f0f9efa054896fe3596e05978bbe686de131".parse().unwrap(); + let contract_address : ethabi::Address = config.ibc_core_address; + let from_address : ethabi::Address = i.from_address.parse().unwrap(); + let to_address : ethabi::Address = i.to_address.parse().unwrap(); + if from_address == relayer_address && to_address == contract_address { + // if i.from_address.eq_ignore_ascii_case("0x6c57e54C379b5716999ae9AAE4917c9B35AC2CB9") && i.to_address.eq_ignore_ascii_case("0xd856f0f9efa054896fe3596e05978bbe686de131"){ + relayer_txs.push(i); + } + } + // db_transactions.append(&mut transactions); // db_receipts.append(&mut receipts); // db_logs.append(&mut logs); @@ -115,6 +157,24 @@ pub async fn sync_chain( } } + // relayer_txs.retain(|x| x.from_address.eq_ignore_ascii_case("0x6c57e54C379b5716999ae9AAE4917c9B35AC2CB9")); + relayer_txs.sort_by_key(|x| x.block_number); + + let relayer_txs = relayer_txs.into_iter().map(|x| { + let methods = get_batch_calls_methods_json(x.input.clone()); + let fee_usd = (x.gas_price.parse::().unwrap_or_default() * x.gas.parse::().unwrap_or_default() / 1e18) * eth_price_usd; + return DatabaseRelayerTransaction::new(x.clone(), fee_usd.to_string(), methods); + }).collect::>(); + + let mut fee_collector = vec![]; + push_fee_collector_event_to_vec(results3_fee_collector, &all_txs, &mut fee_collector, eth_price_usd); + push_fee_collector_event_to_vec(results4_fee_collected_from_ics20_transfer_bank, &all_txs, &mut fee_collector, eth_price_usd); + + info!("Relayer txs len: {:?}", relayer_txs.len()); + info!("Fee collector len: {:?}", fee_collector.len()); + info!("send packet events len: {:?}", db_send_packet_events.len()); + + db.store_data( &db_blocks, &db_transactions, @@ -122,6 +182,9 @@ pub async fn sync_chain( &db_logs, &db_contracts, &db_ibc_events, + &relayer_txs, + &fee_collector, + &db_send_packet_events, ) .await; @@ -138,33 +201,158 @@ pub async fn sync_chain( } } +fn push_fee_collector_event_to_vec(results3_fee_collector: Vec>>, all_txs: &HashMap, fee_collector: &mut Vec, eth_price_usd: f64) { + for fee_withdrawn in results3_fee_collector{ + match fee_withdrawn { + Some(fee_withdrawn) => { + for mut i in fee_withdrawn{ + //need to get a timestamp for this event bz log does not contains timestamp + let db_tx = all_txs.get(&i.transaction_hash.to_lowercase()); + match db_tx { + Some(db_tx) => { + i.timestamp = db_tx.timestamp.clone(); + }, + None => { + warn!("No tx found for fee withdrawn event: {:?}", i); + }, + } + if i.token_address == "0x0000000000000000000000000000000000000000"{ + // i.amount is a hex string + let hex_string = i.amount.trim_start_matches("0x"); + let hex_value = u64::from_str_radix(hex_string, 16); + match hex_value { + Ok(hex_value) => { + let float_amount = hex_value as f64 / 1e18; + i.fee_usd = (float_amount * eth_price_usd).to_string(); + }, + Err(e) => { + error!("Error converting hex string to u64: {:?}", e); + }, + } + } + + fee_collector.push(i); + } + }, + None => continue, + } +} +} + +fn get_batch_calls_methods_json(input: String) -> String{ + let methods = get_batch_calls_methods(input).unwrap_or(vec![]); + let string_json = serde_json::to_string(&methods); + match string_json { + Ok(string_json) => { + return string_json; + }, + Err(e) => { + error!("Error converting batch calls methods to json: {:?}", e); + return "[]".to_string(); + }, + } +} + +fn get_batch_calls_methods(input: String) -> Result, ()>{ + //remove 0x from beginning + let input = input.trim_start_matches("0x"); + let bytes = hex::decode(input).unwrap(); + let batch_func = &CALLBATCHFACETABI_ABI.function("callBatch"); + let Ok(batch_func) = batch_func else { + return Err(()); + }; + let ethers::abi::Token::Array(batch_calldata) = + batch_func + .decode_input(&bytes[4..]).map_err(|e| { + error!("Error decoding input: {:?}", e); + })? + .pop() + .ok_or_else(|| { + error!("No input data found in callBatch."); + })? + else{ + error!("Error decoding input is not a Token::Array"); + return Err(()); + }; + + let mut ret = vec![]; + for calldata in batch_calldata { + let ethers::abi::Token::Bytes(input) = calldata else { + error!("Parsing error in batch call data. Skipping."); + continue; + }; + let method = input[..4].to_vec(); + let method = hex::encode(method); + ret.push(method); + } + Ok(ret) +} + async fn fetch_ibc_events( rpc: &Rpc, block_numbers: &[i64], contract_addresses: &[Address], -) -> Option> { +) -> Option<(Vec, Vec, HashSet)> { // `from_tos` is `block_numbers` shrinked to pairs of `(from_block, to_block)` // e.g. block numbers = [1, 2, 3, 5, 6, 7, 8, 10], from_tos = [(1,3), (5,8), (10, 10)] - let from_tos: Vec<(i64, i64)> = block_numbers.iter().fold(vec![], |mut acc, block_number| { - if acc.is_empty() { - acc.push((*block_number, *block_number)); - return acc + let from_tos: Vec<(i64, i64)> = get_from_to_block_range(block_numbers); + // dbg!(&from_tos); + let mut events = vec![]; + let mut event_send_packets = vec![]; + let mut txs = HashSet::new(); + for (from, to) in from_tos { + let result = rpc.get_ibc_logs(from, to, contract_addresses, &mut txs).await; + match result { + Ok(Some(r)) => { + events.extend(r.0); + event_send_packets.extend(r.1); + }, + Err(e) => { + error!("Error fetching IBC events: {:?}", e); + return None + }, + _ => {}, } + } + return Some((events, event_send_packets, txs)) +} - let last_from_to = acc.last_mut().unwrap(); - - if last_from_to.1 + 1 == *block_number { - last_from_to.1 = *block_number; - } else { - acc.push((*block_number, *block_number)); +async fn fetch_fee_withdrawn_from_collector_events( + rpc: &Rpc, + block_numbers: &[i64], + contract_addresses: &[Address], +) -> Option> { + // `from_tos` is `block_numbers` shrinked to pairs of `(from_block, to_block)` + // e.g. block numbers = [1, 2, 3, 5, 6, 7, 8, 10], from_tos = [(1,3), (5,8), (10, 10)] + let from_tos: Vec<(i64, i64)> = get_from_to_block_range(block_numbers); + // dbg!(&from_tos); + let mut events = vec![]; + for (from, to) in from_tos { + let result = rpc.get_fee_collector_logs(from, to, contract_addresses).await; + match result { + Ok(Some(r)) => events.extend(r), + Err(e) => { + error!("Error fetching IBC events: {:?}", e); + return None + }, + _ => {}, } + } + return Some(events) +} - acc - }); +async fn fetch_fee_collected_events_from_cs20_transfer_bank( + rpc: &Rpc, + block_numbers: &[i64], + contract_addresses: &[Address], +) -> Option> { + // `from_tos` is `block_numbers` shrinked to pairs of `(from_block, to_block)` + // e.g. block numbers = [1, 2, 3, 5, 6, 7, 8, 10], from_tos = [(1,3), (5,8), (10, 10)] + let from_tos = get_from_to_block_range(block_numbers); // dbg!(&from_tos); let mut events = vec![]; for (from, to) in from_tos { - let result = rpc.get_ibc_logs(from, to, contract_addresses).await; + let result = rpc.get_fee_collected_logs_from_ics20_transfer_bank(from, to, contract_addresses).await; match result { Ok(Some(r)) => events.extend(r), Err(e) => { @@ -174,18 +362,29 @@ async fn fetch_ibc_events( _ => {}, } } - // match receipts_data { - // Some((mut receipts, mut logs, mut contracts, mut ibc_events)) => { - // db_receipts.append(&mut receipts); - // db_logs.append(&mut logs); - // db_contracts.append(&mut contracts); - // db_ibc_events.append(&mut ibc_events); - // }, - // None => return None, - // } return Some(events) } +fn get_from_to_block_range(block_numbers: &[i64]) -> Vec<(i64, i64)> { + let from_tos: Vec<(i64, i64)> = block_numbers.iter().fold(vec![], |mut acc, block_number| { + if acc.is_empty() { + acc.push((*block_number, *block_number)); + return acc + } + + let last_from_to = acc.last_mut().unwrap(); + + if last_from_to.1 + 1 == *block_number { + last_from_to.1 = *block_number; + } else { + acc.push((*block_number, *block_number)); + } + + acc + }); + from_tos +} + async fn fetch_block( rpc: &Rpc, block_number: &i64, diff --git a/hyperspace/ethereum/evm-indexer/src/rpc/rpc.rs b/hyperspace/ethereum/evm-indexer/src/rpc/rpc.rs index 0b6d2411..b0377110 100644 --- a/hyperspace/ethereum/evm-indexer/src/rpc/rpc.rs +++ b/hyperspace/ethereum/evm-indexer/src/rpc/rpc.rs @@ -2,8 +2,7 @@ use crate::{ chains::chains::Chain, configs::indexer_config::EVMIndexerConfig, db::models::models::{ - DatabaseBlock, DatabaseContract, DatabaseIBCEventData, DatabaseLog, DatabaseReceipt, - DatabaseTransaction, + DatabaseBlock, DatabaseContract, DatabaseFeeCollector, DatabaseIBCEventData, DatabaseLog, DatabaseReceipt, DatabaseSendPacketEvent, DatabaseTransaction }, utils::format_small_number, }; @@ -15,13 +14,13 @@ use ethers::{ prelude::Log, types::{Block, Transaction, TransactionReceipt, U256}, }; -use jsonrpsee::core::{client::ClientT, rpc_params}; +use jsonrpsee::{core::{client::ClientT, rpc_params}, types::error}; use jsonrpsee_http_client::{HttpClient, HttpClientBuilder}; use log::{debug, info, warn}; use rand::seq::SliceRandom; use serde_json::{json, Error}; use sqlx::types::Json; -use std::time::Duration; +use std::{collections::HashSet, time::Duration}; #[derive(Debug, Clone)] pub struct Rpc { @@ -29,6 +28,47 @@ pub struct Rpc { pub chain: Chain, } +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +struct SendPacketEvent{ + sequence: i64, + source_port: String, + source_channel: String, + source_port_indexed: String, + source_channel_indexed: String, + timeout_height : SendPacketTimeoutEvent, + timeout_timestamp: i64, + data: String, +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +struct SendPacketTimeoutEvent{ + revision_number: i64, + revision_height: i64, +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +struct FeeCollectedEvent{ + token : String, + from : String, + amount : String, +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +struct FeeWithdrawnEvent{ + token : String, + to : String, + amount : String, +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +struct SendPacketData { + amount: String, + denom: String, + receiver: String, + sender: String, + memo: Option, +} + impl Rpc { pub async fn new(config: &EVMIndexerConfig) -> Result { info!("Starting EVM rpc service"); @@ -263,7 +303,8 @@ impl Rpc { from_block: i64, to_block: i64, contract_addresses: &[Address], - ) -> Result>> { + txs: &mut HashSet + ) -> Result, Vec)>> { let client = self.get_client(); debug!("get_ibc_logs: {from_block}..{to_block} of {contract_addresses:?}"); @@ -280,6 +321,7 @@ impl Rpc { let logs: Vec = serde_json::from_value(value)?; let mut db_ibc_events: Vec = Vec::new(); + let mut db_send_packets: Vec = Vec::new(); for log in logs { if log.block_number.is_none() || @@ -293,8 +335,9 @@ impl Rpc { warn!("log was removed: {:?}", log); continue } - + let cloned_log = log.clone(); if let Ok(Some(s)) = parse_log(log.clone()) { + txs.insert(log.transaction_hash.unwrap_or_default().to_string()); db_ibc_events.push(DatabaseIBCEventData { block_number: log.block_number.unwrap().as_u64() as i64, event_data: serde_json::from_str(s.as_str()).unwrap(), @@ -309,11 +352,185 @@ impl Rpc { } else { warn!("failed to parse a log at {}", log.block_number.unwrap().as_u64()); } + //todo return from method and store to db as a send packet event + if let Ok(Some(sent_packet)) = parse_log_as_send_packet(cloned_log.clone()){ + println!("_____________________________________________________________"); + println!("sent_packet : {}", sent_packet); + let object : SendPacketEvent = serde_json::from_str(sent_packet.as_str()).unwrap(); + println!("object : {:?}", object); + + let hex_string = &object.data[2..]; + + let bytes = hex::decode(hex_string).unwrap(); + // Convert bytes to a string + let json_string = String::from_utf8_lossy(&bytes); + + let data: SendPacketData = serde_json::from_str(json_string.as_ref()).unwrap(); + println!("SendPacketData : {:?}", data); + println!("_____________________________________________________________"); + let db_send_packet_event = DatabaseSendPacketEvent{ + transaction_hash: cloned_log.transaction_hash.unwrap_or_default().to_string(), + sequence: object.sequence, + source_port: object.source_port, + source_channel: object.source_channel, + source_port_indexed: object.source_port_indexed, + source_channel_indexed: object.source_channel_indexed, + timeout_revision_number: object.timeout_height.revision_number, + timeout_revision_height: object.timeout_height.revision_height, + timeout_timestamp: object.timeout_timestamp, + data: object.data, + amount: data.amount, + denom: data.denom, + receiver: data.receiver, + sender: data.sender, + memo: data.memo.unwrap_or_default(), + }; + db_send_packets.push(db_send_packet_event); + } + } + + return Ok(Some((db_ibc_events, db_send_packets))) + } + + pub async fn get_fee_collector_logs( + &self, + from_block: i64, + to_block: i64, + contract_addresses: &[Address], + ) -> Result>> { + let client = self.get_client(); + println!("get_fee_collector_logs: {from_block}..{to_block} of {contract_addresses:?}"); + + let value = client + .request( + "eth_getLogs", + rpc_params![json!({ + "fromBlock": format!("0x{:x}", from_block), + "toBlock": format!("0x{:x}", to_block), + "address": contract_addresses.iter().map(|x| format!("0x{:x}", x)).collect::>(), + })], + ) + .await?; + let logs: Vec = serde_json::from_value(value)?; + println!("logs : {:?}", logs); + + let mut db_ibc_events: Vec = Vec::new(); + + for log in logs { + if log.block_number.is_none() || + log.log_index.is_none() || + log.transaction_index.is_none() + { + continue + } + + if log.removed == Some(true) { + warn!("log was removed: {:?}", log); + continue + } + + if let Ok(Some(s)) = parse_log_fee_withdrawn(log.clone()) { + println!("Fee collector log : {}", s); + let object : FeeWithdrawnEvent = serde_json::from_str(s.as_str()).unwrap(); + println!("FeeWithdrawnEvent : {:?}", object); + let db_fee_collected = DatabaseFeeCollector{ + id: 0, + token_address: object.token, + transaction_hash: log.transaction_hash.unwrap_or_default().to_string(), + address: object.to, + block_number: log.block_number.unwrap().as_u64() as i64, + amount: object.amount, + fee_usd: "".to_string(), + timestamp: "".to_string(), + is_withdrawn: true, + }; + + db_ibc_events.push(db_fee_collected) + } else { + warn!("failed to parse a log at {}", log.block_number.unwrap().as_u64()); + } } return Ok(Some(db_ibc_events)) } + pub async fn get_fee_collected_logs_from_ics20_transfer_bank( + &self, + from_block: i64, + to_block: i64, + contract_addresses: &[Address], + ) -> Result>> { + let client = self.get_client(); + println!("get_fee_collector_logs: {from_block}..{to_block} of {contract_addresses:?}"); + + let value = client + .request( + "eth_getLogs", + rpc_params![json!({ + "fromBlock": format!("0x{:x}", from_block), + "toBlock": format!("0x{:x}", to_block), + "address": contract_addresses.iter().map(|x| format!("0x{:x}", x)).collect::>(), + })], + ) + .await?; + let logs: Vec = serde_json::from_value(value)?; + println!("logs : {:?}", logs); + + let mut db_database_fee_collected: Vec = Vec::new(); + + for log in logs { + if log.block_number.is_none() || + log.log_index.is_none() || + log.transaction_index.is_none() + { + continue + } + + if log.removed == Some(true) { + warn!("log was removed: {:?}", log); + continue + } + + if let Ok(Some(s)) = parse_log_fee_collected_from_transfer_bank(log.clone()) { + println!("Fee collected log from ics20 transfer bank: {}", s); + println!("_____________________________________________________________"); + println!("FeeCollectedEvent : {}", s); + let object : FeeCollectedEvent = serde_json::from_str(s.as_str()).unwrap(); + println!("FeeCollectedEvent : {:?}", object); + let db_fee_collected = DatabaseFeeCollector{ + id: 0, + token_address: object.token, + transaction_hash: log.transaction_hash.unwrap_or_default().to_string(), + address: object.from, + block_number: log.block_number.unwrap().as_u64() as i64, + amount: object.amount, + fee_usd: "".to_string(), + timestamp: "".to_string(), + is_withdrawn: false, + }; + println!("_____________________________________________________________"); + + + // db_ibc_events.push(DatabaseIBCEventData { + // block_number: log.block_number.unwrap().as_u64() as i64, + // event_data: serde_json::from_str(s.as_str()).unwrap(), + // address: log.address.0.to_vec(), + // topic0: log.topics[0].0.to_vec(), + // topics: log.topics.iter().map(|x| x.0.to_vec()).collect(), + // data: log.data.0.to_vec(), + // tx_index: log.transaction_index.unwrap().as_u64() as i64, + // event_index: log.log_index.unwrap().as_u64() as i64, + // raw_log: Json(log), + // }) + db_database_fee_collected.push(db_fee_collected) + } else { + warn!("failed to parse a log at {}", log.block_number.unwrap().as_u64()); + } + } + + return Ok(Some(db_database_fee_collected)) + } + fn get_client(&self) -> &HttpClient { let client = self.clients.choose(&mut rand::thread_rng()).unwrap(); return client @@ -325,14 +542,18 @@ fn parse_log(log: Log) -> Result, Error> { let raw_log = RawLog::from(log.clone()); let topic0 = log.topics[0]; + println!("topic0 : {}", topic0); macro_rules! handle_events { ($topic0:ident, $events:ident, $log:ident, $raw_log:ident, $height:ident, $($ty:ty),+) => { $(if $topic0 == <$ty>::signature() { + println!("signature enter {}", <$ty>::signature()); let ev = <$ty>::decode_log(&$raw_log).expect("decode event"); // log::debug!(target: "hyperspace_ethereum", "encountered event: {:?} at {}", ev.event_type(), ev.height()); + println!("data : {}", serde_json::to_string(&ev)?); return Ok(Some(serde_json::to_string(&ev)?)); } else )+ { + // println!("signature did not {}", <$ty>::signature()); log::warn!( target: "hyperspace_ethereum", "unknown event: {}", log.log_type.unwrap_or(format!("{:?}", $topic0)) @@ -376,6 +597,130 @@ fn parse_log(log: Log) -> Result, Error> { GeneratedClientIdentifierFilter, GeneratedConnectionIdentifierFilter, GeneratedChannelIdentifierFilter, - OwnershipTransferredFilter + OwnershipTransferredFilter, + // + FeeWithdrawnFilter, + NativeFeeDepositedFilter, + RefunderTransferredFilter + ) +} + +fn parse_log_fee_withdrawn(log: Log) -> Result, Error> { + use crate::utils::*; + + let raw_log = RawLog::from(log.clone()); + let topic0 = log.topics[0]; + println!("topic0 : {}", topic0); + + macro_rules! handle_events { + ($topic0:ident, $events:ident, $log:ident, $raw_log:ident, $height:ident, $($ty:ty),+) => { + $(if $topic0 == <$ty>::signature() { + let ev = <$ty>::decode_log(&$raw_log); + match ev { + Ok(ev) => { + return Ok(Some(serde_json::to_string(&ev)?)); + }, + Err(e) => { + log::warn!( + target: "hyperspace_ethereum", "failed to decode event: {}, topic0: {}, signature: {}", + e, + $topic0, + <$ty>::signature() + ); + return Ok(None) + } + } + } else )+ { + // println!("signature did not {}", <$ty>::signature()); + log::warn!( + target: "hyperspace_ethereum", "unknown event: {}", + log.log_type.unwrap_or(format!("{:?}", $topic0)) + ); + return Ok(None) + } + }; + } + + handle_events!( + topic0, + event, + log, + raw_log, + height, + FeeWithdrawnFilter + ) +} + + +fn parse_log_fee_collected_from_transfer_bank(log: Log) -> Result, Error> { + use crate::utils::*; + + let raw_log = RawLog::from(log.clone()); + let topic0 = log.topics[0]; + println!("topic0 : {}", topic0); + + macro_rules! handle_events { + ($topic0:ident, $events:ident, $log:ident, $raw_log:ident, $height:ident, $($ty:ty),+) => { + $(if $topic0 == <$ty>::signature() { + println!("signature enter {}", <$ty>::signature()); + let ev = <$ty>::decode_log(&$raw_log).expect("decode event"); + // log::debug!(target: "hyperspace_ethereum", "encountered event: {:?} at {}", ev.event_type(), ev.height()); + println!("data : {}", serde_json::to_string(&ev)?); + return Ok(Some(serde_json::to_string(&ev)?)); + } else )+ { + // println!("signature did not {}", <$ty>::signature()); + log::warn!( + target: "hyperspace_ethereum", "unknown event: {}", + log.log_type.unwrap_or(format!("{:?}", $topic0)) + ); + return Ok(None) + } + }; + } + + handle_events!( + topic0, + event, + log, + raw_log, + height, + FeeCollectedFilter + ) +} + + +fn parse_log_as_send_packet(log: Log) -> Result, Error> { + use crate::utils::*; + + let raw_log = RawLog::from(log.clone()); + let topic0 = log.topics[0]; + println!("topic0 : {}", topic0); + + macro_rules! handle_events { + ($topic0:ident, $events:ident, $log:ident, $raw_log:ident, $height:ident, $($ty:ty),+) => { + $(if $topic0 == <$ty>::signature() { + println!("signature enter {}", <$ty>::signature()); + let ev = <$ty>::decode_log(&$raw_log).expect("decode event"); + // log::debug!(target: "hyperspace_ethereum", "encountered event: {:?} at {}", ev.event_type(), ev.height()); + println!("data : {}", serde_json::to_string(&ev)?); + return Ok(Some(serde_json::to_string(&ev)?)); + } else )+ { + // println!("signature did not {}", <$ty>::signature()); + log::warn!( + target: "hyperspace_ethereum", "unknown event: {}", + log.log_type.unwrap_or(format!("{:?}", $topic0)) + ); + return Ok(None) + } + }; + } + + handle_events!( + topic0, + event, + log, + raw_log, + height, + SendPacketFilter ) } diff --git a/hyperspace/ethereum/evm-indexer/src/utils.rs b/hyperspace/ethereum/evm-indexer/src/utils.rs index 6ddc9d8a..8d537b0c 100644 --- a/hyperspace/ethereum/evm-indexer/src/utils.rs +++ b/hyperspace/ethereum/evm-indexer/src/utils.rs @@ -50,6 +50,13 @@ abigen!( OwnershipFacetAbi, "../src/abi/ownership-facet-abi.json", event_derives (serde::Deserialize, serde::Serialize); + + FeeCollectorAbi, + "../src/abi/fee-collector-abi.json", + event_derives (serde::Deserialize, serde::Serialize); + + CallBatchFacetAbi, + "../src/abi/call-batch-facet-abi.json"; ); pub fn format_nonce(h: H64) -> String { diff --git a/hyperspace/ethereum/src/abi/ics20-transfer-bank-abi.json b/hyperspace/ethereum/src/abi/ics20-transfer-bank-abi.json index 56ae9c34..8caa9d20 100644 --- a/hyperspace/ethereum/src/abi/ics20-transfer-bank-abi.json +++ b/hyperspace/ethereum/src/abi/ics20-transfer-bank-abi.json @@ -36,6 +36,51 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "getFeeCollector", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getFeePercentage", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint32", + "internalType": "uint32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getMinFeeAmount", + "inputs": [ + { + "name": "denom", + "type": "string", + "internalType": "string" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "getMinTokenSendAmount", @@ -568,7 +613,7 @@ } ], "outputs": [], - "stateMutability": "nonpayable" + "stateMutability": "payable" }, { "type": "function", @@ -634,6 +679,24 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "setMinFeeAmount", + "inputs": [ + { + "name": "denom", + "type": "string", + "internalType": "string" + }, + { + "name": "minFeeAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "setMinTimeoutTimestamp", @@ -665,6 +728,74 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "withdrawFee", + "inputs": [ + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "FeeCollected", + "inputs": [ + { + "name": "token", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "from", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "FeeWithdrawn", + "inputs": [ + { + "name": "token", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, { "type": "error", "name": "NotContractOwner",