diff --git a/.trunk/.gitignore b/.trunk/.gitignore index 15966d087..1e2465290 100644 --- a/.trunk/.gitignore +++ b/.trunk/.gitignore @@ -6,4 +6,3 @@ plugins user_trunk.yaml user.yaml -tmp diff --git a/src/test_utils/fixtures.rs b/src/test_utils/fixtures.rs index 984843232..4bf925bd1 100644 --- a/src/test_utils/fixtures.rs +++ b/src/test_utils/fixtures.rs @@ -43,6 +43,28 @@ pub async fn erc20(#[future] katana: Katana) -> (Katana, KakarotEvmContract) { (katana, contract) } +#[cfg(any(test, feature = "arbitrary", feature = "testing"))] +#[fixture] +#[awt] +pub async fn plain_opcodes(#[future] counter: (Katana, KakarotEvmContract)) -> (Katana, KakarotEvmContract) { + use ethers::abi::Address; + + let katana = counter.0; + let counter = counter.1; + let eoa = katana.eoa(); + let counter_address = Address::from_slice(&counter.evm_address.to_bytes_be()[12..]); + let contract = eoa + .deploy_evm_contract( + Some("PlainOpcodes"), + ( + Token::Address(counter_address), // counter address + ), + ) + .await + .expect("Failed to deploy ERC20 contract"); + (katana, contract) +} + /// This fixture creates a new test environment on Katana. #[cfg(any(test, feature = "arbitrary", feature = "testing"))] #[fixture] diff --git a/src/test_utils/katana/mod.rs b/src/test_utils/katana/mod.rs index c6ed04119..0e06e2318 100644 --- a/src/test_utils/katana/mod.rs +++ b/src/test_utils/katana/mod.rs @@ -1,5 +1,6 @@ pub mod genesis; +use std::collections::HashMap; use std::path::Path; use std::sync::Arc; @@ -8,12 +9,19 @@ use katana_primitives::block::GasPrices; use katana_primitives::chain::ChainId; use katana_primitives::genesis::json::GenesisJson; use katana_primitives::genesis::Genesis; +use mongodb::bson::doc; +use mongodb::options::{UpdateModifications, UpdateOptions}; +use reth_primitives::U256; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; -use crate::eth_provider::provider::EthDataProvider; +use crate::eth_provider::database::types::{header::StoredHeader, transaction::StoredTransaction}; +use crate::eth_provider::utils::into_filter; +use crate::eth_provider::{ + constant::{HASH_PADDING, U64_PADDING}, + provider::EthDataProvider, +}; use crate::test_utils::eoa::KakarotEOA; -use std::collections::HashMap; #[cfg(any(test, feature = "arbitrary", feature = "testing"))] use { @@ -148,6 +156,69 @@ impl<'a> Katana { &self.sequencer } + // TODO: improve + pub async fn add_transactions_with_header_to_database(&self, txs: Vec, bn: u64) { + let provider = self.eth_provider(); + let database = provider.database(); + + let block_number = format!("0x{:x}", bn); + let base_fee_per_gas = txs.iter().map(|tx| tx.max_fee_per_gas.unwrap_or_default()).max(); + let padded_block_number = format!("0x{:0>width$}", &block_number[2..], width = U64_PADDING); + + let tx_collection = database.inner().collection::("transactions"); + for tx in txs { + let filter = into_filter("tx.hash", &tx.hash, HASH_PADDING); + database + .update_one::(tx.into(), filter, true) + .await + .expect("Failed to update transaction in database"); + } + tx_collection + .update_many( + doc! {"tx.blockNumber": &block_number}, + UpdateModifications::Document(doc! {"$set": {"tx.blockNumber": &padded_block_number}}), + UpdateOptions::builder().upsert(true).build(), + ) + .await + .expect("Failed to update block number"); + + let header_collection = database.inner().collection::("headers"); + let header = reth_rpc_types::Header { + number: Some(U256::from(bn)), + hash: Some(B256::random()), + parent_hash: Default::default(), + nonce: Default::default(), + logs_bloom: Default::default(), + transactions_root: Default::default(), + state_root: Default::default(), + receipts_root: Default::default(), + difficulty: Default::default(), + total_difficulty: Default::default(), + extra_data: Default::default(), + gas_limit: U256::MAX, + gas_used: Default::default(), + timestamp: Default::default(), + uncles_hash: Default::default(), + miner: Default::default(), + mix_hash: Default::default(), + base_fee_per_gas, + withdrawals_root: Default::default(), + excess_blob_gas: Default::default(), + parent_beacon_block_root: Default::default(), + blob_gas_used: Default::default(), + }; + let filter = into_filter("header.number", &bn, U64_PADDING); + database.update_one(StoredHeader { header }, filter, true).await.expect("Failed to update header in database"); + header_collection + .update_one( + doc! {"header.number": block_number}, + UpdateModifications::Document(doc! {"$set": {"header.number": padded_block_number}}), + UpdateOptions::builder().upsert(true).build(), + ) + .await + .expect("Failed to update block number"); + } + /// Retrieves the first stored transaction pub fn first_transaction(&self) -> Option { self.mock_data diff --git a/tests/trace_api.rs b/tests/trace_api.rs index 580e6d9f3..dba8440d7 100644 --- a/tests/trace_api.rs +++ b/tests/trace_api.rs @@ -1,20 +1,77 @@ #![cfg(feature = "testing")] -use kakarot_rpc::test_utils::fixtures::{katana, setup}; +use kakarot_rpc::eth_provider::provider::EthereumProvider; +use kakarot_rpc::test_utils::eoa::Eoa; +use kakarot_rpc::test_utils::evm_contract::{EvmContract, KakarotEvmContract}; +use kakarot_rpc::test_utils::fixtures::{plain_opcodes, setup}; use kakarot_rpc::test_utils::katana::Katana; use kakarot_rpc::test_utils::rpc::start_kakarot_rpc_server; +use reth_primitives::{U256, U8}; use reth_rpc_types::trace::parity::LocalizedTransactionTrace; +use reth_rpc_types::Signature; use rstest::*; use serde_json::{json, Value}; +const TRACING_BLOCK_NUMBER: u64 = 0x2; +const TRANSACTIONS_COUNT: usize = 2; + #[rstest] #[awt] #[tokio::test(flavor = "multi_thread")] -async fn test_trace_block(#[future] katana: Katana, _setup: ()) { +async fn test_trace_block(#[future] plain_opcodes: (Katana, KakarotEvmContract), _setup: ()) { + let katana = plain_opcodes.0; + let plain_opcodes = plain_opcodes.1; let (server_addr, server_handle) = start_kakarot_rpc_server(&katana).await.expect("Error setting up Kakarot RPC server"); - // Get the first transaction from the mock data. - let tx = &katana.first_transaction().unwrap(); + let eoa = katana.eoa(); + let eoa_address = eoa.evm_address().expect("Failed to get eoa address"); + let nonce: u64 = eoa.nonce().await.expect("Failed to get nonce").to(); + let chain_id = eoa.eth_provider().chain_id().await.expect("Failed to get chain id").unwrap_or_default(); + + // Push 10 RPC transactions into the database. + let mut txs = Vec::with_capacity(TRANSACTIONS_COUNT); + let max_fee_per_gas = 10; + let max_priority_fee_per_gas = 1; + for i in 0..TRANSACTIONS_COUNT { + // We want to trace the "createCounterAndInvoke" which does a CREATE followed by a CALL. + let tx = plain_opcodes + .prepare_call_transaction( + "createCounterAndInvoke", + (), + nonce + i as u64, + 0, + chain_id.to(), + max_fee_per_gas, + max_priority_fee_per_gas, + ) + .expect("Failed to prepare call transaction"); + // Sign the transaction and convert it to a RPC transaction. + let tx_signed = eoa.sign_transaction(tx.clone()).expect("Failed to sign transaction"); + let tx = reth_rpc_types::Transaction { + transaction_type: Some(U8::from(2)), + nonce: tx.nonce(), + hash: tx_signed.hash(), + to: tx.to(), + from: eoa_address, + block_number: Some(U256::from(TRACING_BLOCK_NUMBER)), + chain_id: tx.chain_id(), + gas: U256::from(tx.gas_limit()), + input: tx.input().clone(), + signature: Some(Signature { + r: tx_signed.signature().r, + s: tx_signed.signature().s, + v: U256::from(tx_signed.signature().v(Some(chain_id.to()))), + y_parity: Some(reth_rpc_types::Parity(tx_signed.signature().odd_y_parity)), + }), + max_fee_per_gas: Some(U256::from(max_fee_per_gas)), + gas_price: Some(U256::from(max_fee_per_gas)), + max_priority_fee_per_gas: Some(U256::from(max_priority_fee_per_gas)), + value: tx.value(), + ..Default::default() + }; + txs.push(tx); + } + katana.add_transactions_with_header_to_database(txs, TRACING_BLOCK_NUMBER).await; let reqwest_client = reqwest::Client::new(); let res = reqwest_client @@ -25,7 +82,7 @@ async fn test_trace_block(#[future] katana: Katana, _setup: ()) { { "jsonrpc":"2.0", "method":"trace_block", - "params":[format!("0x{:064x}", tx.block_hash.unwrap_or_default())], + "params":[format!("0x{:016x}", TRACING_BLOCK_NUMBER)], "id":1, } ) @@ -35,7 +92,6 @@ async fn test_trace_block(#[future] katana: Katana, _setup: ()) { .await .expect("Failed to call Debug RPC"); let response = res.text().await.expect("Failed to get response body"); - dbg!(response.clone()); let raw: Value = serde_json::from_str(&response).expect("Failed to deserialize response body"); let traces: Option> = serde_json::from_value(raw["result"].clone()).expect("Failed to deserialize result");