From d7979da6488a1c00556a3b417ccb6df8aa8fab72 Mon Sep 17 00:00:00 2001 From: Max Kalashnikoff Date: Thu, 31 Oct 2024 22:18:16 +0100 Subject: [PATCH] fix: fixing nonce for the initial tx and gas price --- integration/chain_orchestrator.test.ts | 9 ++- src/handlers/chain_agnostic/route.rs | 52 ++++++++++---- src/utils/crypto.rs | 99 ++++++++++++++++++-------- 3 files changed, 116 insertions(+), 44 deletions(-) diff --git a/integration/chain_orchestrator.test.ts b/integration/chain_orchestrator.test.ts index 1aedf601..34ecddec 100644 --- a/integration/chain_orchestrator.test.ts +++ b/integration/chain_orchestrator.test.ts @@ -17,6 +17,8 @@ describe('Chain abstraction orchestrator', () => { const amount_to_send = 3_000_000 // How much needs to be topped up const amount_to_topup = amount_to_send - usdc_funds_on_optimism + // Default gas esimation is default with 4x increase + const gas_estimate = "0xa69ac"; const receiver_address = "0x739ff389c8eBd9339E69611d46Eec6212179BB67"; const chain_id_optimism = "eip155:10"; @@ -188,14 +190,19 @@ describe('Chain abstraction orchestrator', () => { // First transaction expected to be the approval transaction const approvalTransaction = data.transactions[0] expect(approvalTransaction.chainId).toBe(chain_id_base) + expect(approvalTransaction.nonce).not.toBe("0x00") + expect(approvalTransaction.gas).toBe(gas_estimate) const decodedData = erc20Interface.decodeFunctionData('approve', approvalTransaction.data); expect(decodedData.amount.toString()).toBe(amount_to_topup.toString()) + // Second transaction expected to be the bridging to the Base expect(data.transactions[1].chainId).toBe(chain_id_base) + expect(data.transactions[1].nonce).not.toBe("0x00") + expect(data.transactions[1].gas).toBe(gas_estimate) // Last transaction expected to be the initial one - expect(data.transactions[2]).toStrictEqual(transactionObj.transaction) + expect(data.transactions[2].data).toBe(transactionObj.transaction.data) // Set the Orchestration ID for the next test orchestration_id = data.orchestrationId; diff --git a/src/handlers/chain_agnostic/route.rs b/src/handlers/chain_agnostic/route.rs index de93a407..1057f60a 100644 --- a/src/handlers/chain_agnostic/route.rs +++ b/src/handlers/chain_agnostic/route.rs @@ -10,7 +10,7 @@ use { storage::irn::OperationType, utils::crypto::{ convert_alloy_address_to_h160, decode_erc20_function_type, decode_erc20_transfer_data, - get_balance, get_erc20_balance, Erc20FunctionType, + get_balance, get_erc20_balance, get_gas_price, get_nonce, Erc20FunctionType, }, }, alloy::primitives::{Address, U256}, @@ -133,7 +133,7 @@ async fn handler_internal( &request_payload.transaction.chain_id, convert_alloy_address_to_h160(to_address), convert_alloy_address_to_h160(from_address), - &query_params.project_id, + &query_params.project_id.clone(), MessageSource::ChainAgnosticCheck, ) .await?; @@ -144,9 +144,12 @@ async fn handler_internal( let erc20_topup_value = erc20_transfer_value - erc20_balance; // Check for possible bridging by iterating over supported assets - if let Some((bridge_chain_id, bridge_contract)) = - check_bridging_for_erc20_transfer(query_params.project_id, erc20_topup_value, from_address) - .await? + if let Some((bridge_chain_id, bridge_contract)) = check_bridging_for_erc20_transfer( + query_params.project_id.clone(), + erc20_topup_value, + from_address, + ) + .await? { // Skip bridging if that's the same chainId and contract address if bridge_chain_id == request_payload.transaction.chain_id && bridge_contract == to_address @@ -168,6 +171,26 @@ async fn handler_internal( ) .await?; + // Getting the current nonce for the address + let mut current_nonce = get_nonce( + &request_payload.transaction.chain_id.clone(), + from_address, + &query_params.project_id.clone(), + MessageSource::ChainAgnosticCheck, + ) + .await?; + + // Getting the current gas price + let gas_price = get_gas_price( + &bridge_chain_id.clone(), + &query_params.project_id.clone(), + MessageSource::ChainAgnosticCheck, + ) + .await?; + // Default gas estimate + // Using default with 4x increase: '0x029a6b * 4 = 0x52d9ac' + let gas = 0x029a6b * 0x4; + // Build bridging transaction let mut routes = Vec::new(); let best_route = quotes.first().ok_or(RpcError::NoBridgingRoutesAvailable)?; @@ -208,10 +231,10 @@ async fn handler_internal( from: approval_tx.from, to: approval_tx.to, value: "0x00".to_string(), - gas_price: request_payload.transaction.gas_price.clone(), - gas: request_payload.transaction.gas.clone(), + gas_price: format!("0x{:x}", gas_price), + gas: format!("0x{:x}", gas), data: approval_tx.data, - nonce: request_payload.transaction.nonce.clone(), + nonce: format!("0x{:x}", current_nonce), max_fee_per_gas: request_payload.transaction.max_fee_per_gas.clone(), max_priority_fee_per_gas: request_payload .transaction @@ -219,6 +242,7 @@ async fn handler_internal( .clone(), chain_id: bridge_chain_id.clone(), }); + current_nonce += 1; } } @@ -227,17 +251,21 @@ async fn handler_internal( from: from_address, to: bridge_tx.tx_target, value: bridge_tx.value, - gas_price: request_payload.transaction.gas_price.clone(), - gas: request_payload.transaction.gas.clone(), + gas_price: format!("0x{:x}", gas_price), + gas: format!("0x{:x}", gas), data: bridge_tx.tx_data, - nonce: request_payload.transaction.nonce.clone(), + nonce: format!("0x{:x}", current_nonce), max_fee_per_gas: request_payload.transaction.max_fee_per_gas.clone(), max_priority_fee_per_gas: request_payload.transaction.max_priority_fee_per_gas.clone(), chain_id: format!("eip155:{}", bridge_tx.chain_id), }); + current_nonce += 1; // Push initial transaction last after all bridging transactions - routes.push(request_payload.transaction.clone()); + routes.push(Transaction { + nonce: format!("0x{:x}", current_nonce), + ..request_payload.transaction.clone() + }); let orchestration_id = Uuid::new_v4().to_string(); // Save the bridging transaction to the IRN diff --git a/src/utils/crypto.rs b/src/utils/crypto.rs index cfd33744..e52559e6 100644 --- a/src/utils/crypto.rs +++ b/src/utils/crypto.rs @@ -1,7 +1,9 @@ use { crate::{analytics::MessageSource, error::RpcError}, alloy::{ + network::Ethereum, primitives::{Address, U256 as AlloyU256}, + providers::{Provider as AlloyProvider, ReqwestProvider}, rpc::json_rpc::Id, sol, sol_types::SolCall, @@ -336,6 +338,25 @@ pub async fn verify_message_signature( .await } +/// Construct RPC calls url +fn get_rpc_url( + chain_id: &str, + rpc_project_id: &str, + source: MessageSource, +) -> Result { + let mut provider = Url::parse("https://rpc.walletconnect.com/v1").map_err(|e| { + CryptoUitlsError::RpcUrlParseError(format!("Failed to parse RPC url: {}", e)) + })?; + provider.query_pairs_mut().append_pair("chainId", chain_id); + provider + .query_pairs_mut() + .append_pair("projectId", rpc_project_id); + provider + .query_pairs_mut() + .append_pair("source", &source.to_string()); + Ok(provider) +} + /// Veryfy message signature for eip6492 contract #[tracing::instrument(level = "debug")] pub async fn verify_eip6492_message_signature( @@ -350,23 +371,7 @@ pub async fn verify_eip6492_message_signature( let address = Address::parse_checksummed(address, None) .map_err(|_| CryptoUitlsError::AddressChecksum(address.into()))?; - let mut provider = Url::parse("https://rpc.walletconnect.com/v1") - .map_err(|e| { - CryptoUitlsError::RpcUrlParseError(format!( - "Failed to parse RPC url: - {}", - e - )) - }) - .unwrap(); - provider.query_pairs_mut().append_pair("chainId", chain_id); - provider - .query_pairs_mut() - .append_pair("projectId", rpc_project_id); - provider - .query_pairs_mut() - .append_pair("source", &source.to_string()); - + let provider = get_rpc_url(chain_id, rpc_project_id, source)?; let hexed_signature = hex::decode(&signature[2..]) .map_err(|e| CryptoUitlsError::SignatureFormat(format!("Wrong signature format: {}", e)))?; @@ -446,11 +451,11 @@ pub async fn get_erc20_contract_balance( ]"#, ); - let provider = Provider::::try_from(format!( - "https://rpc.walletconnect.com/v1?chainId={}&projectId={}&source={}", - chain_id, rpc_project_id, source - )) - .map_err(|e| CryptoUitlsError::RpcUrlParseError(format!("Failed to parse RPC url: {}", e)))?; + let provider = + Provider::::try_from(get_rpc_url(chain_id, rpc_project_id, source)?.as_str()) + .map_err(|e| { + CryptoUitlsError::RpcUrlParseError(format!("Failed to parse RPC url: {}", e)) + })?; let provider = Arc::new(provider); let contract = ERC20Contract::new(contract, provider); @@ -471,11 +476,11 @@ pub async fn get_balance( rpc_project_id: &str, source: MessageSource, ) -> Result { - let provider = Provider::::try_from(format!( - "https://rpc.walletconnect.com/v1?chainId={}&projectId={}&source={}", - chain_id, rpc_project_id, source - )) - .map_err(|e| CryptoUitlsError::RpcUrlParseError(format!("Failed to parse RPC url: {}", e)))?; + let provider = + Provider::::try_from(get_rpc_url(chain_id, rpc_project_id, source)?.as_str()) + .map_err(|e| { + CryptoUitlsError::RpcUrlParseError(format!("Failed to parse RPC url: {}", e)) + })?; let provider = Arc::new(provider); let balance = provider @@ -485,6 +490,39 @@ pub async fn get_balance( Ok(balance) } +/// Get the gas price +#[tracing::instrument(level = "debug")] +pub async fn get_gas_price( + chain_id: &str, + rpc_project_id: &str, + source: MessageSource, +) -> Result { + let provider = + ReqwestProvider::::new_http(get_rpc_url(chain_id, rpc_project_id, source)?); + let gas_price = provider + .get_gas_price() + .await + .map_err(|e| CryptoUitlsError::ProviderError(format!("{}", e)))?; + Ok(gas_price) +} + +/// Get the nonce +#[tracing::instrument(level = "debug")] +pub async fn get_nonce( + chain_id: &str, + wallet: Address, + rpc_project_id: &str, + source: MessageSource, +) -> Result { + let provider = + ReqwestProvider::::new_http(get_rpc_url(chain_id, rpc_project_id, source)?); + let nonce = provider + .get_transaction_count(wallet) + .await + .map_err(|e| CryptoUitlsError::ProviderError(format!("{}", e)))?; + Ok(nonce) +} + /// Call entry point v07 getUserOpHash contract and get the userOperation hash #[tracing::instrument(level = "debug")] pub async fn call_get_user_op_hash( @@ -501,10 +539,9 @@ pub async fn call_get_user_op_hash( ]"#, ); - let provider = Provider::::try_from(format!( - "https://rpc.walletconnect.com/v1?chainId={}&projectId={}", - chain_id, rpc_project_id - )) + let provider = Provider::::try_from( + get_rpc_url(chain_id, rpc_project_id, MessageSource::ChainAgnosticCheck)?.as_str(), + ) .map_err(|e| CryptoUitlsError::RpcUrlParseError(format!("Failed to parse RPC url: {}", e)))?; let provider = Arc::new(provider);