use std::collections::HashMap;

use crate::{
    abi::{Multicall, SimpleStorage},
    fork::fork_config,
    utils::http_provider_with_signer,
};
use alloy_eips::BlockId;
use alloy_network::{EthereumWallet, TransactionBuilder};
use alloy_primitives::{
    Address, Bytes, U256,
    hex::{self, FromHex},
};
use alloy_provider::{
    Provider,
    ext::{DebugApi, TraceApi},
};
use alloy_rlp::Encodable;
use alloy_rpc_types::{
    BlockNumberOrTag, Index, TransactionRequest,
    state::StateOverride,
    trace::{
        filter::{TraceFilter, TraceFilterMode},
        geth::{
            AccountState, CallConfig, GethDebugBuiltInTracerType, GethDebugTracerType,
            GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, PreStateConfig,
            PreStateFrame,
        },
        opcode::{BlockOpcodeGas, TransactionOpcodeGas},
        parity::{Action, ChangedType, LocalizedTransactionTrace, TraceResults, TraceType},
    },
};
use alloy_rpc_types_eth::AccountInfo;
use alloy_serde::WithOtherFields;
use alloy_sol_types::{SolCall, SolValue, sol};
use anvil::{NodeConfig, spawn};
use foundry_evm::hardfork::EthereumHardfork;

#[tokio::test(flavor = "multi_thread")]
async fn test_get_transfer_parity_traces() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.ws_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();
    let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap();
    // specify the `from` field so that the client knows which account to use
    let tx = TransactionRequest::default().to(to).value(amount).from(from);
    let tx = WithOtherFields::new(tx);

    // broadcast it via the eth_sendTransaction API
    let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();

    let traces = provider.trace_transaction(tx.transaction_hash).await.unwrap();
    assert!(!traces.is_empty());

    match traces[0].trace.action {
        Action::Call(ref call) => {
            assert_eq!(call.from, from);
            assert_eq!(call.to, to);
            assert_eq!(call.value, amount);
        }
        _ => unreachable!("unexpected action"),
    }

    let num = provider.get_block_number().await.unwrap();
    let block_traces = provider.trace_block(num.into()).await.unwrap();
    assert!(!block_traces.is_empty());

    assert_eq!(traces, block_traces);
}

#[tokio::test(flavor = "multi_thread")]
async fn test_trace_transaction_opcode_gas_local() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let wallets = handle.dev_wallets().collect::<Vec<_>>();
    let signer: EthereumWallet = wallets[0].clone().into();
    let provider = http_provider_with_signer(&handle.http_endpoint(), signer);

    let storage = SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap();
    let receipt =
        storage.setValue("bar".to_string()).send().await.unwrap().get_receipt().await.unwrap();
    let opcode_gas: Option<TransactionOpcodeGas> = handle
        .http_provider()
        .raw_request("trace_transactionOpcodeGas".into(), (receipt.transaction_hash,))
        .await
        .unwrap();
    let opcode_gas = opcode_gas.unwrap();

    assert_eq!(opcode_gas.transaction_hash, receipt.transaction_hash);
    assert!(opcode_gas.contains("SSTORE"));
}

#[tokio::test(flavor = "multi_thread")]
async fn test_trace_block_opcode_gas_local() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let wallets = handle.dev_wallets().collect::<Vec<_>>();
    let signer: EthereumWallet = wallets[0].clone().into();
    let provider = http_provider_with_signer(&handle.http_endpoint(), signer);

    let genesis = provider.get_block(BlockId::number(0)).await.unwrap().unwrap();
    for block_id in [BlockId::number(0), BlockId::hash(genesis.header.hash), BlockId::earliest()] {
        let opcode_gas: Option<BlockOpcodeGas> = handle
            .http_provider()
            .raw_request("trace_blockOpcodeGas".into(), (block_id,))
            .await
            .unwrap();
        let opcode_gas = opcode_gas.unwrap();

        assert_eq!(opcode_gas.block_hash, genesis.header.hash);
        assert_eq!(opcode_gas.block_number, genesis.header.number);
        assert!(opcode_gas.transactions.is_empty());
    }

    let storage = SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap();
    let receipt =
        storage.setValue("bar".to_string()).send().await.unwrap().get_receipt().await.unwrap();
    let block_number = receipt.block_number.unwrap();
    let block_hash = receipt.block_hash.unwrap();

    let by_number: Option<BlockOpcodeGas> = handle
        .http_provider()
        .raw_request("trace_blockOpcodeGas".into(), (BlockId::number(block_number),))
        .await
        .unwrap();
    let by_hash: Option<BlockOpcodeGas> = handle
        .http_provider()
        .raw_request("trace_blockOpcodeGas".into(), (BlockId::Hash(block_hash.into()),))
        .await
        .unwrap();

    let by_number = by_number.unwrap();
    let by_hash = by_hash.unwrap();

    assert_eq!(by_number.block_hash, block_hash);
    assert_eq!(by_number.block_number, block_number);
    assert_eq!(by_number.transactions.len(), 1);
    assert_eq!(by_number.transactions[0].transaction_hash, receipt.transaction_hash);
    assert!(by_number.contains("SSTORE"));

    assert_eq!(by_hash.block_hash, block_hash);
    assert_eq!(by_hash.block_number, block_number);
    assert_eq!(by_hash.transactions.len(), 1);
    assert_eq!(by_hash.transactions[0].transaction_hash, receipt.transaction_hash);
    assert!(by_hash.contains("SSTORE"));
}

#[tokio::test(flavor = "multi_thread")]
async fn test_trace_raw_transaction_local() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();
    let value = U256::from(1_000_000_000u64);
    let from_balance = provider.get_balance(from).await.unwrap();
    let to_balance = provider.get_balance(to).await.unwrap();

    let tx = TransactionRequest::default()
        .from(from)
        .to(to)
        .value(value)
        .with_gas_limit(21_000)
        .max_fee_per_gas(20_000_000_000)
        .max_priority_fee_per_gas(1_000_000_000);
    let signed_tx = api.sign_transaction(WithOtherFields::new(tx)).await.unwrap();
    let raw_tx = hex::decode(&signed_tx[2..]).unwrap();

    let traces =
        provider.trace_raw_transaction(&raw_tx).trace().state_diff().vm_trace().await.unwrap();

    assert!(traces.state_diff.is_some());
    assert!(traces.vm_trace.is_some());
    assert!(!traces.trace.is_empty());
    match traces.trace[0].action {
        Action::Call(ref call) => {
            assert_eq!(call.from, from);
            assert_eq!(call.to, to);
            assert_eq!(call.value, value);
        }
        _ => unreachable!("unexpected action"),
    }

    assert_eq!(provider.get_transaction_count(from).await.unwrap(), 0);
    assert_eq!(provider.get_balance(from).await.unwrap(), from_balance);
    assert_eq!(provider.get_balance(to).await.unwrap(), to_balance);
}

#[tokio::test(flavor = "multi_thread")]
async fn test_debug_account_info_at_local() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();
    let amount = U256::from(1000);

    let tx = TransactionRequest::default().to(to).value(amount).from(from);
    let tx = WithOtherFields::new(tx);
    let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
    let block_number = receipt.block_number.unwrap();
    let block_hash = receipt.block_hash.unwrap();

    let by_number: Option<AccountInfo> = provider
        .raw_request(
            "debug_accountInfoAt".into(),
            (BlockId::number(block_number), Index::from(0), to),
        )
        .await
        .unwrap();
    let by_hash: Option<AccountInfo> = provider
        .raw_request(
            "debug_accountInfoAt".into(),
            (BlockId::Hash(block_hash.into()), Index::from(0), to),
        )
        .await
        .unwrap();

    let expected_balance = handle.genesis_balance().saturating_add(amount);
    for account in [by_number.unwrap(), by_hash.unwrap()] {
        assert_eq!(account.balance, expected_balance);
        assert_eq!(account.nonce, 0);
        assert!(account.code.is_empty());
    }
}

#[tokio::test(flavor = "multi_thread")]
async fn test_debug_account_info_at_local_block_on_fork() {
    let (_origin_api, origin_handle) = spawn(NodeConfig::test()).await;
    let origin_provider = origin_handle.http_provider();
    let origin_accounts = origin_handle.dev_wallets().collect::<Vec<_>>();
    let origin_signer: EthereumWallet = origin_accounts[0].clone().into();
    let origin_provider_with_signer =
        http_provider_with_signer(&origin_handle.http_endpoint(), origin_signer);
    let storage = SimpleStorage::deploy(&origin_provider_with_signer, "init value".to_string())
        .await
        .unwrap();
    let fork_account = *storage.address();
    let expected_code = origin_provider.get_code_at(fork_account).await.unwrap();

    let (_api, handle) =
        spawn(NodeConfig::test().with_eth_rpc_url(Some(origin_handle.http_endpoint()))).await;
    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();
    let tx = TransactionRequest::default().to(to).value(U256::from(1000)).from(from);
    let receipt = provider
        .send_transaction(WithOtherFields::new(tx))
        .await
        .unwrap()
        .get_receipt()
        .await
        .unwrap();
    let block_number = receipt.block_number.unwrap();

    let account: Option<AccountInfo> = provider
        .raw_request(
            "debug_accountInfoAt".into(),
            (BlockId::number(block_number), Index::from(0), fork_account),
        )
        .await
        .unwrap();
    let account = account.unwrap();

    assert_eq!(account.balance, U256::ZERO);
    assert_eq!(account.nonce, 1);
    assert_eq!(account.code, expected_code);
}

