From 94b44db5cd6543c6614fb2d9a763f29b04b10806 Mon Sep 17 00:00:00 2001 From: scx1332 Date: Mon, 23 Oct 2023 16:26:56 +0200 Subject: [PATCH] Fixes to replacement logic, a bit of refactor and improvements --- config-payments.toml | 5 +- crates/erc20_payment_lib/src/config.rs | 2 +- .../src/db/ops/chain_tx_ops.rs | 16 ++ crates/erc20_payment_lib/src/db/ops/tx_ops.rs | 3 +- .../erc20_payment_lib/src/sender/process.rs | 222 ++++++++++++------ crates/erc20_payment_lib/src/setup.rs | 6 +- crates/erc20_payment_lib/src/transaction.rs | 6 +- crates/erc20_payment_lib/src/utils.rs | 73 +++++- .../src/account_balance.rs | 6 +- .../src/config_setup.rs | 2 +- .../erc20_payment_lib_test/src/durabily2.rs | 6 +- .../src/multi_erc20_transfer.rs | 6 +- crates/web3_test_proxy_client/src/list_txs.rs | 6 +- junk/generate_command.sh | 1 + src/main.rs | 30 ++- src/options.rs | 15 +- src/stats.rs | 109 +++++++-- .../docker_01_basic/single_erc20_transfer.rs | 4 +- tests/docker_01_basic/single_gas_transfer.rs | 4 +- tests/docker_02_errors/wrong_chain_id.rs | 5 +- .../single_transfer_with_problems.rs | 5 +- tests/docker_04_multi.rs | 4 +- 22 files changed, 400 insertions(+), 136 deletions(-) create mode 100644 junk/generate_command.sh diff --git a/config-payments.toml b/config-payments.toml index 4d39a66d..4f091a12 100644 --- a/config-payments.toml +++ b/config-payments.toml @@ -1,6 +1,6 @@ [engine] -service-sleep = 1 -process-sleep = 1 +service-sleep = 10 +process-sleep = 10 automatic-recover = false [chain.rinkeby] @@ -61,7 +61,6 @@ transaction-timeout = 100 token = { address = "0x0B220b82F3eA3B7F6d9A1D8ab58930C064A2b5Bf", symbol = "GLM" } # multi-contract = { address = "0x50100d4faf5f3b09987dea36dc2eddd57a3e561b", max-at-once = 10 } confirmation-blocks = 1 -allow-max-fee-greater-than-priority-fee = true block-explorer-url = "https://polygonscan.com" [chain.dev] diff --git a/crates/erc20_payment_lib/src/config.rs b/crates/erc20_payment_lib/src/config.rs index 8a6cafe8..e2bd5d6c 100644 --- a/crates/erc20_payment_lib/src/config.rs +++ b/crates/erc20_payment_lib/src/config.rs @@ -81,7 +81,7 @@ pub struct Chain { pub faucet_eth_amount: Option, pub faucet_glm_amount: Option, pub block_explorer_url: Option, - pub allow_max_fee_greater_than_priority_fee: Option, + pub replacement_timeout: Option, } #[derive(Deserialize, Debug, Clone)] diff --git a/crates/erc20_payment_lib/src/db/ops/chain_tx_ops.rs b/crates/erc20_payment_lib/src/db/ops/chain_tx_ops.rs index 57c36a7b..e48b6b0c 100644 --- a/crates/erc20_payment_lib/src/db/ops/chain_tx_ops.rs +++ b/crates/erc20_payment_lib/src/db/ops/chain_tx_ops.rs @@ -33,6 +33,22 @@ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) R Ok(res) } +pub async fn get_chain_txs_by_chain_id( + conn: &SqlitePool, + chain_id: i64, + limit: Option, +) -> Result, sqlx::Error> { + let limit = limit.unwrap_or(i64::MAX); + let rows = sqlx::query_as::<_, ChainTxDao>( + r"SELECT * FROM chain_tx WHERE chain_id = $1 ORDER by id DESC LIMIT $2", + ) + .bind(chain_id) + .bind(limit) + .fetch_all(conn) + .await?; + Ok(rows) +} + pub async fn get_chain_tx(conn: &SqlitePool, id: i64) -> Result { let row = sqlx::query_as::<_, ChainTxDao>(r"SELECT * FROM chain_tx WHERE id = $1") .bind(id) diff --git a/crates/erc20_payment_lib/src/db/ops/tx_ops.rs b/crates/erc20_payment_lib/src/db/ops/tx_ops.rs index ad9ba376..6d0e63a9 100644 --- a/crates/erc20_payment_lib/src/db/ops/tx_ops.rs +++ b/crates/erc20_payment_lib/src/db/ops/tx_ops.rs @@ -8,6 +8,7 @@ pub const TRANSACTION_FILTER_PROCESSING: &str = "processing > 0 AND first_proces pub const TRANSACTION_FILTER_TO_PROCESS: &str = "processing > 0"; pub const TRANSACTION_FILTER_ALL: &str = "id >= 0"; pub const TRANSACTION_FILTER_DONE: &str = "processing = 0"; +pub const TRANSACTION_ORDER_BY_ID_AND_REPLACEMENT_ID: &str = "orig_tx_id DESC,id ASC"; pub const TRANSACTION_ORDER_BY_CREATE_DATE: &str = "created_date ASC"; pub const TRANSACTION_ORDER_BY_FIRST_PROCESSED_DATE_DESC: &str = "first_processed DESC"; @@ -124,7 +125,7 @@ pub async fn get_next_transactions_to_process( conn, Some(TRANSACTION_FILTER_TO_PROCESS), Some(limit), - Some(TRANSACTION_ORDER_BY_CREATE_DATE), + Some(TRANSACTION_ORDER_BY_ID_AND_REPLACEMENT_ID), ) .await } diff --git a/crates/erc20_payment_lib/src/sender/process.rs b/crates/erc20_payment_lib/src/sender/process.rs index 797d2d54..0a5a26e8 100644 --- a/crates/erc20_payment_lib/src/sender/process.rs +++ b/crates/erc20_payment_lib/src/sender/process.rs @@ -5,6 +5,7 @@ use crate::db::ops::{ use crate::error::PaymentError; use crate::error::*; use crate::{err_create, err_custom_create, err_from}; +use rust_decimal::prelude::Zero; use rust_decimal::Decimal; use sqlx::SqlitePool; use std::str::FromStr; @@ -12,7 +13,7 @@ use std::sync::Arc; use std::time::Duration; use tokio::sync::Mutex; use web3::transports::Http; -use web3::types::{Address, BlockId, BlockNumber, U256}; +use web3::types::{Address, BlockId, BlockNumber, U256, U64}; use web3::Web3; use crate::db::model::TxDao; @@ -27,7 +28,9 @@ use crate::transaction::check_transaction; use crate::transaction::find_receipt; use crate::transaction::send_transaction; use crate::transaction::sign_transaction_with_callback; -use crate::utils::{datetime_from_u256_timestamp, u256_to_rust_dec}; +use crate::utils::{ + datetime_from_u256_timestamp, get_env_bool_value, DecimalConvExt, StringConvExt, U256ConvExt, +}; #[derive(Debug)] pub enum ProcessTransactionResult { @@ -70,6 +73,8 @@ pub async fn process_transaction( return Ok((web3_tx_dao.clone(), ProcessTransactionResult::Unknown)); }; + let is_polygon_eco_mode = chain_setup.chain_id == 137 && get_env_bool_value("POLYGON_ECO_MODE"); + let web3 = payment_setup.get_provider(chain_id).map_err(|_e| { err_create!(TransactionFailedError::new(&format!( "Failed to get provider for chain id: {chain_id}" @@ -138,6 +143,71 @@ pub async fn process_transaction( } } } + let max_fee_per_gas = + U256::from_dec_str(&web3_tx_dao.max_fee_per_gas).map_err(err_from!())?; + + if is_polygon_eco_mode { + let blockchain_gas_price = web3 + .eth() + .block(BlockId::Number(BlockNumber::Latest)) + .await + .map_err(err_from!())? + .ok_or(err_create!(TransactionFailedError::new( + "Failed to get latest block from RPC node" + )))? + .base_fee_per_gas + .ok_or(err_create!(TransactionFailedError::new( + "Failed to get base_fee_per_gas from RPC node" + )))?; + + let extra_gas = if let Ok(extra_gas) = std::env::var("POLYGON_ECO_MODE_EXTRA_GAS") { + let mut extra_gas = Decimal::from_str_exact(&extra_gas).map_err(|err| { + err_custom_create!("POLYGON_ECO_MODE_EXTRA_GAS has to be decimal format {err}") + })?; + + if extra_gas > Decimal::from(30) { + log::warn!("Extra gas is too high, setting to 30"); + extra_gas = Decimal::from(30); + } + if extra_gas < Decimal::from(-10) { + log::warn!("Extra gas is too low, setting to -10"); + extra_gas = Decimal::from(-10); + } + extra_gas + } else { + Decimal::zero() + }; + + let mut new_target_gas_u256 = if extra_gas >= Decimal::zero() { + let extra_gas_u256 = extra_gas.to_u256_from_gwei().map_err(err_from!())?; + blockchain_gas_price + extra_gas_u256 + } else { + let extra_gas_u256 = extra_gas.to_u256_from_gwei().map_err(err_from!())?; + let min_base_price = U256::from(1_000_000_000u64); + + let mut new_target_gas_u256 = blockchain_gas_price - extra_gas_u256; + if blockchain_gas_price < min_base_price + extra_gas_u256 { + new_target_gas_u256 = min_base_price; + } + new_target_gas_u256 + }; + let tx_priority_fee_u256 = + U256::from_dec_str(&web3_tx_dao.priority_fee).map_err(err_from!())?; + + //max_fee_per_gas cannot be lower than priority fee + if new_target_gas_u256 < tx_priority_fee_u256 { + new_target_gas_u256 = tx_priority_fee_u256; + } + + if new_target_gas_u256 * 11 < max_fee_per_gas * 10 { + log::warn!("Eco mode activated. Sending transaction with lower base fee. Blockchain base fee: {} Gwei, Tx base fee: {} Gwei", + blockchain_gas_price.to_gwei().map_err(err_from!())?, + new_target_gas_u256.to_gwei().map_err(err_from!())?, + ); + + web3_tx_dao.max_fee_per_gas = new_target_gas_u256.to_string(); + } + } web3_tx_dao.nonce = Some(nonce); nonce @@ -172,8 +242,8 @@ pub async fn process_transaction( msg, chain_id, from_addr, - u256_to_rust_dec(gas_balance, Some(18)).map_err(err_from!())?, - u256_to_rust_dec(expected_gas_balance, Some(18)).map_err(err_from!())? + gas_balance.to_eth().map_err(err_from!())?, + expected_gas_balance.to_eth().map_err(err_from!())? ); } } @@ -211,29 +281,24 @@ pub async fn process_transaction( if gas_balance < res { log::warn!( "Gas balance too low for gas {} - vs needed: {}", - u256_to_rust_dec(gas_balance, Some(18)).map_err(err_from!())?, - u256_to_rust_dec(res, Some(18)).map_err(err_from!())? + gas_balance.to_eth().map_err(err_from!())?, + res.to_eth().map_err(err_from!())? ); send_driver_event( &event_sender, DriverEventContent::TransactionStuck(TransactionStuckReason::NoGas( NoGasDetails { tx: web3_tx_dao.clone(), - gas_balance: Some( - u256_to_rust_dec(gas_balance, Some(18)) - .map_err(err_from!())?, - ), - gas_needed: Some( - u256_to_rust_dec(res, Some(18)).map_err(err_from!())?, - ), + gas_balance: Some(gas_balance.to_eth().map_err(err_from!())?), + gas_needed: Some(res.to_eth().map_err(err_from!())?), }, )), ) .await; return Err(err_custom_create!( "Gas balance too low for gas {} - vs needed: {}", - u256_to_rust_dec(gas_balance, Some(18)).map_err(err_from!())?, - u256_to_rust_dec(res, Some(18)).map_err(err_from!())? + gas_balance.to_eth().map_err(err_from!())?, + res.to_eth().map_err(err_from!())? )); } } @@ -341,7 +406,7 @@ pub async fn process_transaction( let mut current_tx = web3_tx_dao.clone(); let res = loop { let res = find_receipt(web3, &mut current_tx).await?; - if res { + if res.is_some() { //if receipt found then break early, we found our transaction break res; } @@ -355,13 +420,14 @@ pub async fn process_transaction( } }; - if res { + if let Some(effective_gas_price) = res { let Some(block_number) = current_tx.block_number.map(|bn| bn as u64) else { return Err(err_custom_create!( "Block number not found on dao for tx: {}", current_tx.id )); }; + log::info!( "Receipt found: tx {} tx_hash: {}", current_tx.id, @@ -375,6 +441,38 @@ pub async fn process_transaction( current_tx.tx_hash.clone().unwrap_or_default() ); + if is_polygon_eco_mode { + let blockchain_gas_price = web3 + .eth() + .block(BlockId::Number(BlockNumber::Number(U64::from( + block_number, + )))) + .await; + if let Ok(Some(block)) = blockchain_gas_price { + if let Some(base_fee_per_gas_u256) = block.base_fee_per_gas { + if effective_gas_price > base_fee_per_gas_u256 { + let base_fee_per_gas = + base_fee_per_gas_u256.to_gwei().unwrap_or_default(); + let effective_priority_fee = (effective_gas_price + - base_fee_per_gas_u256) + .to_gwei() + .unwrap_or_default(); + log::info!( + "Effective priority fee: {}", + effective_priority_fee + ); + log::info!( + "Saved: {:.2} Gwei ({:.1}%)", + Decimal::from(30) - effective_priority_fee, + Decimal::from(100) + * (Decimal::from(30) - effective_priority_fee) + / (base_fee_per_gas + Decimal::from(30)) + ); + } + } + }; + } + //cleanup txs //let confirmed_tx = current_tx.clone(); let mut orig_tx = current_tx.clone(); @@ -459,37 +557,35 @@ pub async fn process_transaction( .await .map_err(err_from!())?; - let tx_fee_per_gas = u256_to_rust_dec( - U256::from_dec_str(&web3_tx_dao.max_fee_per_gas).map_err(err_from!())?, - Some(9), - ) - .map_err(err_from!())?; - let max_fee_per_gas = - u256_to_rust_dec(chain_setup.max_fee_per_gas, Some(9)).map_err(err_from!())?; - let tx_priority_fee_u256 = - U256::from_dec_str(&web3_tx_dao.priority_fee).map_err(err_from!())?; - let tx_priority_fee = - u256_to_rust_dec(tx_priority_fee_u256, Some(9)).map_err(err_from!())?; - let config_priority_fee = - u256_to_rust_dec(chain_setup.priority_fee, Some(9)).map_err(err_from!())?; - - if !chain_setup - .allow_max_fee_greater_than_priority_fee - .unwrap_or(false) - && tx_priority_fee > tx_fee_per_gas - { + let tx_fee_per_gas = web3_tx_dao.max_fee_per_gas.to_gwei().map_err(err_from!())?; + let max_fee_per_gas = chain_setup.max_fee_per_gas.to_gwei().map_err(err_from!())?; + let tx_pr_fee_u256 = web3_tx_dao.priority_fee.to_u256().map_err(err_from!())?; + let tx_pr_fee = tx_pr_fee_u256.to_gwei().map_err(err_from!())?; + let config_priority_fee = chain_setup.priority_fee.to_gwei().map_err(err_from!())?; + + if tx_pr_fee > tx_fee_per_gas { log::error!( - "Transaction priority fee is greater than max fee per gas for tx: {}. Setup allow_max_fee_greater_than_priority_fee if chain supports it", + "Transaction priority fee is greater than max fee per gas for tx: {}", web3_tx_dao.id ); return Err(err_custom_create!( - "Transaction priority fee is greater than max fee per gas for tx: {}. Setup allow_max_fee_greater_than_priority_fee if chain supports it", + "Transaction priority fee is greater than max fee per gas for tx: {}", web3_tx_dao.id )); } - let support_replacement_transactions = true; - if support_replacement_transactions { + let is_ready_for_replacement = if let Some(first_processed) = web3_tx_dao.first_processed { + let now = chrono::Utc::now(); + let diff = now - first_processed; + if diff.num_seconds() < -10 { + log::warn!("Time changed?? time diff lower than 0"); + } + diff.num_seconds() > chain_setup.replacement_timeout.unwrap_or(60.0).floor() as i64 + } else { + false + }; + + if is_ready_for_replacement { let mut fee_per_gas_changed = false; let mut fee_per_gas_bumped_10 = false; if tx_fee_per_gas != max_fee_per_gas { @@ -514,9 +610,9 @@ pub async fn process_transaction( let mut priority_fee_changed = false; let mut priority_fee_changed_10 = false; - if tx_priority_fee != config_priority_fee { + if tx_pr_fee != config_priority_fee { priority_fee_changed = true; - if tx_priority_fee * Decimal::from(11) <= config_priority_fee * Decimal::from(10) { + if tx_pr_fee * Decimal::from(11) <= config_priority_fee * Decimal::from(10) { priority_fee_changed_10 = true; log::warn!( "Transaction priority fee bumped more than 10% from {} to {} for tx: {}", @@ -532,31 +628,22 @@ pub async fn process_transaction( if fee_per_gas_changed || priority_fee_changed { let mut send_replacement_tx = false; let mut replacement_priority_fee = chain_setup.priority_fee; - let replacement_max_fee_per_gas = chain_setup.max_fee_per_gas; + let mut replacement_max_fee_per_gas = chain_setup.max_fee_per_gas; if priority_fee_changed_10 && fee_per_gas_bumped_10 { send_replacement_tx = true; } else if fee_per_gas_bumped_10 && !priority_fee_changed_10 { replacement_priority_fee = - tx_priority_fee_u256 * U256::from(11) / U256::from(10) + U256::from(1); + tx_pr_fee_u256 * U256::from(11) / U256::from(10) + U256::from(1); if replacement_priority_fee > replacement_max_fee_per_gas { - if chain_setup - .allow_max_fee_greater_than_priority_fee - .unwrap_or(false) - { - //polygon is allowing to send transactions with priority fee greater than max fee per gas - //no additional fixes are needed - } else { - //on other networks this fix is needed - //priority fee cannot be greater than max fee per gas - //it should cover very niche case, because almost always priority fee is lower than max fee per gas - replacement_priority_fee = replacement_max_fee_per_gas; - } + //priority fee cannot be greater than max fee per gas + //it should cover very niche case, because priority fee is lower than max fee per gas + replacement_max_fee_per_gas = replacement_priority_fee; } log::warn!( "Replacement priority fee is bumped by 10% from {} to {}", - tx_priority_fee, - u256_to_rust_dec(replacement_priority_fee, Some(9)).map_err(err_from!())? + tx_pr_fee, + replacement_priority_fee.to_gwei().map_err(err_from!())? ); send_replacement_tx = true; } else { @@ -661,17 +748,14 @@ pub async fn process_transaction( if let Ok(Some(block)) = web3.eth().block(BlockId::Number(BlockNumber::Latest)).await { - let block_base_fee_per_gas_gwei = u256_to_rust_dec( - block.base_fee_per_gas.unwrap_or(U256::zero()), - Some(9), - ) - .map_err(err_from!())?; - let tx_max_fee_per_gas_gwei = u256_to_rust_dec( - U256::from_dec_str(&web3_tx_dao.max_fee_per_gas) - .map_err(err_from!())?, - Some(9), - ) - .map_err(err_from!())?; + let block_base_fee_per_gas_gwei = block + .base_fee_per_gas + .unwrap_or_default() + .to_gwei() + .map_err(err_from!())?; + + let tx_max_fee_per_gas_gwei = + web3_tx_dao.max_fee_per_gas.to_gwei().map_err(err_from!())?; let assumed_min_priority_fee_gwei = if web3_tx_dao.chain_id == 137 { const POLYGON_MIN_PRIORITY_FEE_FOR_GAS_PRICE_CHECK: u32 = 30; Decimal::from(POLYGON_MIN_PRIORITY_FEE_FOR_GAS_PRICE_CHECK) diff --git a/crates/erc20_payment_lib/src/setup.rs b/crates/erc20_payment_lib/src/setup.rs index ab03f0ad..606be784 100644 --- a/crates/erc20_payment_lib/src/setup.rs +++ b/crates/erc20_payment_lib/src/setup.rs @@ -38,10 +38,10 @@ pub struct ChainSetup { pub transaction_timeout: u64, pub skip_multi_contract_check: bool, pub confirmation_blocks: u64, - pub allow_max_fee_greater_than_priority_fee: Option, pub faucet_eth_amount: Option, pub faucet_glm_amount: Option, pub block_explorer_url: Option, + pub replacement_timeout: Option, } #[derive(Serialize, Clone, Debug)] @@ -156,11 +156,9 @@ impl PaymentSetup { currency_gas_symbol: chain_config.1.currency_symbol.clone(), faucet_eth_amount, faucet_glm_amount, - allow_max_fee_greater_than_priority_fee: chain_config - .1 - .allow_max_fee_greater_than_priority_fee, block_explorer_url: chain_config.1.block_explorer_url.clone(), chain_id: chain_config.1.chain_id, + replacement_timeout: chain_config.1.replacement_timeout, }, ); } diff --git a/crates/erc20_payment_lib/src/transaction.rs b/crates/erc20_payment_lib/src/transaction.rs index 485e26d3..f1376a5f 100644 --- a/crates/erc20_payment_lib/src/transaction.rs +++ b/crates/erc20_payment_lib/src/transaction.rs @@ -537,7 +537,7 @@ pub async fn find_tx(web3: &Web3, web3_tx_dao: &mut TxDao) -> Result, web3_tx_dao: &mut TxDao, -) -> Result { +) -> Result, PaymentError> { if let Some(tx_hash) = web3_tx_dao.tx_hash.as_ref() { let tx_hash = web3::types::H256::from_str(tx_hash) .map_err(|_err| ConversionError::from("Cannot parse tx_hash".to_string())) @@ -558,12 +558,12 @@ pub async fn find_receipt( .effective_gas_price .ok_or_else(|| err_custom_create!("Effective gas price expected"))?; web3_tx_dao.fee_paid = Some((gas_used * effective_gas_price).to_string()); - Ok(true) + Ok(Some(effective_gas_price)) } else { web3_tx_dao.block_number = None; web3_tx_dao.chain_status = None; web3_tx_dao.fee_paid = None; - Ok(false) + Ok(None) } } else { Err(err_custom_create!("No tx hash")) diff --git a/crates/erc20_payment_lib/src/utils.rs b/crates/erc20_payment_lib/src/utils.rs index f7652d6a..34784fa0 100644 --- a/crates/erc20_payment_lib/src/utils.rs +++ b/crates/erc20_payment_lib/src/utils.rs @@ -80,7 +80,7 @@ fn compute_base(num_decimals: u32) -> rust_decimal::Decimal { } ///good from one gwei up to at least one billion ethers -pub fn rust_dec_to_u256( +fn rust_dec_to_u256( dec_amount: rust_decimal::Decimal, decimals: Option, ) -> Result { @@ -113,7 +113,7 @@ pub fn rust_dec_to_u256( Ok(U256::from(u128)) } -pub fn u256_to_rust_dec( +fn u256_to_rust_dec( amount: U256, decimals: Option, ) -> Result { @@ -136,6 +136,75 @@ pub fn u256_to_rust_dec( Ok(Decimal::from(amount.as_u128()) / dec_base) } +fn u256_to_gwei(amount: U256) -> Result { + u256_to_rust_dec(amount, Some(9)) +} + +pub trait U256ConvExt { + fn to_gwei(&self) -> Result; + fn to_eth(&self) -> Result; +} +impl U256ConvExt for U256 { + fn to_gwei(&self) -> Result { + u256_to_gwei(*self) + } + fn to_eth(&self) -> Result { + u256_to_eth(*self) + } +} + +pub trait StringConvExt { + fn to_gwei(&self) -> Result; + fn to_eth(&self) -> Result; + fn to_u256(&self) -> Result; +} +impl StringConvExt for String { + fn to_gwei(&self) -> Result { + self.to_u256()?.to_gwei() + } + fn to_eth(&self) -> Result { + self.to_u256()?.to_eth() + } + + fn to_u256(&self) -> Result { + U256::from_dec_str(self).map_err(|err| { + ConversionError::from(format!("Invalid string when converting: {err:?}")) + }) + } +} + +pub trait DecimalConvExt { + fn to_u256_from_gwei(&self) -> Result; + fn to_u256_from_eth(&self) -> Result; +} + +impl DecimalConvExt for Decimal { + fn to_u256_from_gwei(&self) -> Result { + rust_dec_to_u256(*self, Some(9)) + } + fn to_u256_from_eth(&self) -> Result { + rust_dec_to_u256(*self, Some(18)) + } +} + +fn u256_to_eth(amount: U256) -> Result { + u256_to_rust_dec(amount, Some(18)) +} + +pub fn u256_eth_from_str(val: &str) -> Result<(U256, Decimal), ConversionError> { + let u256 = U256::from_dec_str(val) + .map_err(|err| ConversionError::from(format!("Invalid string when converting: {err:?}")))?; + let eth = u256_to_eth(u256)?; + Ok((u256, eth)) +} + +pub fn u256_gwei_from_str(val: &str) -> Result<(U256, Decimal), ConversionError> { + let u256 = U256::from_dec_str(val) + .map_err(|err| ConversionError::from(format!("Invalid string when converting: {err:?}")))?; + let gwei = u256_to_gwei(u256)?; + Ok((u256, gwei)) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/erc20_payment_lib_extra/src/account_balance.rs b/crates/erc20_payment_lib_extra/src/account_balance.rs index a22150ee..dd9c17b0 100644 --- a/crates/erc20_payment_lib_extra/src/account_balance.rs +++ b/crates/erc20_payment_lib_extra/src/account_balance.rs @@ -1,7 +1,7 @@ use erc20_payment_lib::error::PaymentError; use erc20_payment_lib::eth::get_balance; use erc20_payment_lib::setup::PaymentSetup; -use erc20_payment_lib::utils::u256_to_rust_dec; +use erc20_payment_lib::utils::U256ConvExt; use erc20_payment_lib::{config, err_custom_create}; use futures_util::{stream, StreamExt}; use serde::{Deserialize, Serialize}; @@ -124,10 +124,10 @@ pub async fn account_balance( log::debug!("{:#x} token: {:?}", job, token_balance); let gas_balance_decimal = balance .gas_balance - .map(|v| u256_to_rust_dec(v, None).unwrap_or_default().to_string()); + .map(|v| v.to_eth().unwrap_or_default().to_string()); let token_balance_decimal = balance .token_balance - .map(|v| u256_to_rust_dec(v, None).unwrap_or_default().to_string()); + .map(|v| v.to_eth().unwrap_or_default().to_string()); let gas_balance_human = gas_balance_decimal.clone().map(|v| { format!( "{:.03} {}", diff --git a/crates/erc20_payment_lib_test/src/config_setup.rs b/crates/erc20_payment_lib_test/src/config_setup.rs index 68ebc9af..6c990976 100644 --- a/crates/erc20_payment_lib_test/src/config_setup.rs +++ b/crates/erc20_payment_lib_test/src/config_setup.rs @@ -29,10 +29,10 @@ pub async fn create_default_config_setup(proxy_url_base: &str, proxy_key: &str) }), transaction_timeout: 25, confirmation_blocks: 1, - allow_max_fee_greater_than_priority_fee: Some(false), faucet_eth_amount: Some(10.0), faucet_glm_amount: Some(20.0), block_explorer_url: Some("http://127.0.0.1:4000".to_string()), + replacement_timeout: Some(1.0), }; let mut chain_map = BTreeMap::new(); chain_map.insert("dev".to_string(), chain); diff --git a/crates/erc20_payment_lib_test/src/durabily2.rs b/crates/erc20_payment_lib_test/src/durabily2.rs index 856812c3..78122572 100644 --- a/crates/erc20_payment_lib_test/src/durabily2.rs +++ b/crates/erc20_payment_lib_test/src/durabily2.rs @@ -9,7 +9,7 @@ use erc20_payment_lib::misc::load_private_keys; use erc20_payment_lib::runtime::DriverEventContent::*; use erc20_payment_lib::runtime::{DriverEvent, PaymentRuntime}; use erc20_payment_lib::signer::PrivateKeySigner; -use erc20_payment_lib::utils::u256_to_rust_dec; +use erc20_payment_lib::utils::U256ConvExt; use erc20_payment_lib_extra::{generate_test_payments, GenerateOptions}; use std::env; use std::path::Path; @@ -151,12 +151,12 @@ pub async fn test_durability2(generate_count: u64, gen_interval_secs: f64, trans { // *** RESULT CHECK *** let (fee_paid_events, fee_paid_events_approve) = receiver_loop.await.unwrap(); - log::info!("fee paid from events: {}", u256_to_rust_dec(fee_paid_events, None).unwrap()); + log::info!("fee paid from events: {}", fee_paid_events.to_eth().unwrap()); let transfer_stats = get_transfer_stats(&conn, chain_id, None).await.unwrap(); let stats_all = transfer_stats.per_sender.iter().next().unwrap().1.all.clone(); let fee_paid_stats = stats_all.fee_paid; - log::info!("fee paid from stats: {}", u256_to_rust_dec(fee_paid_stats, None).unwrap()); + log::info!("fee paid from stats: {}", fee_paid_stats.to_eth().unwrap()); assert_eq!(fee_paid_events, fee_paid_stats); diff --git a/crates/erc20_payment_lib_test/src/multi_erc20_transfer.rs b/crates/erc20_payment_lib_test/src/multi_erc20_transfer.rs index 1cdf14f8..104777bb 100644 --- a/crates/erc20_payment_lib_test/src/multi_erc20_transfer.rs +++ b/crates/erc20_payment_lib_test/src/multi_erc20_transfer.rs @@ -9,7 +9,7 @@ use erc20_payment_lib::misc::load_private_keys; use erc20_payment_lib::runtime::DriverEventContent::*; use erc20_payment_lib::runtime::{DriverEvent, PaymentRuntime}; use erc20_payment_lib::signer::PrivateKeySigner; -use erc20_payment_lib::utils::u256_to_rust_dec; +use erc20_payment_lib::utils::U256ConvExt; use erc20_payment_lib_extra::{generate_test_payments, GenerateOptions}; use std::env; use std::path::Path; @@ -152,12 +152,12 @@ pub async fn test_durability(generate_count: u64, gen_interval_secs: f64, transf { // *** RESULT CHECK *** let (fee_paid_events, fee_paid_events_approve) = receiver_loop.await.unwrap(); - log::info!("fee paid from events: {}", u256_to_rust_dec(fee_paid_events, None).unwrap()); + log::info!("fee paid from events: {}", fee_paid_events.to_eth().unwrap()); let transfer_stats = get_transfer_stats(&conn, chain_id, None).await.unwrap(); let stats_all = transfer_stats.per_sender.iter().next().unwrap().1.all.clone(); let fee_paid_stats = stats_all.fee_paid; - log::info!("fee paid from stats: {}", u256_to_rust_dec(fee_paid_stats, None).unwrap()); + log::info!("fee paid from stats: {}", fee_paid_stats.to_eth().unwrap()); assert_eq!(fee_paid_events, fee_paid_stats); diff --git a/crates/web3_test_proxy_client/src/list_txs.rs b/crates/web3_test_proxy_client/src/list_txs.rs index cc8d7a6d..12027a91 100644 --- a/crates/web3_test_proxy_client/src/list_txs.rs +++ b/crates/web3_test_proxy_client/src/list_txs.rs @@ -1,5 +1,5 @@ use crate::{get_calls, JSONRPCResult}; -use erc20_payment_lib::utils::u256_to_rust_dec; +use erc20_payment_lib::utils::U256ConvExt; use web3::types::U256; /// List transactions captured by web3 proxy in human readable format @@ -71,9 +71,7 @@ pub async fn list_transactions_human(proxy_url_base: &str, proxy_key: &str) -> V let result = if let Some(result_int) = result_int { result_int.to_string() } else if let Some(result_balance) = result_balance { - u256_to_rust_dec(result_balance, Some(18)) - .unwrap() - .to_string() + result_balance.to_eth().unwrap().to_string() } else if c.method == "eth_getTransactionReceipt" { "details?".to_string() } else if call.status_code != 200 { diff --git a/junk/generate_command.sh b/junk/generate_command.sh new file mode 100644 index 00000000..abdc8822 --- /dev/null +++ b/junk/generate_command.sh @@ -0,0 +1 @@ +cargo run -- generate -a -c polygon -n 10 --receivers-random-pool 100000 diff --git a/src/main.rs b/src/main.rs index 2e27f06d..8be2955a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,6 @@ use erc20_payment_lib::db::ops::{ }; use erc20_payment_lib::server::*; use erc20_payment_lib::signer::PrivateKeySigner; -use erc20_payment_lib::utils::rust_dec_to_u256; use erc20_payment_lib::{ config, err_custom_create, err_from, @@ -24,13 +23,14 @@ use erc20_payment_lib::{ use std::env; use std::str::FromStr; -use crate::stats::run_stats; +use crate::stats::{export_stats, run_stats}; use erc20_payment_lib::runtime::remove_last_unsent_transactions; use erc20_payment_lib::service::transaction_from_chain_and_into_db; use erc20_payment_lib::setup::PaymentSetup; use erc20_payment_lib::transaction::import_erc20_txs; use erc20_payment_lib_extra::{account_balance, generate_test_payments}; +use erc20_payment_lib::utils::DecimalConvExt; use std::sync::Arc; use structopt::StructOpt; use tokio::sync::Mutex; @@ -229,7 +229,9 @@ async fn main_internal() -> Result<(), PaymentError> { receiver_addr: format!("{:#x}", single_transfer_options.recipient), chain_id: chain_cfg.chain_id, token_addr: token, - token_amount: rust_dec_to_u256(single_transfer_options.amount, Some(18)) + token_amount: single_transfer_options + .amount + .to_u256_from_eth() .unwrap() .to_string(), create_date: Default::default(), @@ -278,6 +280,9 @@ async fn main_internal() -> Result<(), PaymentError> { generate_test_payments(generate_options, &config, public_addrs, Some(conn.clone())) .await?; } + PaymentCommands::ExportHistory { + export_history_stats_options, + } => export_stats(conn.clone(), export_history_stats_options, &config).await?, PaymentCommands::PaymentStats { payment_stats_options, } => run_stats(conn.clone(), payment_stats_options, &config).await?, @@ -331,10 +336,21 @@ async fn main_internal() -> Result<(), PaymentError> { .as_u64() as i64; //start around 30 days ago - let mut start_block = std::cmp::max( - 1, - current_block - scan_blockchain_options.from_blocks_ago as i64, - ); + let mut start_block = std::cmp::max(1, scan_blockchain_options.from_block as i64); + + if scan_blockchain_options.from_block > current_block as u64 { + log::warn!( + "From block {} is higher than current block {}, no newer data on blockchain", + scan_blockchain_options.from_block, + current_block + ); + return Ok(()); + } + + if current_block < scan_info.last_block { + log::warn!("Current block {} is lower than last block from db {}, no newer data on blockchain", current_block, scan_info.last_block); + return Ok(()); + } if scan_info.last_block > start_block { log::info!("Start block from db is higher than start block from cli {}, using start block from db {}", start_block, scan_info.last_block); diff --git a/src/options.rs b/src/options.rs index a6bf9726..f0cb3801 100644 --- a/src/options.rs +++ b/src/options.rs @@ -113,8 +113,8 @@ pub struct ScanBlockchainOptions { #[structopt(short = "c", long = "chain-name", default_value = "polygon")] pub chain_name: String, - #[structopt(short = "b", long = "blocks-ago", default_value = "43200")] - pub from_blocks_ago: u64, + #[structopt(short = "b", long = "from-block")] + pub from_block: u64, #[structopt(long = "start-new-scan")] pub start_new_scan: bool, @@ -146,6 +146,13 @@ pub struct ScanBlockchainOptions { pub sender: String, } +#[derive(StructOpt)] +#[structopt(about = "Export history stats")] +pub struct ExportHistoryStatsOptions { + #[structopt(short = "c", long = "chain-name", default_value = "polygon")] + pub chain_name: String, +} + #[derive(StructOpt)] #[structopt(about = "Payment statistics options")] pub struct PaymentStatsOptions { @@ -235,6 +242,10 @@ pub enum PaymentCommands { #[structopt(flatten)] payment_stats_options: PaymentStatsOptions, }, + ExportHistory { + #[structopt(flatten)] + export_history_stats_options: ExportHistoryStatsOptions, + }, DecryptKeyStore { #[structopt(flatten)] decrypt_options: DecryptKeyStoreOptions, diff --git a/src/stats.rs b/src/stats.rs index 81cdde05..9d550023 100644 --- a/src/stats.rs +++ b/src/stats.rs @@ -1,15 +1,95 @@ +use csv::WriterBuilder; use erc20_payment_lib::config::Config; +use erc20_payment_lib::db::model::ChainTxDao; use erc20_payment_lib::db::ops::{ - get_transfer_stats, get_transfer_stats_from_blockchain, TransferStatsPart, + get_chain_transfers_by_chain_id, get_chain_txs_by_chain_id, get_transfer_stats, + get_transfer_stats_from_blockchain, TransferStatsPart, }; use erc20_payment_lib::err_custom_create; +use itertools::Itertools; +use rust_decimal::Decimal; use sqlx::SqlitePool; +use std::collections::HashMap; use std::fs; use web3::types::{H160, U256}; -use crate::options::PaymentStatsOptions; +use crate::options::{ExportHistoryStatsOptions, PaymentStatsOptions}; use erc20_payment_lib::error::PaymentError; -use erc20_payment_lib::utils::u256_to_rust_dec; +use erc20_payment_lib::utils::{u256_eth_from_str, U256ConvExt}; + +pub async fn export_stats( + conn: SqlitePool, + payment_stats_options: ExportHistoryStatsOptions, + config: &Config, +) -> Result<(), PaymentError> { + let chain_cfg = + config + .chain + .get(&payment_stats_options.chain_name) + .ok_or(err_custom_create!( + "Chain {} not found in config file", + payment_stats_options.chain_name + ))?; + + let mut writer = WriterBuilder::new() + .delimiter(b';') + .from_writer(std::fs::File::create("export.csv").unwrap()); + + let tchains = get_chain_transfers_by_chain_id(&conn, chain_cfg.chain_id, None) + .await + .unwrap(); + + let txs = get_chain_txs_by_chain_id(&conn, chain_cfg.chain_id, None) + .await + .unwrap(); + + let _tx_by_id = txs + .clone() + .into_iter() + .map(|tx| (tx.id, tx)) + .collect::>(); + + let mut transaction_ids = HashMap::>::new(); + for tchain in tchains { + transaction_ids + .entry(tchain.chain_tx_id) + .or_default() + .push(tchain.id); + } + + let txs = txs + .clone() + .into_iter() + .sorted_by_key(|t| t.blockchain_date) + .collect_vec(); + + writer + .write_record([ + "time", + "fee_paid", + "fee_paid_total", + "tx_count", + "payment_count", + ]) + .unwrap(); + let mut fee_paid_total = Decimal::default(); + for tx in txs { + let (_, fee_paid) = u256_eth_from_str(&tx.fee_paid).unwrap(); + fee_paid_total += fee_paid; + writer + .write_record([ + tx.blockchain_date + .format("%Y-%m-%dT%H:%M:%S%.3f") + .to_string(), + format!("{:.6}", fee_paid), + format!("{:.6}", fee_paid_total), + 1.to_string(), + transaction_ids.get(&tx.id).unwrap().len().to_string(), + ]) + .unwrap(); + } + Ok(()) +} pub async fn run_stats( conn: SqlitePool, @@ -48,10 +128,7 @@ pub async fn run_stats( let main_sender = transfer_stats.per_sender.iter().next().unwrap(); let stats_all = main_sender.1.all.clone(); let fee_paid_stats = stats_all.fee_paid; - println!( - "fee paid from stats: {}", - u256_to_rust_dec(fee_paid_stats, None).unwrap() - ); + println!("fee paid from stats: {}", fee_paid_stats.to_eth().unwrap()); println!("Number of transfers done: {}", stats_all.done_count); @@ -82,7 +159,7 @@ pub async fn run_stats( "# TYPE erc20_transferred counter", chain_cfg.chain_id, sender, - u256_to_rust_dec(token_transferred.unwrap_or(U256::zero()), None).unwrap(), + token_transferred.unwrap_or_default().to_eth().unwrap(), ); metrics += &format!( @@ -109,7 +186,7 @@ pub async fn run_stats( "# TYPE fee_paid counter", chain_cfg.chain_id, sender, - u256_to_rust_dec(stats.all.fee_paid, None).unwrap_or_default(), + stats.all.fee_paid.to_eth().unwrap_or_default(), ); } @@ -166,7 +243,7 @@ pub async fn run_stats( println!( "Native token sent: {}", - u256_to_rust_dec(main_sender.1.all.native_token_transferred, None).unwrap() + main_sender.1.all.native_token_transferred.to_eth().unwrap() ); let token_transferred = main_sender .1 @@ -176,7 +253,7 @@ pub async fn run_stats( .copied(); println!( "Erc20 token sent: {}", - u256_to_rust_dec(token_transferred.unwrap_or(U256::zero()), None).unwrap() + token_transferred.unwrap_or_default().to_eth().unwrap() ); let per_receiver = main_sender.1.per_receiver.clone(); @@ -231,13 +308,9 @@ pub async fn run_stats( receiver.0, receiver.1.done_count, receiver.1.transaction_ids.len(), - u256_to_rust_dec(receiver.1.fee_paid, None).unwrap(), - u256_to_rust_dec(receiver.1.native_token_transferred, None).unwrap(), - u256_to_rust_dec( - ts, - None - ) - .unwrap(), + receiver.1.fee_paid.to_eth().unwrap(), + receiver.1.native_token_transferred.to_eth().unwrap(), + ts.to_eth().unwrap(), ); println!( " First transfer requested at {}", diff --git a/tests/docker_01_basic/single_erc20_transfer.rs b/tests/docker_01_basic/single_erc20_transfer.rs index ea83dbfa..7a261f30 100644 --- a/tests/docker_01_basic/single_erc20_transfer.rs +++ b/tests/docker_01_basic/single_erc20_transfer.rs @@ -6,7 +6,7 @@ use erc20_payment_lib::runtime::DriverEventContent::*; use erc20_payment_lib::runtime::{verify_transaction, DriverEvent, PaymentRuntime}; use erc20_payment_lib::signer::PrivateKeySigner; use erc20_payment_lib::transaction::create_token_transfer; -use erc20_payment_lib::utils::u256_to_rust_dec; +use erc20_payment_lib::utils::U256ConvExt; use erc20_payment_lib_test::*; use rust_decimal::prelude::ToPrimitive; use std::str::FromStr; @@ -105,7 +105,7 @@ async fn test_erc20_transfer() -> Result<(), anyhow::Error> { { // *** RESULT CHECK *** let (fee_paid_u256, tx_dao) = receiver_loop.await.unwrap(); - let fee_paid = u256_to_rust_dec(fee_paid_u256,None).unwrap(); + let fee_paid = fee_paid_u256.to_eth().unwrap(); log::info!("fee paid: {}", fee_paid); assert!(fee_paid.to_f64().unwrap() > 0.00008 && fee_paid.to_f64().unwrap() < 0.00015); diff --git a/tests/docker_01_basic/single_gas_transfer.rs b/tests/docker_01_basic/single_gas_transfer.rs index a8cf3560..f5baa830 100644 --- a/tests/docker_01_basic/single_gas_transfer.rs +++ b/tests/docker_01_basic/single_gas_transfer.rs @@ -5,7 +5,7 @@ use erc20_payment_lib::runtime::DriverEventContent::*; use erc20_payment_lib::runtime::{DriverEvent, PaymentRuntime}; use erc20_payment_lib::signer::PrivateKeySigner; use erc20_payment_lib::transaction::create_token_transfer; -use erc20_payment_lib::utils::u256_to_rust_dec; +use erc20_payment_lib::utils::U256ConvExt; use erc20_payment_lib_test::*; use rust_decimal::prelude::ToPrimitive; use std::str::FromStr; @@ -94,7 +94,7 @@ async fn test_gas_transfer() -> Result<(), anyhow::Error> { { // *** RESULT CHECK *** let fee_paid_u256 = receiver_loop.await.unwrap(); - let fee_paid = u256_to_rust_dec(fee_paid_u256,None).unwrap(); + let fee_paid = fee_paid_u256.to_eth().unwrap(); log::info!("fee paid: {}", fee_paid); assert!(fee_paid.to_f64().unwrap() > 0.00002 && fee_paid.to_f64().unwrap() < 0.00003); let res = test_get_balance(&proxy_url_base, "0x653b48e1348f480149047aa3a58536eb0dbbb2e2,0x41162e565ebbf1a52ec904c7365e239c40d82568").await?; diff --git a/tests/docker_02_errors/wrong_chain_id.rs b/tests/docker_02_errors/wrong_chain_id.rs index 3381b7ea..44194e7f 100644 --- a/tests/docker_02_errors/wrong_chain_id.rs +++ b/tests/docker_02_errors/wrong_chain_id.rs @@ -5,12 +5,11 @@ use erc20_payment_lib::runtime::DriverEventContent::*; use erc20_payment_lib::runtime::{DriverEvent, PaymentRuntime, TransactionFailedReason}; use erc20_payment_lib::signer::PrivateKeySigner; use erc20_payment_lib::transaction::create_token_transfer; -use erc20_payment_lib::utils::u256_to_rust_dec; +use erc20_payment_lib::utils::U256ConvExt; use erc20_payment_lib_test::*; use std::str::FromStr; use std::time::Duration; use web3::types::{Address, U256}; - #[tokio::test(flavor = "multi_thread")] #[rustfmt::skip] async fn test_wrong_chain_id() -> Result<(), anyhow::Error> { @@ -113,7 +112,7 @@ async fn test_wrong_chain_id() -> Result<(), anyhow::Error> { // *** RESULT CHECK *** let fee_paid = receiver_loop.await.unwrap(); assert_eq!(fee_paid, U256::zero()); - log::info!("fee paid: {}", u256_to_rust_dec(fee_paid, None).unwrap()); + log::info!("fee paid: {}", fee_paid.to_eth().unwrap()); let res = test_get_balance(&proxy_url_base, "0x653b48E1348F480149047AA3a58536eb0dbBB2E2,0x41162E565ebBF1A52eC904c7365E239c40d82568").await?; diff --git a/tests/docker_03_problems/single_transfer_with_problems.rs b/tests/docker_03_problems/single_transfer_with_problems.rs index ff9a68b3..c451a4cf 100644 --- a/tests/docker_03_problems/single_transfer_with_problems.rs +++ b/tests/docker_03_problems/single_transfer_with_problems.rs @@ -6,14 +6,13 @@ use erc20_payment_lib::runtime::DriverEventContent::*; use erc20_payment_lib::runtime::{DriverEvent, PaymentRuntime, TransactionStuckReason}; use erc20_payment_lib::signer::PrivateKeySigner; use erc20_payment_lib::transaction::create_token_transfer; -use erc20_payment_lib::utils::u256_to_rust_dec; +use erc20_payment_lib::utils::U256ConvExt; use erc20_payment_lib_test::*; use std::str::FromStr; use std::time::Duration; use tokio::task; use web3::types::{Address, U256}; use web3_test_proxy_client::{list_transactions_human, EndpointSimulateProblems}; - #[rustfmt::skip] async fn test_gas_transfer(error_probability: f64) -> Result<(), anyhow::Error> { // *** TEST SETUP *** @@ -151,7 +150,7 @@ async fn test_gas_transfer(error_probability: f64) -> Result<(), anyhow::Error> { // *** RESULT CHECK *** let fee_paid = receiver_loop.await.unwrap(); - log::info!("fee paid: {}", u256_to_rust_dec(fee_paid, None).unwrap()); + log::info!("fee paid: {}", fee_paid.to_eth().unwrap()); let res = test_get_balance(&proxy_url_base, "0x653b48e1348f480149047aa3a58536eb0dbbb2e2,0x41162e565ebbf1a52ec904c7365e239c40d82568").await?; assert_eq!(res["0x41162e565ebbf1a52ec904c7365e239c40d82568"].gas_decimal, Some("0.456000000000000222".to_string())); assert_eq!(res["0x41162e565ebbf1a52ec904c7365e239c40d82568"].token_decimal, Some("0".to_string())); diff --git a/tests/docker_04_multi.rs b/tests/docker_04_multi.rs index 3aabc9a3..af616e37 100644 --- a/tests/docker_04_multi.rs +++ b/tests/docker_04_multi.rs @@ -6,7 +6,7 @@ use erc20_payment_lib::runtime::DriverEventContent::*; use erc20_payment_lib::runtime::{verify_transaction, DriverEvent, PaymentRuntime}; use erc20_payment_lib::signer::PrivateKeySigner; use erc20_payment_lib::transaction::create_token_transfer; -use erc20_payment_lib::utils::u256_to_rust_dec; +use erc20_payment_lib::utils::U256ConvExt; use erc20_payment_lib_test::*; use std::str::FromStr; use std::time::Duration; @@ -150,7 +150,7 @@ async fn test_multi_erc20_transfer(payment_count: usize, use_direct_method: bool { // *** RESULT CHECK *** let (fee_paid, tx_dao) = receiver_loop.await.unwrap(); - let fee_paid_decimal = u256_to_rust_dec(fee_paid, None).unwrap(); + let fee_paid_decimal = fee_paid.to_eth().unwrap(); log::info!("fee paid: {fee_paid_decimal}"); //intersperse is joining strings with separator