#[tokio::test(flavor = "multi_thread")]
async fn test_debug_account_info_at_delegates_pre_fork_block() {
    let (_origin_api, origin_handle) = spawn(NodeConfig::test()).await;
    let origin_provider = origin_handle.http_provider();
    let origin_accounts = origin_handle.dev_wallets().collect::<Vec<_>>();
    let from = origin_accounts[0].address();
    let to = origin_accounts[1].address();
    let amount = U256::from(1000);
    let tx = TransactionRequest::default().to(to).value(amount).from(from);
    origin_provider
        .send_transaction(WithOtherFields::new(tx))
        .await
        .unwrap()
        .get_receipt()
        .await
        .unwrap();

    let (_api, handle) =
        spawn(NodeConfig::test().with_eth_rpc_url(Some(origin_handle.http_endpoint()))).await;
    let provider = handle.http_provider();

    let account: Option<AccountInfo> = provider
        .raw_request("debug_accountInfoAt".into(), (BlockId::number(1), Index::from(0), to))
        .await
        .unwrap();
    let account = account.unwrap();

    assert_eq!(account.balance, origin_handle.genesis_balance().saturating_add(amount));
    assert_eq!(account.nonce, 0);
    assert!(account.code.is_empty());
}

#[tokio::test(flavor = "multi_thread")]
async fn test_trace_call_local() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();
    let amount = U256::from(1000);
    let before = provider.get_balance(to).await.unwrap();
    let tx = TransactionRequest::default().to(to).value(amount).from(from);
    let tx = WithOtherFields::new(tx);

    let traces: TraceResults = provider
        .client()
        .request(
            "trace_call",
            (
                tx,
                vec![TraceType::Trace, TraceType::VmTrace, TraceType::StateDiff],
                BlockId::latest(),
            ),
        )
        .await
        .unwrap();

    assert!(!traces.trace.is_empty());
    assert!(traces.vm_trace.is_some());
    assert!(traces.state_diff.is_some());

    match &traces.trace[0].action {
        Action::Call(call) => {
            assert_eq!(call.from, from);
            assert_eq!(call.to, to);
            assert_eq!(call.value, amount);
        }
        action => panic!("expected call action, got {action:?}"),
    }

    let ChangedType { from: before_diff, to: after_diff } =
        traces.state_diff.as_ref().unwrap().get(&to).unwrap().balance.as_changed().unwrap();
    assert_eq!(*before_diff, before);
    assert_eq!(after_diff.saturating_sub(*before_diff), amount);

    let after = provider.get_balance(to).await.unwrap();
    assert_eq!(after, before);
}

#[tokio::test(flavor = "multi_thread")]
async fn test_trace_call_many_local() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let wallets = handle.dev_wallets().collect::<Vec<_>>();
    let deployer: EthereumWallet = wallets[0].clone().into();
    let provider = http_provider_with_signer(&handle.http_endpoint(), deployer);

    let storage = SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap();
    let set_value = storage.setValue("bar".to_string());
    let get_value = storage.getValue();

    let set_tx = TransactionRequest::default()
        .from(wallets[1].address())
        .to(*storage.address())
        .with_input(set_value.calldata().to_owned());
    let get_tx = TransactionRequest::default()
        .from(wallets[1].address())
        .to(*storage.address())
        .with_input(get_value.calldata().to_owned());
    let trace_types = [TraceType::Trace];
    let calls = [
        (WithOtherFields::new(set_tx), trace_types.as_slice()),
        (WithOtherFields::new(get_tx), trace_types.as_slice()),
    ];

    let traces = handle.http_provider().trace_call_many(&calls).await.unwrap();

    assert_eq!(traces.len(), 2);
    assert!(!traces[0].trace.is_empty());
    assert!(!traces[1].trace.is_empty());

    let traced_value = SimpleStorage::getValueCall::abi_decode_returns(&traces[1].output).unwrap();
    assert_eq!(traced_value, "bar".to_string());
    assert_eq!(storage.getValue().call().await.unwrap(), "init value".to_string());
}

#[tokio::test(flavor = "multi_thread")]
async fn test_trace_get_local() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();
    let amount = U256::from(1000);
    let tx = TransactionRequest::default().to(to).value(amount).from(from);
    let tx = WithOtherFields::new(tx);
    let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();

    let traces = provider.trace_transaction(receipt.transaction_hash).await.unwrap();
    assert!(!traces.is_empty());

    let trace = provider.trace_get(receipt.transaction_hash, 0).await.unwrap();
    assert_eq!(trace, traces[0]);

    let missing: Option<LocalizedTransactionTrace> = provider
        .client()
        .request("trace_get", (receipt.transaction_hash, vec![Index::from(999)]))
        .await
        .unwrap();
    assert_eq!(missing, None);

    let invalid_indices: Option<LocalizedTransactionTrace> = provider
        .client()
        .request("trace_get", (receipt.transaction_hash, vec![Index::from(0), Index::from(1)]))
        .await
        .unwrap();
    assert_eq!(invalid_indices, None);
}

sol!(
    #[sol(rpc, bytecode = "0x6080604052348015600f57600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060a48061005e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806375fc8e3c14602d575b600080fd5b60336035565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16fffea26469706673582212205006867290df97c54f2df1cb94fc081197ab670e2adf5353071d2ecce1d694b864736f6c634300080d0033")]
    contract SuicideContract {
        address payable private owner;
        constructor() public {
            owner = payable(msg.sender);
        }
        function goodbye() public {
            selfdestruct(owner);
        }
    }
);

#[tokio::test(flavor = "multi_thread")]
async fn test_parity_suicide_trace() {
    let (_api, handle) =
        spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Shanghai.into()))).await;
    let provider = handle.ws_provider();
    let wallets = handle.dev_wallets().collect::<Vec<_>>();
    let owner = wallets[0].address();
    let destructor = wallets[1].address();

    let contract_addr =
        SuicideContract::deploy_builder(provider.clone()).from(owner).deploy().await.unwrap();
    let contract = SuicideContract::new(contract_addr, provider.clone());
    let call = contract.goodbye().from(destructor);
    let call = call.send().await.unwrap();
    let tx = call.get_receipt().await.unwrap();

    let traces = handle.http_provider().trace_transaction(tx.transaction_hash).await.unwrap();
    assert!(!traces.is_empty());
    assert!(traces[1].trace.action.is_selfdestruct());
}

sol!(
    #[sol(rpc, bytecode = "0x6080604052348015600f57600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060a48061005e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806375fc8e3c14602d575b600080fd5b60336035565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16fffea26469706673582212205006867290df97c54f2df1cb94fc081197ab670e2adf5353071d2ecce1d694b864736f6c634300080d0033")]
    contract DebugTraceContract {
        address payable private owner;
        constructor() public {
            owner = payable(msg.sender);
        }
        function goodbye() public {
            selfdestruct(owner);
        }
    }
);

#[tokio::test(flavor = "multi_thread")]
async fn test_transfer_debug_trace_call() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let wallets = handle.dev_wallets().collect::<Vec<_>>();
    let deployer: EthereumWallet = wallets[0].clone().into();
    let provider = http_provider_with_signer(&handle.http_endpoint(), deployer);

    let contract_addr = DebugTraceContract::deploy_builder(provider.clone())
        .from(wallets[0].clone().address())
        .deploy()
        .await
        .unwrap();

    let caller: EthereumWallet = wallets[1].clone().into();
    let caller_provider = http_provider_with_signer(&handle.http_endpoint(), caller);
    let contract = DebugTraceContract::new(contract_addr, caller_provider);

    let call = contract.goodbye().from(wallets[1].address());
    let calldata = call.calldata().to_owned();

    let tx = TransactionRequest::default()
        .from(wallets[1].address())
        .to(*contract.address())
        .with_input(calldata);

    let traces = handle
        .http_provider()
        .debug_trace_call(
            WithOtherFields::new(tx),
            BlockId::latest(),
            GethDebugTracingCallOptions::default(),
        )
        .await
        .unwrap();

    match traces {
        GethTrace::Default(default_frame) => {
            assert!(!default_frame.failed);
        }
        _ => {
            unreachable!()
        }
    }
}

#[tokio::test(flavor = "multi_thread")]
async fn test_call_tracer_debug_trace_call() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let wallets = handle.dev_wallets().collect::<Vec<_>>();
    let deployer: EthereumWallet = wallets[0].clone().into();
    let provider = http_provider_with_signer(&handle.http_endpoint(), deployer);

    let multicall_contract = Multicall::deploy(&provider).await.unwrap();

    let simple_storage_contract =
        SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap();

    let set_value = simple_storage_contract.setValue("bar".to_string());
    let set_value_calldata = set_value.calldata();

    let internal_call_tx_builder = multicall_contract.aggregate(vec![Multicall::Call {
        target: *simple_storage_contract.address(),
        callData: set_value_calldata.to_owned(),
    }]);

    let internal_call_tx_calldata = internal_call_tx_builder.calldata().to_owned();

    // calling SimpleStorage contract through Multicall should result in an internal call
    let internal_call_tx = TransactionRequest::default()
        .from(wallets[1].address())
        .to(*multicall_contract.address())
        .with_input(internal_call_tx_calldata);

    let internal_call_tx_traces = handle
        .http_provider()
        .debug_trace_call(
            WithOtherFields::new(internal_call_tx.clone()),
            BlockId::latest(),
            GethDebugTracingCallOptions::default().with_tracing_options(
                GethDebugTracingOptions::default()
                    .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer))
                    .with_call_config(CallConfig::default().with_log()),
            ),
        )
        .await
        .unwrap();

    match internal_call_tx_traces {
        GethTrace::CallTracer(call_frame) => {
            assert!(call_frame.calls.len() == 1);
            assert!(
                call_frame.calls.first().unwrap().to.unwrap() == *simple_storage_contract.address()
            );
            assert!(call_frame.calls.first().unwrap().logs.len() == 1);
        }
        _ => {
            unreachable!()
        }
    }

    // only_top_call option - should not return any internal calls
    let internal_call_only_top_call_tx_traces = handle
        .http_provider()
        .debug_trace_call(
            WithOtherFields::new(internal_call_tx.clone()),
            BlockId::latest(),
            GethDebugTracingCallOptions::default().with_tracing_options(
                GethDebugTracingOptions::default()
                    .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer))
                    .with_call_config(CallConfig::default().with_log().only_top_call()),
            ),
        )
        .await
        .unwrap();

    match internal_call_only_top_call_tx_traces {
        GethTrace::CallTracer(call_frame) => {
            assert!(call_frame.calls.is_empty());
        }
        _ => {
            unreachable!()
        }
    }

    let receipt = internal_call_tx_builder.send().await.unwrap().get_receipt().await.unwrap();
    let internal_call_tx_hash = receipt.transaction_hash;
    let trace_provider = handle.http_provider();

    let internal_call_tx_traces: GethTrace = trace_provider
        .raw_request(
            "debug_traceTransaction".into(),
            (
                internal_call_tx_hash,
                serde_json::json!({
                    "tracer": "callTracer",
                    "tracerConfig": {
                        "withLog": true
                    }
                }),
            ),
        )
        .await
        .unwrap();

    match internal_call_tx_traces {
        GethTrace::CallTracer(call_frame) => {
            assert_eq!(call_frame.calls.len(), 1);
            assert_eq!(
                call_frame.calls.first().unwrap().to.unwrap(),
                *simple_storage_contract.address()
            );
        }
        _ => {
            unreachable!()
        }
    }

    let internal_call_only_top_level_call_tx_traces: GethTrace = trace_provider
        .raw_request(
            "debug_traceTransaction".into(),
            (
                internal_call_tx_hash,
                serde_json::json!({
                    "tracer": "callTracer",
                    "tracerConfig": {
                        "onlyTopLevelCall": true,
                        "withLog": true
                    }
                }),
            ),
        )
        .await
        .unwrap();

    match internal_call_only_top_level_call_tx_traces {
        GethTrace::CallTracer(call_frame) => {
            assert!(call_frame.calls.is_empty());
        }
        _ => {
            unreachable!()
        }
    }

    // directly calling the SimpleStorage contract should not result in any internal calls
    let direct_call_tx = TransactionRequest::default()
        .from(wallets[1].address())
        .to(*simple_storage_contract.address())
        .with_input(set_value_calldata.to_owned());

    let direct_call_tx_traces = handle
        .http_provider()
        .debug_trace_call(
            WithOtherFields::new(direct_call_tx),
            BlockId::latest(),
            GethDebugTracingCallOptions::default().with_tracing_options(
                GethDebugTracingOptions::default()
                    .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer))
                    .with_call_config(CallConfig::default().with_log()),
            ),
        )
        .await
        .unwrap();

    match direct_call_tx_traces {
        GethTrace::CallTracer(call_frame) => {
            assert!(call_frame.calls.is_empty());
            assert!(call_frame.to.unwrap() == *simple_storage_contract.address());
            assert!(call_frame.logs.len() == 1);
        }
        _ => {
            unreachable!()
        }
    }
}

#[tokio::test(flavor = "multi_thread")]
async fn test_debug_trace_call_state_override() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let wallets = handle.dev_wallets().collect::<Vec<_>>();

    let tx = TransactionRequest::default()
        .from(wallets[1].address())
        .to("0x1234567890123456789012345678901234567890".parse().unwrap());

    let override_json = r#"{
            "0x1234567890123456789012345678901234567890": {
                "balance": "0x01",
                "code": "0x30315f5260205ff3"
            }
        }"#;

    let state_override: StateOverride = serde_json::from_str(override_json).unwrap();

    let tx_traces = handle
        .http_provider()
        .debug_trace_call(
            WithOtherFields::new(tx.clone()),
            BlockId::latest(),
            GethDebugTracingCallOptions::default()
                .with_tracing_options(GethDebugTracingOptions::default())
                .with_state_overrides(state_override),
        )
        .await
        .unwrap();

    match tx_traces {
        GethTrace::Default(trace_res) => {
            assert_eq!(
                trace_res.return_value,
                Bytes::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
                    .unwrap()
            );
        }
        _ => {
            unreachable!()
        }
    }
}

#[tokio::test(flavor = "multi_thread")]
async fn test_debug_trace_call_tx_index() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let wallets = handle.dev_wallets().collect::<Vec<_>>();
    let deployer: EthereumWallet = wallets[0].clone().into();
    let provider = http_provider_with_signer(&handle.http_endpoint(), deployer);

    let simple_storage_contract =
        SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap();

    api.anvil_set_auto_mine(false).await.unwrap();

    let from = wallets[0].address();
    let nonce = provider.get_transaction_count(from).await.unwrap();
    for (offset, value) in ["first", "second", "third"].into_iter().enumerate() {
        let set_value = simple_storage_contract.setValue(value.to_string());
        let tx = TransactionRequest::default()
            .from(from)
            .to(*simple_storage_contract.address())
            .with_input(set_value.calldata().to_owned())
            .nonce(nonce + offset as u64);
        let _ = provider.send_transaction(WithOtherFields::new(tx)).await.unwrap();
    }

    api.mine_one().await.unwrap();
    let block_number = provider.get_block_number().await.unwrap();

    let get_value = simple_storage_contract.getValue();
    let call = TransactionRequest::default()
        .from(from)
        .to(*simple_storage_contract.address())
        .with_input(get_value.calldata().to_owned());

    for (tx_index, expected) in [(0, "init value"), (1, "first"), (2, "second")] {
        let trace = provider
            .debug_trace_call(
                WithOtherFields::new(call.clone()),
                BlockId::number(block_number),
                GethDebugTracingCallOptions::default().with_tx_index(tx_index),
            )
            .await
            .unwrap();
        match trace {
            GethTrace::Default(default_frame) => {
                assert_eq!(
                    default_frame.return_value,
                    Bytes::from(String::from(expected).abi_encode())
                );
            }
            _ => unreachable!(),
        }
    }
}

#[tokio::test(flavor = "multi_thread")]
async fn test_debug_trace_transaction_reports_transaction_gas() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    api.anvil_set_auto_mine(false).await.unwrap();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();
    let nonce = provider.get_transaction_count(from).await.unwrap();

    let first = provider
        .send_transaction(WithOtherFields::new(
            TransactionRequest::default().from(from).to(to).value(U256::from(1)).nonce(nonce),
        ))
        .await
        .unwrap();
    let second = provider
        .send_transaction(WithOtherFields::new(
            TransactionRequest::default().from(from).to(to).value(U256::from(2)).nonce(nonce + 1),
        ))
        .await
        .unwrap();

    api.mine_one().await.unwrap();
    let first_receipt = first.get_receipt().await.unwrap();
    let second_receipt = second.get_receipt().await.unwrap();
    assert_eq!(first_receipt.block_hash, second_receipt.block_hash);

    let default_trace = api
        .debug_trace_transaction(
            second_receipt.transaction_hash,
            GethDebugTracingOptions::default(),
        )
        .await
        .unwrap();
    let GethTrace::Default(default_frame) = default_trace else {
        unreachable!("expected default trace")
    };
    assert_eq!(default_frame.gas, second_receipt.gas_used);

    let call_trace = api
        .debug_trace_transaction(
            second_receipt.transaction_hash,
            GethDebugTracingOptions::default()
                .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer)),
        )
        .await
        .unwrap();
    let GethTrace::CallTracer(call_frame) = call_trace else { unreachable!("expected call trace") };
    assert_eq!(call_frame.gas_used, U256::from(second_receipt.gas_used));
}

#[tokio::test(flavor = "multi_thread")]
async fn test_debug_trace_call_tx_index_fork_lazy_state() {
    let (_api, handle) = spawn(fork_config()).await;
    let provider = handle.http_provider();
    let from = handle.dev_wallets().next().unwrap().address();

    let tx = TransactionRequest::default().from(from).to(Address::random()).value(U256::from(1));
    let _ = provider
        .send_transaction(WithOtherFields::new(tx))
        .await
        .unwrap()
        .get_receipt()
        .await
        .unwrap();
    let block_number = provider.get_block_number().await.unwrap();

    let usdc = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48".parse().unwrap();
    let total_supply = TransactionRequest::default()
        .from(from)
        .to(usdc)
        .with_input(Bytes::from_hex("18160ddd").unwrap());

    let tx_index_trace = provider
        .debug_trace_call(
            WithOtherFields::new(total_supply.clone()),
            BlockId::number(block_number),
            GethDebugTracingCallOptions::default().with_tx_index(0),
        )
        .await
        .unwrap();
    let GethTrace::Default(tx_index_frame) = tx_index_trace else { unreachable!() };

    let latest_trace = provider
        .debug_trace_call(
            WithOtherFields::new(total_supply),
            BlockId::number(block_number),
            GethDebugTracingCallOptions::default(),
        )
        .await
        .unwrap();
    let GethTrace::Default(latest_frame) = latest_trace else { unreachable!() };

    assert!(!tx_index_frame.return_value.is_empty());
    assert_eq!(tx_index_frame.return_value, latest_frame.return_value);
}

// <https://github.com/foundry-rs/foundry/issues/2656>
#[tokio::test(flavor = "multi_thread")]
async fn test_trace_address_fork() {
    let (api, handle) = spawn(fork_config().with_fork_block_number(Some(15291050u64))).await;
    let provider = handle.http_provider();

    let input = hex::decode("43bcfab60000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000e0bd811c8769a824b00000000000000000000000000000000000000000000000e0ae9925047d8440b60000000000000000000000002e4777139254ff76db957e284b186a4507ff8c67").unwrap();

    let from: Address = "0x2e4777139254ff76db957e284b186a4507ff8c67".parse().unwrap();
    let to: Address = "0xe2f2a5c287993345a840db3b0845fbc70f5935a5".parse().unwrap();
    let tx = TransactionRequest::default()
        .to(to)
        .from(from)
        .with_input::<Bytes>(input.into())
        .with_gas_limit(300_000);

    let tx = WithOtherFields::new(tx);
    api.anvil_impersonate_account(from).await.unwrap();

    let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();

    let traces = provider.trace_transaction(tx.transaction_hash).await.unwrap();
    assert!(!traces.is_empty());
    match traces[0].trace.action {
        Action::Call(ref call) => {
            assert_eq!(call.from, from);
            assert_eq!(call.to, to);
        }
        _ => unreachable!("unexpected action"),
    }

    let json = serde_json::json!([
        {
            "action": {
                "callType": "call",
                "from": "0x2e4777139254ff76db957e284b186a4507ff8c67",
                "gas": "0x262b3",
                "input": "0x43bcfab60000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000e0bd811c8769a824b00000000000000000000000000000000000000000000000e0ae9925047d8440b60000000000000000000000002e4777139254ff76db957e284b186a4507ff8c67",
                "to": "0xe2f2a5c287993345a840db3b0845fbc70f5935a5",
                "value": "0x0"
            },
            "blockHash": "0xa47c8f1d8c284cb614e9c8e10d260b33eae16b1957a83141191bc335838d7e29",
            "blockNumber": 15291051,
            "result": {
                "gasUsed": "0x2131b",
                "output": "0x0000000000000000000000000000000000000000000000e0e82ca52ec6e6a4d3"
            },
            "subtraces": 1,
            "traceAddress": [],
            "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a",
            "transactionPosition": 19,
            "type": "call"
        },
        {
            "action": {
                "callType": "delegatecall",
                "from": "0xe2f2a5c287993345a840db3b0845fbc70f5935a5",
                "gas": "0x23d88",
                "input": "0x43bcfab60000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000e0bd811c8769a824b00000000000000000000000000000000000000000000000e0ae9925047d8440b60000000000000000000000002e4777139254ff76db957e284b186a4507ff8c67",
                "to": "0x15b2838cd28cc353afbe59385db3f366d8945aee",
                "value": "0x0"
            },
            "blockHash": "0xa47c8f1d8c284cb614e9c8e10d260b33eae16b1957a83141191bc335838d7e29",
            "blockNumber": 15291051,
            "result": {
                "gasUsed": "0x1f6e1",
                "output": "0x0000000000000000000000000000000000000000000000e0e82ca52ec6e6a4d3"
            },
            "subtraces": 2,
            "traceAddress": [0],
            "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a",
            "transactionPosition": 19,
            "type": "call"
        },
        {
            "action": {
                "callType": "staticcall",
                "from": "0xe2f2a5c287993345a840db3b0845fbc70f5935a5",
                "gas": "0x192ed",
                "input": "0x50494dc000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000e0b1ff65617f654b2f00000000000000000000000000000000000000000000000000000000000061a800000000000000000000000000000000000000000000000000b1a2bc2ec5000000000000000000000000000000000000000000000000000006f05b59d3b2000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000414ec22973db48fd3a3370000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000a7314a9ba5c0000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000095f783edc5a5dabcb4ba70000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000a3f42df4dab",
                "to": "0xca480d596e6717c95a62a4dc1bd4fbd7b7e7d705",
                "value": "0x0"
            },
            "blockHash": "0xa47c8f1d8c284cb614e9c8e10d260b33eae16b1957a83141191bc335838d7e29",
            "blockNumber": 15291051,
            "result": {
                "gasUsed": "0x661a",
                "output": "0x0000000000000000000000000000000000000000000000e0e82ca52ec6e6a4d3"
            },
            "subtraces": 0,
            "traceAddress": [0, 0],
            "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a",
            "transactionPosition": 19,
            "type": "call"
        },
        {
            "action": {
                "callType": "delegatecall",
                "from": "0xe2f2a5c287993345a840db3b0845fbc70f5935a5",
                "gas": "0xd2dc",
                "input": "0x4e331a540000000000000000000000000000000000000000000000e0e82ca52ec6e6a4d30000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000a2a3cae63476891ab2d640d9a5a800755ee79d6e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000095f783edc5a5dabcb4ba70000000000000000000000002e4777139254ff76db957e284b186a4507ff8c6700000000000000000000000000000000000000000000f7be2b91f8a2e2df496e",
                "to": "0x1e91f826fa8aa4fa4d3f595898af3a64dd188848",
                "value": "0x0"
            },
            "blockHash": "0xa47c8f1d8c284cb614e9c8e10d260b33eae16b1957a83141191bc335838d7e29",
            "blockNumber": 15291051,
            "result": {
                "gasUsed": "0x7617",
                "output": "0x"
            },
            "subtraces": 2,
            "traceAddress": [0, 1],
            "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a",
            "transactionPosition": 19,
            "type": "call"
        },
        {
            "action": {
                "callType": "staticcall",
                "from": "0xe2f2a5c287993345a840db3b0845fbc70f5935a5",
                "gas": "0xbf50",
                "input": "0x70a08231000000000000000000000000a2a3cae63476891ab2d640d9a5a800755ee79d6e",
                "to": "0x6b175474e89094c44da98b954eedeac495271d0f",
                "value": "0x0"
            },
            "blockHash": "0xa47c8f1d8c284cb614e9c8e10d260b33eae16b1957a83141191bc335838d7e29",
            "blockNumber": 15291051,
            "result": {
                "gasUsed": "0xa2a",
                "output": "0x0000000000000000000000000000000000000000000020fe99f8898600d94750"
            },
            "subtraces": 0,
            "traceAddress": [0, 1, 0],
            "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a",
            "transactionPosition": 19,
            "type": "call"
        },
        {
            "action": {
                "callType": "call",
                "from": "0xe2f2a5c287993345a840db3b0845fbc70f5935a5",
                "gas": "0xa92a",
                "input": "0xa4e285950000000000000000000000002e4777139254ff76db957e284b186a4507ff8c670000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000e0e82ca52ec6e6a4d3",
                "to": "0xa2a3cae63476891ab2d640d9a5a800755ee79d6e",
                "value": "0x0"
            },
            "blockHash": "0xa47c8f1d8c284cb614e9c8e10d260b33eae16b1957a83141191bc335838d7e29",
            "blockNumber": 15291051,
            "result": {
                "gasUsed": "0x4ed3",
                "output": "0x"
            },
            "subtraces": 1,
            "traceAddress": [0, 1, 1],
            "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a",
            "transactionPosition": 19,
            "type": "call"
        },
        {
            "action": {
                "callType": "call",
                "from": "0xa2a3cae63476891ab2d640d9a5a800755ee79d6e",
                "gas": "0x8c90",
                "input": "0xa9059cbb0000000000000000000000002e4777139254ff76db957e284b186a4507ff8c670000000000000000000000000000000000000000000000e0e82ca52ec6e6a4d3",
                "to": "0x6b175474e89094c44da98b954eedeac495271d0f",
                "value": "0x0"
            },
            "blockHash": "0xa47c8f1d8c284cb614e9c8e10d260b33eae16b1957a83141191bc335838d7e29",
            "blockNumber": 15291051,
            "result": {
                "gasUsed": "0x2b42",
                "output": "0x0000000000000000000000000000000000000000000000000000000000000001"
            },
            "subtraces": 0,
            "traceAddress": [0, 1, 1, 0],
            "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a",
            "transactionPosition": 19,
            "type": "call"
        }
    ]);

    let expected_traces: Vec<LocalizedTransactionTrace> = serde_json::from_value(json).unwrap();

    // test matching traceAddress
    traces.into_iter().zip(expected_traces).for_each(|(a, b)| {
        assert_eq!(a.trace.trace_address, b.trace.trace_address);
        assert_eq!(a.trace.subtraces, b.trace.subtraces);
        match (a.trace.action, b.trace.action) {
            (Action::Call(a), Action::Call(b)) => {
                assert_eq!(a.from, b.from);
                assert_eq!(a.to, b.to);
            }
            _ => unreachable!("unexpected action"),
        }
    })
}

// <https://github.com/foundry-rs/foundry/issues/2705>
// <https://etherscan.io/tx/0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845>
#[tokio::test(flavor = "multi_thread")]
async fn test_trace_address_fork2() {
    let (api, handle) = spawn(fork_config().with_fork_block_number(Some(15314401u64))).await;
    let provider = handle.http_provider();

    let input = hex::decode("30000003000000000000000000000000adda1059a6c6c102b0fa562b9bb2cb9a0de5b1f4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a300000004fffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb980c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f4d2888d29d722226fafa5d9b24f9164c092421e000bb8000000000000004319b52bf08b65295d49117e790000000000000000000000000000000000000000000000008b6d9e8818d6141f000000000000000000000000000000000000000000000000000000086a23af210000000000000000000000000000000000000000000000000000000000").unwrap();

    let from: Address = "0xa009fa1ac416ec02f6f902a3a4a584b092ae6123".parse().unwrap();
    let to: Address = "0x99999999d116ffa7d76590de2f427d8e15aeb0b8".parse().unwrap();
    let tx = TransactionRequest::default()
        .to(to)
        .from(from)
        .with_input::<Bytes>(input.into())
        .with_gas_limit(350_000);

    let tx = WithOtherFields::new(tx);
    api.anvil_impersonate_account(from).await.unwrap();

    let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
    let status = tx.inner.inner.inner.receipt.status.coerce_status();
    assert!(status);

    let traces = provider.trace_transaction(tx.transaction_hash).await.unwrap();

    assert!(!traces.is_empty());
    match traces[0].trace.action {
        Action::Call(ref call) => {
            assert_eq!(call.from, from);
            assert_eq!(call.to, to);
        }
        _ => unreachable!("unexpected action"),
    }

    let json = serde_json::json!([
        {
            "action": {
                "from": "0xa009fa1ac416ec02f6f902a3a4a584b092ae6123",
                "callType": "call",
                "gas": "0x4fabc",
                "input": "0x30000003000000000000000000000000adda1059a6c6c102b0fa562b9bb2cb9a0de5b1f4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a300000004fffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb980c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f4d2888d29d722226fafa5d9b24f9164c092421e000bb8000000000000004319b52bf08b65295d49117e790000000000000000000000000000000000000000000000008b6d9e8818d6141f000000000000000000000000000000000000000000000000000000086a23af210000000000000000000000000000000000000000000000000000000000",
                "to": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8",
                "value": "0x0"
            },
            "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73",
            "blockNumber": 15314402,
            "result": {
                "gasUsed": "0x1d51b",
                "output": "0x"
            },
            "subtraces": 1,
            "traceAddress": [],
            "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845",
            "transactionPosition": 289,
            "type": "call"
        },
        {
            "action": {
                "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8",
                "callType": "delegatecall",
                "gas": "0x4d594",
                "input": "0x00000004fffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb980c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f4d2888d29d722226fafa5d9b24f9164c092421e000bb8000000000000004319b52bf08b65295d49117e790000000000000000000000000000000000000000000000008b6d9e8818d6141f000000000000000000000000000000000000000000000000000000086a23af21",
                "to": "0xadda1059a6c6c102b0fa562b9bb2cb9a0de5b1f4",
                "value": "0x0"
            },
            "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73",
            "blockNumber": 15314402,
            "result": {
                "gasUsed": "0x1c35f",
                "output": "0x"
            },
            "subtraces": 3,
            "traceAddress": [0],
            "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845",
            "transactionPosition": 289,
            "type": "call"
        },
        {
            "action": {
                "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8",
                "callType": "call",
                "gas": "0x4b6d6",
                "input": "0x16b2da82000000000000000000000000000000000000000000000000000000086a23af21",
                "to": "0xd1663cfb8ceaf22039ebb98914a8c98264643710",
                "value": "0x0"
            },
            "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73",
            "blockNumber": 15314402,
            "result": {
                "gasUsed": "0xd6d",
                "output": "0x0000000000000000000000000000000000000000000000000000000000000000"
            },
            "subtraces": 0,
            "traceAddress": [0, 0],
            "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845",
            "transactionPosition": 289,
            "type": "call"
        },
        {
            "action": {
                "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8",
                "callType": "staticcall",
                "gas": "0x49c35",
                "input": "0x3850c7bd",
                "to": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011",
                "value": "0x0"
            },
            "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73",
            "blockNumber": 15314402,
            "result": {
                "gasUsed": "0xa88",
                "output": "0x000000000000000000000000000000000000004319b52bf08b65295d49117e7900000000000000000000000000000000000000000000000000000000000148a0000000000000000000000000000000000000000000000000000000000000010e000000000000000000000000000000000000000000000000000000000000012c000000000000000000000000000000000000000000000000000000000000012c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"
            },
            "subtraces": 0,
            "traceAddress": [0, 1],
            "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845",
            "transactionPosition": 289,
            "type": "call"
        },
        {
            "action": {
                "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8",
                "callType": "call",
                "gas": "0x48d01",
                "input": "0x128acb0800000000000000000000000099999999d116ffa7d76590de2f427d8e15aeb0b80000000000000000000000000000000000000000000000000000000000000001fffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb98000000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f4d2888d29d722226fafa5d9b24f9164c092421e000bb8000000000000000000000000000000000000000000",
                "to": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011",
                "value": "0x0"
            },
            "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73",
            "blockNumber": 15314402,
            "result": {
                "gasUsed": "0x18c20",
                "output": "0x0000000000000000000000000000000000000000000000008b5116525f9edc3efffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb980"
            },
            "subtraces": 4,
            "traceAddress": [0, 2],
            "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845",
            "transactionPosition": 289,
            "type": "call"
        },
        {
            "action": {
                "from": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011",
                "callType": "call",
                "gas": "0x3802a",
                "input": "0xa9059cbb00000000000000000000000099999999d116ffa7d76590de2f427d8e15aeb0b8000000000000000000000000000000000000000000000986236e1301eaf04680",
                "to": "0xf4d2888d29d722226fafa5d9b24f9164c092421e",
                "value": "0x0"
            },
            "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73",
            "blockNumber": 15314402,
            "result": {
                "gasUsed": "0x31b6",
                "output": "0x0000000000000000000000000000000000000000000000000000000000000001"
            },
            "subtraces": 0,
            "traceAddress": [0, 2, 0],
            "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845",
            "transactionPosition": 289,
            "type": "call"
        },
        {
            "action": {
                "from": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011",
                "callType": "staticcall",
                "gas": "0x34237",
                "input": "0x70a082310000000000000000000000004b5ab61593a2401b1075b90c04cbcdd3f87ce011",
                "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
                "value": "0x0"
            },
            "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73",
            "blockNumber": 15314402,
            "result": {
                "gasUsed": "0x9e6",
                "output": "0x000000000000000000000000000000000000000000000091cda6c1ce33e53b89"
            },
            "subtraces": 0,
            "traceAddress": [0, 2, 1],
            "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845",
            "transactionPosition": 289,
            "type": "call"
        },
        {
            "action": {
                "from": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011",
                "callType": "call",
                "gas": "0x3357e",
                "input": "0xfa461e330000000000000000000000000000000000000000000000008b5116525f9edc3efffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb9800000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f4d2888d29d722226fafa5d9b24f9164c092421e000bb8000000000000000000000000000000000000000000",
                "to": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8",
                "value": "0x0"
            },
            "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73",
            "blockNumber": 15314402,
            "result": {
                "gasUsed": "0x2e8b",
                "output": "0x"
            },
            "subtraces": 1,
            "traceAddress": [0, 2, 2],
            "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845",
            "transactionPosition": 289,
            "type": "call"
        },
        {
            "action": {
                "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8",
                "callType": "call",
                "gas": "0x324db",
                "input": "0xa9059cbb0000000000000000000000004b5ab61593a2401b1075b90c04cbcdd3f87ce0110000000000000000000000000000000000000000000000008b5116525f9edc3e",
                "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
                "value": "0x0"
            },
            "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73",
            "blockNumber": 15314402,
            "result": {
                "gasUsed": "0x2a6e",
                "output": "0x0000000000000000000000000000000000000000000000000000000000000001"
            },
            "subtraces": 0,
            "traceAddress": [0, 2, 2, 0],
            "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845",
            "transactionPosition": 289,
            "type": "call"
        },
        {
            "action": {
                "from": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011",
                "callType": "staticcall",
                "gas": "0x30535",
                "input": "0x70a082310000000000000000000000004b5ab61593a2401b1075b90c04cbcdd3f87ce011",
                "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
                "value": "0x0"
            },
            "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73",
            "blockNumber": 15314402,
            "result": {
                "gasUsed": "0x216",
                "output": "0x00000000000000000000000000000000000000000000009258f7d820938417c7"
            },
            "subtraces": 0,
            "traceAddress": [0, 2, 3],
            "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845",
            "transactionPosition": 289,
            "type": "call"
        }
    ]);

    let expected_traces: Vec<LocalizedTransactionTrace> = serde_json::from_value(json).unwrap();

    // test matching traceAddress
    traces.into_iter().zip(expected_traces).for_each(|(a, b)| {
        assert_eq!(a.trace.trace_address, b.trace.trace_address);
        assert_eq!(a.trace.subtraces, b.trace.subtraces);
        match (a.trace.action, b.trace.action) {
            (Action::Call(a), Action::Call(b)) => {
                assert_eq!(a.from, b.from);
                assert_eq!(a.to, b.to);
            }
            _ => unreachable!("unexpected action"),
        }
    })
}

#[tokio::test(flavor = "multi_thread")]
async fn test_trace_filter() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.ws_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();
    let from_two = accounts[2].address();
    let to_two = accounts[3].address();

    // Test default block ranges.
    // From will be earliest, to will be best/latest
    let tracer = TraceFilter {
        from_block: None,
        to_block: None,
        from_address: vec![],
        to_address: vec![],
        mode: TraceFilterMode::Intersection,
        after: None,
        count: None,
    };

    for i in 0..=5 {
        let tx = TransactionRequest::default().to(to).value(U256::from(i)).from(from);
        let tx = WithOtherFields::new(tx);
        provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
    }

    let traces = api.trace_filter(tracer).await.unwrap();
    assert_eq!(traces.len(), 6);

    // Test filtering by address
    let tracer = TraceFilter {
        from_block: Some(provider.get_block_number().await.unwrap()),
        to_block: None,
        from_address: vec![from_two],
        to_address: vec![to_two],
        mode: TraceFilterMode::Intersection,
        after: None,
        count: None,
    };

    for i in 0..=5 {
        let tx = TransactionRequest::default().to(to).value(U256::from(i)).from(from);
        let tx = WithOtherFields::new(tx);
        provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();

        let tx = TransactionRequest::default().to(to_two).value(U256::from(i)).from(from_two);
        let tx = WithOtherFields::new(tx);
        provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
    }

    let traces = api.trace_filter(tracer).await.unwrap();
    assert_eq!(traces.len(), 6);

    // Test for the following actions:
    // Create (deploy the contract)
    // Call (goodbye function)
    // SelfDestruct (side-effect of goodbye)
    let contract_addr =
        SuicideContract::deploy_builder(provider.clone()).from(from).deploy().await.unwrap();
    let contract = SuicideContract::new(contract_addr, provider.clone());

    // Test TraceActions
    let tracer = TraceFilter {
        from_block: Some(provider.get_block_number().await.unwrap()),
        to_block: None,
        from_address: vec![from, contract_addr],
        to_address: vec![], // Leave as 0 address
        mode: TraceFilterMode::Union,
        after: None,
        count: None,
    };

    // Execute call
    let call = contract.goodbye().from(from);
    let call = call.send().await.unwrap();
    call.get_receipt().await.unwrap();

    // Mine transactions to filter against
    for i in 0..=5 {
        let tx = TransactionRequest::default().to(to_two).value(U256::from(i)).from(from_two);
        let tx = WithOtherFields::new(tx);
        provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
    }

    let traces = api.trace_filter(tracer).await.unwrap();
    assert_eq!(traces.len(), 3);

    // Test Range Error
    let latest = provider.get_block_number().await.unwrap();
    let tracer = TraceFilter {
        from_block: Some(latest),
        to_block: Some(latest + 301),
        from_address: vec![],
        to_address: vec![],
        mode: TraceFilterMode::Union,
        after: None,
        count: None,
    };

    let traces = api.trace_filter(tracer).await;
    assert!(traces.is_err());

    // Test same from and to block is valid
    let latest = provider.get_block_number().await.unwrap();
    let tracer = TraceFilter {
        from_block: Some(latest),
        to_block: Some(latest),
        from_address: vec![],
        to_address: vec![],
        mode: TraceFilterMode::Union,
        after: None,
        count: None,
    };

    let traces = api.trace_filter(tracer).await;
    assert!(traces.is_ok());

    // Test invalid block range
    let latest = provider.get_block_number().await.unwrap();
    let tracer = TraceFilter {
        from_block: Some(latest + 10),
        to_block: Some(latest),
        from_address: vec![],
        to_address: vec![],
        mode: TraceFilterMode::Union,
        after: None,
        count: None,
    };

    let traces = api.trace_filter(tracer).await;
    assert!(traces.is_err());

    // Test after and count
    let tracer = TraceFilter {
        from_block: Some(provider.get_block_number().await.unwrap()),
        to_block: None,
        from_address: vec![],
        to_address: vec![],
        mode: TraceFilterMode::Union,
        after: Some(3),
        count: Some(5),
    };

    for i in 0..=10 {
        let tx = TransactionRequest::default().to(to).value(U256::from(i)).from(from);
        let tx = WithOtherFields::new(tx);
        provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
    }

    let traces = api.trace_filter(tracer).await.unwrap();
    assert_eq!(traces.len(), 5);
}

#[cfg(feature = "js-tracer")]
#[tokio::test(flavor = "multi_thread")]
async fn test_call_tracer_debug_trace_call_js_tracer() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let wallets = handle.dev_wallets().collect::<Vec<_>>();
    let deployer: EthereumWallet = wallets[0].clone().into();
    let provider = http_provider_with_signer(&handle.http_endpoint(), deployer);

    let multicall_contract = Multicall::deploy(&provider).await.unwrap();
    let simple_storage_contract =
        SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap();

    let set_value = simple_storage_contract.setValue("bar".to_string());
    let set_value_calldata = set_value.calldata();

    let internal_call_tx_builder = multicall_contract.aggregate(vec![Multicall::Call {
        target: *simple_storage_contract.address(),
        callData: set_value_calldata.to_owned(),
    }]);

    let internal_call_tx_calldata = internal_call_tx_builder.calldata().to_owned();

    let internal_call_tx = TransactionRequest::default()
        .from(wallets[1].address())
        .to(*multicall_contract.address())
        .with_input(internal_call_tx_calldata);

    let js_tracer_code = r#"
{
data: [],
step: function(log) {
    var op = log.op.toString();
    if (op === "SLOAD") {
    this.data.push(log.getPC() + ": SLOAD " + log.contract.getAddress() + ":" + log.stack.peek(0));
    this.data.push("    Result: " + log.stack.peek(0));
    } else if (op === "SSTORE") {
    this.data.push(log.getPC() + ": SSTORE " + log.contract.getAddress() + ":" + log.stack.peek(1) + " <- " + log.stack.peek(0));
    }
},
result: function() {
    return this.data;
},
fault: function(log) {}
}
"#;

    let result = api
        .debug_trace_call(
            WithOtherFields::new(internal_call_tx),
            Some(BlockId::latest()),
            GethDebugTracingCallOptions::default()
                .with_tracing_options(GethDebugTracingOptions::js_tracer(js_tracer_code)),
        )
        .await
        .unwrap();

    let expected = vec![
        "547: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:0",
        "    Result: 0",
        "1907: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:1",
        "    Result: 1",
        "772: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:1",
        "    Result: 1",
        "835: SSTORE 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:44498830125527143464827115118378702402016761369235290884359940707316142178310 <- 1",
        "919: SSTORE 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:0 <- 80084422859880547211683076133703299733277748156566366325829078699459944778998",
        "712: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:0",
        "    Result: 0",
        "765: SSTORE 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:546584486846459126461364135121053344201067465379 <- 0",
    ];

    let actual: Vec<String> = result
        .try_into_json_value()
        .ok()
        .and_then(|val| val.as_array().cloned())
        .map(|arr| arr.into_iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
        .unwrap_or_default();

    assert_eq!(actual, expected);
}

#[cfg(feature = "js-tracer")]
#[tokio::test(flavor = "multi_thread")]
async fn test_debug_trace_transaction_js_tracer() {
    let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()));
    let (api, handle) = spawn(node_config).await;
    let provider = crate::utils::http_provider(&handle.http_endpoint());

    let wallets = handle.dev_wallets().collect::<Vec<_>>();
    let from = wallets[0].address();
    api.anvil_add_balance(from, U256::MAX).await.unwrap();
    api.anvil_add_balance(wallets[1].address(), U256::MAX).await.unwrap();

    let multicall_contract = Multicall::deploy(&provider).await.unwrap();
    let simple_storage_contract =
        SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap();

    let set_value = simple_storage_contract.setValue("bar".to_string());
    let set_value_calldata = set_value.calldata();

    let internal_call_tx_builder = multicall_contract.aggregate(vec![Multicall::Call {
        target: *simple_storage_contract.address(),
        callData: set_value_calldata.to_owned(),
    }]);

    let internal_call_tx_calldata = internal_call_tx_builder.calldata().to_owned();

    let internal_call_tx = TransactionRequest::default()
        .from(wallets[1].address())
        .to(*multicall_contract.address())
        .with_input(internal_call_tx_calldata)
        .with_gas_limit(1_000_000)
        .with_max_fee_per_gas(100_000_000_000)
        .with_max_priority_fee_per_gas(100_000_000_000);

    let receipt = provider
        .send_transaction(internal_call_tx.into())
        .await
        .unwrap()
        .get_receipt()
        .await
        .unwrap();
    let js_tracer_code = r#"
{
data: [],
step: function(log) {
    var op = log.op.toString();
    var pc = log.getPC();
    var addr = log.contract.getAddress();

    if (op === "SLOAD") {
        this.data.push(pc + ": SLOAD " + addr + ":" + log.stack.peek(0));
        this.data.push("    Result: " + log.stack.peek(0));
    } else if (op === "SSTORE") {
        this.data.push(pc + ": SSTORE " + addr + ":" + log.stack.peek(1) + " <- " + log.stack.peek(0));
    } 
},
result: function() {
    return this.data;
},
fault: function(log) {}
}
"#;

    let expected = vec![
        "547: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:0",
        "    Result: 0",
        "1907: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:1",
        "    Result: 1",
        "772: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:1",
        "    Result: 1",
        "835: SSTORE 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:44498830125527143464827115118378702402016761369235290884359940707316142178310 <- 1",
        "919: SSTORE 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:0 <- 80084422859880547211683076133703299733277748156566366325829078699459944778998",
        "712: SLOAD 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:0",
        "    Result: 0",
        "765: SSTORE 231,241,114,94,119,52,206,40,143,131,103,225,187,20,62,144,187,63,5,18:546584486846459126461364135121053344201067465379 <- 0",
    ];
    let result = api
        .debug_trace_transaction(
            receipt.transaction_hash,
            GethDebugTracingOptions::js_tracer(js_tracer_code),
        )
        .await
        .unwrap();

    let actual: Vec<String> = result
        .try_into_json_value()
        .ok()
        .and_then(|val| val.as_array().cloned())
        .map(|arr| arr.into_iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
        .unwrap_or_default();

    assert_eq!(actual, expected);
}

#[tokio::test(flavor = "multi_thread")]
async fn test_call_tracer_debug_trace_call_pre_state_tracer() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let wallets = handle.dev_wallets().collect::<Vec<_>>();
    let deployer: EthereumWallet = wallets[0].clone().into();
    let provider = http_provider_with_signer(&handle.http_endpoint(), deployer);

    let multicall_contract = Multicall::deploy(&provider).await.unwrap();
    let simple_storage_contract =
        SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap();

    let set_value = simple_storage_contract.setValue("bar".to_string());
    let set_value_calldata = set_value.calldata();

    let internal_call_tx_builder = multicall_contract.aggregate(vec![Multicall::Call {
        target: *simple_storage_contract.address(),
        callData: set_value_calldata.to_owned(),
    }]);

    let internal_call_tx_calldata = internal_call_tx_builder.calldata().to_owned();

    let internal_call_tx = TransactionRequest::default()
        .from(wallets[1].address())
        .to(*multicall_contract.address())
        .with_input(internal_call_tx_calldata);

    let result = api
        .debug_trace_call(
            WithOtherFields::new(internal_call_tx),
            Some(BlockId::latest()),
            GethDebugTracingCallOptions::default().with_tracing_options(
                GethDebugTracingOptions::prestate_tracer(PreStateConfig::default()),
            ),
        )
        .await
        .unwrap();

    let expected = r#"
{
  "0x0000000000000000000000000000000000000000": {
    "balance": "0x12670f"
  },
  "0x5fbdb2315678afecb367f032d93f642f64180aa3": {
    "balance": "0x0",
    "nonce": 1
  },
  "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": {
    "balance": "0x56bc75e2d63100000"
  },
  "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512": {
    "balance": "0x0",
    "nonce": 1,
    "storage": {
      "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "0x0000000000000000000000000000000000000000000000000000000000000001": "0x696e69742076616c756500000000000000000000000000000000000000000014",
      "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6": "0x0000000000000000000000000000000000000000000000000000000000000000"
    }
  }
}
    "#;
    let expected: HashMap<Address, AccountState> = serde_json::from_str(expected).unwrap();

    match result {
        GethTrace::PreStateTracer(PreStateFrame::Default(pre_state_mode)) => {
            for (addr, acc) in pre_state_mode.0 {
                let expected_acc = expected.get(&addr).unwrap();
                assert_eq!(acc.balance, expected_acc.balance);
                assert_eq!(acc.nonce, expected_acc.nonce);
                let expected_storage = &expected_acc.storage;
                for (slot, value) in acc.storage {
                    assert_eq!(value, *expected_storage.get(&slot).unwrap())
                }
            }
        }
        _ => unreachable!(),
    }
}

#[tokio::test(flavor = "multi_thread")]
async fn test_debug_trace_transaction_pre_state_tracer() {
    let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()));
    let (api, handle) = spawn(node_config).await;
    let provider = crate::utils::http_provider(&handle.http_endpoint());

    let wallets = handle.dev_wallets().collect::<Vec<_>>();
    let from = wallets[0].address();
    api.anvil_add_balance(from, U256::MAX).await.unwrap();
    api.anvil_add_balance(wallets[1].address(), U256::MAX).await.unwrap();

    let multicall_contract = Multicall::deploy(&provider).await.unwrap();
    let simple_storage_contract =
        SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap();

    let set_value = simple_storage_contract.setValue("bar".to_string());
    let set_value_calldata = set_value.calldata();

    let internal_call_tx_builder = multicall_contract.aggregate(vec![Multicall::Call {
        target: *simple_storage_contract.address(),
        callData: set_value_calldata.to_owned(),
    }]);

    let internal_call_tx_calldata = internal_call_tx_builder.calldata().to_owned();

    let internal_call_tx = TransactionRequest::default()
        .from(wallets[1].address())
        .to(*multicall_contract.address())
        .with_input(internal_call_tx_calldata)
        .with_gas_limit(1_000_000)
        .with_max_fee_per_gas(100_000_000_000)
        .with_max_priority_fee_per_gas(100_000_000_000);

    let receipt = provider
        .send_transaction(internal_call_tx.into())
        .await
        .unwrap()
        .get_receipt()
        .await
        .unwrap();

    let result = api
        .debug_trace_transaction(
            receipt.transaction_hash,
            GethDebugTracingOptions::prestate_tracer(PreStateConfig::default()),
        )
        .await
        .unwrap();

    let expected = r#"
{
  "0x0000000000000000000000000000000000000000": {
    "balance": "1206031000000000"
  },
  "0x5fbdb2315678afecb367f032d93f642f64180aa3": {
    "balance": "0x0",
    "nonce": 1
  },
  "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": {
    "balance": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
  },
  "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512": {
    "balance": "0x0",
    "nonce": 1,
    "storage": {
      "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "0x0000000000000000000000000000000000000000000000000000000000000001": "0x696e69742076616c756500000000000000000000000000000000000000000014",
      "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6": "0x0000000000000000000000000000000000000000000000000000000000000000"
    }
  }
}
    "#;
    let expected: HashMap<Address, AccountState> = serde_json::from_str(expected).unwrap();

    match result {
        GethTrace::PreStateTracer(PreStateFrame::Default(pre_state_mode)) => {
            for (addr, acc) in pre_state_mode.0 {
                let expected_acc = expected.get(&addr).unwrap();
                assert_eq!(acc.balance, expected_acc.balance);
                assert_eq!(acc.nonce, expected_acc.nonce);
                let expected_storage = &expected_acc.storage;
                for (slot, value) in acc.storage {
                    assert_eq!(value, *expected_storage.get(&slot).unwrap())
                }
            }
        }
        _ => unreachable!(),
    }
}

#[tokio::test(flavor = "multi_thread")]
async fn test_trace_replay_block_transactions_local() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    api.anvil_set_auto_mine(false).await.unwrap();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();
    let amount = U256::from(1000000u64);

    // Send first transaction
    let tx1 = TransactionRequest::default().to(to).value(amount).from(from);
    let tx1 = WithOtherFields::new(tx1);
    let pending_tx1 = provider.send_transaction(tx1).await.unwrap();

    // Send second transaction with different value
    let tx2 = TransactionRequest::default().to(to).value(amount).from(from);
    let tx2 = WithOtherFields::new(tx2);
    let pending_tx2 = provider.send_transaction(tx2).await.unwrap();

    api.mine_one().await.unwrap();
    let receipt1 = pending_tx1.get_receipt().await.unwrap();
    let receipt2 = pending_tx2.get_receipt().await.unwrap();

    let block_number = receipt2.block_number.unwrap();

    // Replay the block transactions with call trace type
    // Pass block number as hex string as per Ethereum RPC spec
    let results = api
        .trace_replay_block_transactions(
            block_number.into(),
            vec![TraceType::Trace, TraceType::VmTrace, TraceType::StateDiff].into_iter().collect(),
        )
        .await
        .unwrap();

    // Verify we have traces for both transactions
    assert_eq!(results.len(), 2, "Should have traces for 2 transactions");

    // Verify first transaction hash matches
    assert_eq!(results[0].transaction_hash, receipt1.transaction_hash);

    // Verify second transaction hash matches
    assert_eq!(results[1].transaction_hash, receipt2.transaction_hash);

    // Verify trace types are present and accurate
    for result in results {
        let full_trace = &result.full_trace;

        // Verify Trace (call trace) is present and accurate
        assert!(!full_trace.trace.is_empty(), "Trace should not be empty");
        let first_trace = &full_trace.trace[0];
        match &first_trace.action {
            Action::Call(call) => {
                assert_eq!(call.from, from, "Call from address should match");
                assert_eq!(call.to, to, "Call to address should match");
            }
            _ => panic!("Expected Call action, got {:?}", first_trace.action),
        }

        // Verify VmTrace is present
        assert!(full_trace.vm_trace.is_some(), "VmTrace should be present when requested");

        // Verify StateDiff is present
        assert!(full_trace.state_diff.is_some(), "StateDiff should be present when requested");
        // Verify balance change is correct in state diff
        let ChangedType::<U256> { from, to } =
            full_trace.state_diff.as_ref().unwrap().get(&to).unwrap().balance.as_changed().unwrap();
        assert_eq!(
            to.checked_sub(*from).unwrap(),
            amount,
            "Incorrect balance change in state diff"
        );
    }
}

#[tokio::test(flavor = "multi_thread")]
async fn test_trace_replay_transaction() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();
    let amount = U256::from(12345);

    let tx = TransactionRequest::default().to(to).value(amount).from(from);
    let tx = WithOtherFields::new(tx);
    let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();

    let result: TraceResults = provider
        .client()
        .request(
            "trace_replayTransaction",
            (receipt.transaction_hash, vec![TraceType::Trace, TraceType::StateDiff]),
        )
        .await
        .unwrap();

    assert!(!result.trace.is_empty());
    match &result.trace[0].action {
        Action::Call(call) => {
            assert_eq!(call.from, from);
            assert_eq!(call.to, to);
        }
        _ => panic!("Expected Call action, got {:?}", result.trace[0].action),
    }

    let ChangedType::<U256> { from, to } =
        result.state_diff.as_ref().unwrap().get(&to).unwrap().balance.as_changed().unwrap();
    assert_eq!(to.checked_sub(*from).unwrap(), amount);
}

#[tokio::test(flavor = "multi_thread")]
async fn test_debug_trace_block_by_number() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();
    let amount = U256::from(1000);

    let tx = TransactionRequest::default().to(to).value(amount).from(from);
    let tx = WithOtherFields::new(tx);
    let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
    let block_number = receipt.block_number.unwrap();

    let traces = api
        .backend
        .debug_trace_block_by_number(
            BlockNumberOrTag::Number(block_number),
            GethDebugTracingOptions::default()
                .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer)),
        )
        .await
        .unwrap();

    assert_eq!(traces.len(), 1);

    match &traces[0] {
        alloy_rpc_types::trace::geth::TraceResult::Success { result, .. } => match result {
            GethTrace::CallTracer(call_frame) => {
                assert_eq!(call_frame.from, from);
                assert_eq!(call_frame.to.unwrap(), to);
                assert_eq!(call_frame.value, Some(amount));
            }
            _ => unreachable!("expected CallTracer"),
        },
        alloy_rpc_types::trace::geth::TraceResult::Error { error, .. } => {
            panic!("trace failed: {error}");
        }
    }
}

#[tokio::test(flavor = "multi_thread")]
async fn test_debug_trace_block_by_hash() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();
    let amount = U256::from(2000);

    let tx = TransactionRequest::default().to(to).value(amount).from(from);
    let tx = WithOtherFields::new(tx);
    let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
    let block_hash = receipt.block_hash.unwrap();

    let traces = api
        .backend
        .debug_trace_block_by_hash(
            block_hash,
            GethDebugTracingOptions::default()
                .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer)),
        )
        .await
        .unwrap();

    assert_eq!(traces.len(), 1);

    match &traces[0] {
        alloy_rpc_types::trace::geth::TraceResult::Success { result, .. } => match result {
            GethTrace::CallTracer(call_frame) => {
                assert_eq!(call_frame.from, from);
                assert_eq!(call_frame.to.unwrap(), to);
                assert_eq!(call_frame.value, Some(amount));
            }
            _ => unreachable!("expected CallTracer"),
        },
        alloy_rpc_types::trace::geth::TraceResult::Error { error, .. } => {
            panic!("trace failed: {error}");
        }
    }
}

#[tokio::test(flavor = "multi_thread")]
async fn test_debug_trace_block() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();
    let amount = U256::from(3000);

    let tx = TransactionRequest::default().to(to).value(amount).from(from);
    let tx = WithOtherFields::new(tx);
    let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
    let block_hash = receipt.block_hash.unwrap();
    let block = api.backend.get_block(BlockId::Hash(block_hash.into())).unwrap();

    let mut rlp_block = Vec::new();
    block.encode(&mut rlp_block);

    let traces = provider
        .debug_trace_block(
            &rlp_block,
            GethDebugTracingOptions::default()
                .with_tracer(GethDebugTracerType::from(GethDebugBuiltInTracerType::CallTracer)),
        )
        .await
        .unwrap();

    assert_eq!(traces.len(), 1);

    match &traces[0] {
        alloy_rpc_types::trace::geth::TraceResult::Success { result, .. } => match result {
            GethTrace::CallTracer(call_frame) => {
                assert_eq!(call_frame.from, from);
                assert_eq!(call_frame.to.unwrap(), to);
                assert_eq!(call_frame.value, Some(amount));
            }
            _ => unreachable!("expected CallTracer"),
        },
        alloy_rpc_types::trace::geth::TraceResult::Error { error, .. } => {
            panic!("trace failed: {error}");
        }
    }
}
