import copy
import re
from typing import Any, ClassVar, cast

import pytest
from eth_pydantic_types import HexBytes, HexBytes32
from eth_typing import HexAddress, HexStr
from ethpm_types import ContractType, ErrorABI
from ethpm_types.abi import ABIType, EventABI, MethodABI
from evm_trace import CallTreeNode, CallType

from ape.api.networks import ForkedNetworkAPI, NetworkAPI
from ape.exceptions import CustomError, DecodingError, NetworkError, NetworkNotFoundError
from ape.types.address import AddressType
from ape.types.units import CurrencyValueComparable
from ape.utils.misc import DEFAULT_LOCAL_TRANSACTION_ACCEPTANCE_TIMEOUT, LOCAL_NETWORK_NAME
from ape_ethereum.ecosystem import BLUEPRINT_HEADER, BaseEthereumConfig, Block, Ethereum
from ape_ethereum.trace import TransactionTrace
from ape_ethereum.transactions import (
    DynamicFeeTransaction,
    Receipt,
    SetCodeTransaction,
    SharedBlobReceipt,
    SharedBlobTransaction,
    StaticFeeTransaction,
    TransactionType,
)

LOG = {
    "removed": False,
    "logIndex": "0x0",
    "transactionIndex": "0x0",
    "transactionHash": "0x74dd040dfa06f0af9af8ca95d7aae409978400151c746f55ecce19e7356cfc5a",
    "blockHash": "0x2c99950b07accf3e442512a3352a11e6fed37b2331de5f71b7743b357d96e4e8",
    "blockNumber": "0xa946ac",
    "address": "0x274b028b03a250ca03644e6c578d81f019ee1323",
    "data": "0xabffd4675206dab5d04a6b0d62c975049665d1f512f29f303908abdd20bc08a100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000744796e616d696300000000000000000000000000000000000000000000000000",
    "topics": [
        "0xa84473122c11e32cd505595f246a28418b8ecd6cf819f4e3915363fad1b8f968",
        "0x0000000000000000000000000000000000000000000000000000000000000006",
        "0x9f3d45ac20ccf04b45028b8080bb191eab93e29f7898ed43acf480dd80bba94d",
    ],
}
CUSTOM_ECOSYSTEM_NAME = "custom-ecosystem"


def make_method_abi(name: str, inputs=None, outputs=None) -> MethodABI:
    inputs = inputs or []
    outputs = outputs or []
    return MethodABI.model_validate(
        {
            "type": "function",
            "name": name,
            "stateMutability": "nonpayable",
            "inputs": inputs,
            "outputs": outputs,
        }
    )


@pytest.fixture
def event_abi(vyper_contract_instance):
    return vyper_contract_instance.NumberChange.abi


@pytest.fixture
def configured_custom_ecosystem(
    custom_networks_config_dict, project, networks, custom_network_name_0
):
    data = copy.deepcopy(custom_networks_config_dict)
    data["networks"]["custom"][0]["ecosystem"] = CUSTOM_ECOSYSTEM_NAME

    # Also ensure we can configure the custom ecosystem as if
    # it were from a plugin.
    data[CUSTOM_ECOSYSTEM_NAME] = {"default_network": custom_network_name_0}

    with project.temp_config(**data):
        yield


def test_name(ethereum):
    assert ethereum.name == "ethereum"


def test_request_header(ethereum):
    actual = ethereum.request_header
    expected = {"User-Agent": "ape-ethereum"}
    assert actual == expected


def test_request_header_subclass():
    class L2(Ethereum):
        name: str = "l2"

    l2 = L2()
    actual = l2.request_header
    expected = {"User-Agent": "ape-l2"}
    assert actual == expected


def test_name_when_custom(configured_custom_ecosystem, networks):
    ecosystem = networks.get_ecosystem(CUSTOM_ECOSYSTEM_NAME)
    actual = ecosystem.name
    expected = CUSTOM_ECOSYSTEM_NAME
    assert actual == expected


@pytest.mark.parametrize(
    "address",
    (
        "0x63953eB1B3D8DB28334E7C1C69456C851F934199".lower(),
        0x63953EB1B3D8DB28334E7C1C69456C851F934199,
    ),
)
def test_decode_address(ethereum, address):
    expected = "0x63953eB1B3D8DB28334E7C1C69456C851F934199"
    actual = ethereum.decode_address(address)
    assert actual == expected


def test_encode_address(ethereum):
    raw_address = "0x63953eB1B3D8DB28334E7C1C69456C851F934199"
    address = AddressType(HexAddress(HexStr(raw_address)))
    actual = ethereum.encode_address(address)
    assert actual == raw_address


def test_encode_calldata(ethereum, address):
    abi = make_method_abi(
        "callMe",
        inputs=[
            ABIType(name="a", type="bytes4"),
            ABIType(name="b", type="address"),
            ABIType(name="c", type="uint256"),
            ABIType(name="d", type="bytes4[]"),
        ],
    )
    byte_array = ["0x456", "0x678"]
    values = ("0x0123", address, HexBytes(55), byte_array)

    actual = ethereum.encode_calldata(abi, *values)
    expected = HexBytes(
        # 0x123
        "0123000000000000000000000000000000000000000000000000000000000000"
        # address
        "000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045"
        # HexBytes(55)
        "0000000000000000000000000000000000000000000000000000000000000037"
        # byte_array
        "0000000000000000000000000000000000000000000000000000000000000080"
        # length
        "0000000000000000000000000000000000000000000000000000000000000002"
        # first bytes4
        "0456000000000000000000000000000000000000000000000000000000000000"
        # second bytes4
        "0678000000000000000000000000000000000000000000000000000000000000"
    )
    assert actual == expected


@pytest.mark.parametrize(
    "sequence_type,item_type",
    [
        (list, str),
        (tuple, str),
        (list, HexBytes),
        (tuple, HexBytes),
    ],
)
def test_encode_calldata_byte_array(ethereum, sequence_type, item_type):
    """
    Tests against a bug where we could not pass a tuple of HexStr
    for a byte-array.
    """
    abi = make_method_abi(
        "mint", inputs=[{"name": "leaf", "type": "bytes32"}, {"name": "proof", "type": "bytes32[]"}]
    )
    hexstr_array = sequence_type(
        (
            item_type("0xfadbd3"),
            item_type("0x9c6b2c"),
            item_type("0xc5d246"),
        )
    )
    actual = ethereum.encode_calldata(abi, "0x0123", hexstr_array)
    assert isinstance(actual, bytes)


def test_encode_calldata_nested_structs(ethereum):
    abi = make_method_abi(
        "check",
        inputs=[
            ABIType(
                name="data",
                type="tuple",
                components=[
                    ABIType(
                        name="tokenIn", type="address", components=None, internal_type="address"
                    ),
                    ABIType(
                        name="amountIn", type="uint256", components=None, internal_type="uint256"
                    ),
                    ABIType(
                        name="minAmountOut",
                        type="uint256",
                        components=None,
                        internal_type="uint256",
                    ),
                    ABIType(
                        name="path",
                        type="tuple",
                        components=[
                            ABIType(
                                name="pairBinSteps",
                                type="uint256[]",
                                components=None,
                                internal_type="uint256[]",
                            ),
                            ABIType(
                                name="versions",
                                type="uint8[]",
                                components=None,
                                internal_type="enum SwapWrapper.Version[]",
                            ),
                            ABIType(
                                name="tokenPath",
                                type="address[]",
                                components=None,
                                internal_type="address[]",
                            ),
                        ],
                        internal_type="struct SwapWrapper.Path",
                    ),
                ],
                internal_type="struct SwapWrapper.SwapData",
            )
        ],
        outputs=[ABIType(name="", type="bool", components=None, internal_type="bool")],
    )
    calldata = {
        "tokenIn": "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7",
        "amountIn": 1000000000000000000,
        "minAmountOut": 10000000,
        "path": {
            "pairBinSteps": [0],
            "versions": [0],
            "tokenPath": [
                "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7",
                "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
            ],
        },
    }
    actual = ethereum.encode_calldata(abi, calldata)
    expected = HexBytes(
        "0x000000000000000000000000000000000000000000000000000000000000002"
        "0000000000000000000000000b31f66aa3c1e785363f0875a1b74e27b85fd66c7"
        "0000000000000000000000000000000000000000000000000de0b6b3a76400000"
        "00000000000000000000000000000000000000000000000000000000098968000"
        "00000000000000000000000000000000000000000000000000000000000080000"
        "00000000000000000000000000000000000000000000000000000000000600000"
        "0000000000000000000000000000000000000000000000000000000000a000000"
        "000000000000000000000000000000000000000000000000000000000e0000000"
        "00000000000000000000000000000000000000000000000000000000010000000"
        "00000000000000000000000000000000000000000000000000000000000000000"
        "00000000000000000000000000000000000000000000000000000001000000000"
        "00000000000000000000000000000000000000000000000000000000000000000"
        "00000000000000000000000000000000000000000000000000000200000000000"
        "0000000000000b31f66aa3c1e785363f0875a1b74e27b85fd66c7000000000000"
        "000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e"
    )
    assert actual == expected


def test_block_handles_snake_case_parent_hash(eth_tester_provider, sender, receiver):
    # Transaction to change parent hash of next block
    sender.transfer(receiver, "1 gwei")

    # Replace 'parentHash' key with 'parent_hash'
    latest_block = eth_tester_provider.get_block("latest")
    latest_block_dict = eth_tester_provider.get_block("latest").model_dump()
    latest_block_dict["parent_hash"] = latest_block_dict.pop("parentHash")

    redefined_block = Block.model_validate(latest_block_dict)
    assert redefined_block.parent_hash == latest_block.parent_hash


def test_transaction_acceptance_timeout(project, networks):
    assert (
        networks.provider.network.transaction_acceptance_timeout
        == DEFAULT_LOCAL_TRANSACTION_ACCEPTANCE_TIMEOUT
    )
    new_value = DEFAULT_LOCAL_TRANSACTION_ACCEPTANCE_TIMEOUT + 1
    timeout_config = {"ethereum": {"local": {"transaction_acceptance_timeout": new_value}}}
    with project.temp_config(**timeout_config):
        assert networks.provider.network.transaction_acceptance_timeout == new_value


def test_decode_logs(ethereum, vyper_contract_instance):
    abi = vyper_contract_instance.NumberChange.abi
    result = [x for x in ethereum.decode_logs([LOG], abi)]
    assert len(result) == 1
    assert result[0].model_dump() == {
        "event_name": "NumberChange",
        "contract_address": "0x274b028b03A250cA03644E6c578D81f019eE1323",
        "event_arguments": {
            "newNum": 6,
            "dynIndexed": HexBytes(
                "0x9f3d45ac20ccf04b45028b8080bb191eab93e29f7898ed43acf480dd80bba94d"
            ),
            "b": HexBytes("0xabffd4675206dab5d04a6b0d62c975049665d1f512f29f303908abdd20bc08a1"),
            "prevNum": 0,
            "dynData": "Dynamic",
        },
        "transaction_hash": "0x74dd040dfa06f0af9af8ca95d7aae409978400151c746f55ecce19e7356cfc5a",
        "block_number": 11093676,
        "block_hash": "0x2c99950b07accf3e442512a3352a11e6fed37b2331de5f71b7743b357d96e4e8",
        "log_index": 0,
        "transaction_index": 0,
    }


def test_decode_logs_empty_list(ethereum, event_abi):
    actual = [x for x in ethereum.decode_logs([], event_abi)]
    assert actual == []


def test_decode_logs_with_struct_from_interface(ethereum):
    abi = EventABI.model_validate(
        {
            "type": "event",
            "name": "ConditionalOrderCreated",
            "inputs": [
                {"name": "owner", "type": "address", "internalType": "address", "indexed": True},
                {
                    "name": "params",
                    "type": "tuple",
                    "components": [
                        {
                            "name": "handler",
                            "type": "address",
                            "internalType": "contract IConditionalOrder",
                        },
                        {"name": "salt", "type": "bytes32", "internalType": "bytes32"},
                        {"name": "staticInput", "type": "bytes", "internalType": "bytes"},
                    ],
                    "internalType": "struct IConditionalOrder.ConditionalOrderParams",
                    "indexed": False,
                },
            ],
            "anonymous": False,
        }
    )
    logs = [
        {
            "address": "0xfdafc9d1902f4e0b84f65f49f244b32b31013b74",
            "blockHash": "0xec1b8e18412dd91114c0b3ea03c41d02243d60cd05661861df5765fb5ec462c6",
            "blockNumber": "0x11101cc",
            "data": "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000006cf1e9ca41f7611def408122793c358a3d11e5a5000000000000000000000000000000000000000000000000000000189e491e96000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001400000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000e7602ca44f83a5e9ba8bd14125ddcb295f3d63bd00000000000000000000000000000000000000000000018eb03406b6af33c8c3000000000000000000000000000000000000000000000000345d7669a1e8815700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000012c00000000000000000000000000000000000000000000000000000000000000007b9bc44550e2cc369010139a1d3a5a6ec6d65910379e0ee35a7c3cdf24cb12a7",
            "logIndex": "0x97",
            "removed": False,
            "topics": [
                "0x2cceac5555b0ca45a3744ced542f54b56ad2eb45e521962372eef212a2cbf361",
                "0x000000000000000000000000e7602ca44f83a5e9ba8bd14125ddcb295f3d63bd",
            ],
            "transactionHash": "0x55859e74e591100e0f37f146abc0dbebe2252eaf5f533319ca77427745b85ceb",
            "transactionIndex": "0x45",
        }
    ]
    actual = list(ethereum.decode_logs(logs, abi))
    assert len(actual) == 1
    assert actual[0].owner == "0xE7602Ca44f83a5E9Ba8BD14125dDcb295f3D63BD"
    assert actual[0].params == [
        "0x6cf1e9ca41f7611def408122793c358a3d11e5a5",
        HexBytes("0x000000000000000000000000000000000000000000000000000000189e491e96"),
        HexBytes(
            "0x0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000"
            "00000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000"
            "00000000e7602ca44f83a5e9ba8bd14125ddcb295f3d63bd0000000000000000000000000"
            "0000000000000000000018eb03406b6af33c8c30000000000000000000000000000000000"
            "00000000000000345d7669a1e881570000000000000000000000000000000000000000000"
            "0000000000000000000000000000000000000000000000000000000000000000000000000"
            "0000000000020000000000000000000000000000000000000000000000000000000000000"
            "12c00000000000000000000000000000000000000000000000000000000000000007b9bc4"
            "4550e2cc369010139a1d3a5a6ec6d65910379e0ee35a7c3cdf24cb12a7"
        ),
    ]


def test_decode_block_when_hash_is_none(ethereum):
    # When using some providers, such as hardhat, the hash of the pending block is None
    block_data_with_none_hash: dict[str, Any] = {
        "number": None,
        "hash": None,
        "parentHash": HexBytes(
            "0xcb94e150c06faee9ab2bf12a40b0937ac9eab1879c733ebe3249aafbba2f80b1"
        ),
        "nonce": None,
        "mixHash": None,
        "sha3Uncles": HexBytes(
            "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
        ),
        "logsBloom": None,
        "transactionsRoot": HexBytes(
            "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
        ),
        "stateRoot": HexBytes("0x8728474146565003152f9cee496de043fd68566dabdb06116a0d5bfc63e1a5a9"),
        "receiptsRoot": HexBytes(
            "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
        ),
        "miner": "0xC014BA5EC014ba5ec014Ba5EC014ba5Ec014bA5E",
        "difficulty": 131072,
        "totalDifficulty": 131073,
        "extraData": HexBytes("0x"),
        "size": 513,
        "gasLimit": 0,
        "gasUsed": 0,
        "timestamp": 1660932629,
        "transactions": [],
        "uncles": [],
        "baseFeePerGas": 0,
    }
    actual = ethereum.decode_block(block_data_with_none_hash)
    assert actual.hash is None
    assert actual.gas_limit == 0


def test_decode_block_with_hex_values(ethereum):
    """
    When using WS providers directly, the values are all hex strings (more raw).
    This test ensures we can still decode blocks that are in this form.
    """
    raw_block = {
        "baseFeePerGas": "0x62d76fae2",
        "difficulty": "0x0",
        "extraData": "0x506f776572656420627920626c6f58726f757465",
        "gasLimit": "0x1c9c380",
        "gasUsed": "0x91b5e5",
        "hash": "0x925cac44d8bac5df3e3eeed6379b3924d6769a054b3c4079899c1d9b442a4041",
        "logsBloom": "0x7823d304ab8c72f91270a023c11482f341a08a00c91b00f8859906c8e9f22e04b19d311e422cfa79d8107e2b901fc7d9c62fcd2d8a673802f500f4a023693525a2ce66481cc51d1a4e97422b8424912b8c719c6821589a240d5015c5cbe4de46b3b60ad43621cd2f540119b3c938d88f011a49d755a31cc415f34896596912d40b0397d0c544571704ea001c56303f02086e16a3a3b7168f443d224034102ca4c6b21456932dae401c8da7c51d36ae3804c8a084811802c708ae94b7010302100700a28f156d2ca2d61420320e82e8b9927b626bc0ec003dfa09009e3d1668798fb0f8c81a205eb100ee8ea2e20c6041b2d8b2b01bd4d14e8acfaa317049d654",
        "miner": "0x6b3a8798e5fb9fc5603f3ab5ea2e8136694e55d0",
        "mixHash": "0x0493af8ac5b7de66c7660c22a703a54069cbd561a2767537eaef994bd77e084b",
        "nonce": "0x0000000000000000",
        "number": "0x11234df",
        "parentHash": "0x47651c13bda353d1bbc610bbdb8a9aa1436629f05f8a32a27f143200da698df8",
        "receiptsRoot": "0xa5abe7eeac74aa03ea82421fcef9b74d1822cfe0ee4b29a8dc01eef31e7d0019",
        "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
        "size": "0x1cf02",
        "stateRoot": "0x59eb1dfb098361d1f74dffc8436da680715f671dc3f02be28f4db3f44b1a09bf",
        "timestamp": "0x64e4b0cb",
        "transactionsRoot": "0xf2e3c3e4fbe06ff882f3deb8a7ec8cbbe09e55b2c995d93dd0a36d5e843f3efc",
    }
    actual = ethereum.decode_block(raw_block)
    assert actual.difficulty == 0


def test_decode_logs_topics_not_first(ethereum):
    """
    Tests against a condition where we could not decode logs if the
    topics were not defined first.
    """
    abi_dict = {
        "anonymous": False,
        "inputs": [
            {"indexed": False, "internalType": "address", "name": "sender", "type": "address"},
            {"indexed": True, "internalType": "address", "name": "owner", "type": "address"},
            {"indexed": True, "internalType": "int24", "name": "tickLower", "type": "int24"},
            {"indexed": True, "internalType": "int24", "name": "tickUpper", "type": "int24"},
            {"indexed": False, "internalType": "uint128", "name": "amount", "type": "uint128"},
            {"indexed": False, "internalType": "uint256", "name": "amount0", "type": "uint256"},
            {"indexed": False, "internalType": "uint256", "name": "amount1", "type": "uint256"},
        ],
        "name": "Mint",
        "type": "event",
    }
    abi = EventABI.model_validate(abi_dict)
    logs = [
        {
            "address": "0x3416cf6c708da44db2624d63ea0aaef7113527c6",
            "blockHash": "0x488f23ba55f64bf1aac02ee7278b70c3f4bb2fb57b8aaa6ab3b481f1809f18ea",
            "blockNumber": "0xcfa869",
            "data": "0x000000000000000000000000c36442b4a4522e871399cd717abdd847ab11fe8800000000000000000000000000000000000000000000000000821d6b102660fb000000000000000000000000000000000000000000000000000009fde8545338000000000000000000000000000000000000000000000000000003548cf35031",
            "logIndex": "0x7a",
            "removed": False,
            "topics": [
                "0x7a53080ba414158be7ec69b987b5fb7d07dee101fe85488f0853ae16239d0bde",
                "0x000000000000000000000000c36442b4a4522e871399cd717abdd847ab11fe88",
                "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc",
                "0x0000000000000000000000000000000000000000000000000000000000000004",
            ],
            "transactionHash": "0xf093a630478562d03e3b2476a5b0551609722747d442a462579fa02e1332a941",
            "transactionIndex": "0x41",
        }
    ]
    actual = list(ethereum.decode_logs(logs, abi))
    assert actual


def test_decode_receipt(eth_tester_provider, ethereum):
    receipt_data = {
        "provider": eth_tester_provider,
        "required_confirmations": 0,
        "blockHash": HexBytes("0x8988adc8a0b346526f1d769841b7464e38a282b25ed346f1f810be8cb7393bc2"),
        "blockNumber": 2,
        "from": "0x318b469BBa396AEc2C60342F9441be36A1945174",
        "gas": 1764431,
        "hash": HexBytes("0x58d086e6b7ecf55c92a5fe420d870ff2eebe99824a711a3cdbe29f497a0e534c"),
        "input": " ",
        "nonce": 1,
        "to": None,
        "transactionIndex": 0,
        "value": 0,
        "v": 1,
        "r": HexBytes("0xfa532141efbfa5bbeaa542cf68bfe5df97dd18b696b8a183f74714758a646238"),
        "s": HexBytes("0x1b80edf76bb6f0295ff847c1b99eb2928ea3533fe41125cfe0fa17fdc634b938"),
        "type": 2,
        "accessList": [],
        "chainId": 31337,
        "gasPrice": 0,
        "maxFeePerGas": 0,
        "maxPriorityFeePerGas": 0,
        "transactionHash": HexBytes(
            "0x58d086e6b7ecf55c92a5fe420d870ff2eebe99824a711a3cdbe29f497a0e534c"
        ),
        "cumulativeGasUsed": 1764431,
        "gasUsed": 1764431,
        "contractAddress": "0xC072c85922b7233998B2Ab990fEFdE80218Ca63F",
        "logs": [],
        "logsBloom": HexBytes(
            "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
        ),
        "status": 1,
        "effectiveGasPrice": 0,
    }
    receipt = ethereum.decode_receipt(receipt_data)
    assert type(receipt) is Receipt  # NOTE: Purposely not using isinstance()

    # Tests against bug where input data would come back improperly
    assert receipt.data == HexBytes(
        " "
    )


def test_decode_receipt_from_etherscan(eth_tester_provider, ethereum):
    receipt = ethereum.decode_receipt(
        {
            "blockNumber": "11291970",
            "timeStamp": "1661846925",
            "hash": "0x5780b43d819035ed1fa079171bdce7f0bbeaa6b01f201f8985d279a66cfc6844",
            "nonce": "0",
            "blockHash": "0x3175f953c1da4bf3d15b853dae4a150ae44e2e71380936463e89142c12961968",
            "transactionIndex": "31",
            "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
            "to": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
            "value": "0",
            "gas": "4712388",
            "gasPrice": "1499999989",
            "isError": "0",
            "input": "0x608060405234801561000f575f80fd5b506101248061001d5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c8063f207564e14602a575b5f80fd5b6039603536600460b4565b603b565b005b6040516390b5561d60e01b81526004810182905273274b028b03a250ca03644e6c578d81f019ee1323906390b5561d90602401602060405180830381865af41580156088573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019060aa919060ca565b60b1575f80fd5b50565b5f6020828403121560c3575f80fd5b5035919050565b5f6020828403121560d9575f80fd5b8151801515811460e7575f80fd5b939250505056fea2646970667358221220050eefcb4ae3417fbe131ccad8d577d9488919884d4c4a4d0bf8b9c46ce640f664736f6c63430008150033",
            "contractAddress": "",
            "cumulativeGasUsed": "8978461",
            "gasUsed": "257131",
            "methodId": "0xf926657e",
            "functionName": "setup(address _reputationOracle, address _recordingOracle, uint256 _reputationOracleStake, uint256 _recordingOracleStake, string _url, string _hash)",
            "required_confirmations": "13703",
            "status": "1",
            "chainId": 1,
        }
    )
    assert type(receipt) is Receipt  # NOTE: Purposely not using isinstance()
    assert receipt.type == 0
    assert receipt.max_fee > 0
    assert receipt.gas_price == 1499999989


@pytest.mark.parametrize(
    "blob_gas_used,blob_gas_key",
    [
        ("0x20000", "blobGasUsed"),
        (131072, "blob_gas_used"),
        (0, "blobGasUsed"),
        (None, "blobGasUsed"),
    ],
)
def test_decode_receipt_shared_blob(ethereum, blob_gas_used, blob_gas_key):
    blob_gas_price = "0x4d137e31b"

    data = {
        "required_confirmations": 0,
        "blockHash": HexBytes("0x051fb508617fcbe0034538b7ee54c1dedbbbaa6f8d0aeb776edf81bafbc883bd"),
        "blockNumber": 10526428,
        "from": "0x194E22F49BC3f58903866d55488E1e9e8d69b517",
        "gas": 5500000,
        "gasPrice": 1000000008,
        "maxFeePerGas": 150000000000,
        "maxPriorityFeePerGas": 1000000000,
        "maxFeePerBlobGas": "0x22ecb25c00",
        "hash": HexBytes("0xd2882bae0d79a6c8e0fbf0089bbcb4b2eef3a1365471ad9f779b06a41ba47d3c"),
        "input": HexBytes("0xb72d42a1000000000000000000000000000000000000000000"),  # Note: abridged
        "nonce": 329925,
        "to": "0xd5c325D183C592C94998000C5e0EED9e6655c020",
        "transactionIndex": 48,
        "value": 0,
        "type": 3,
        "accessList": [],
        "chainId": 5,
        "blobVersionedHashes": [
            "0x015405d502a27897ad38ffcb80566d1559fa53764befc6d90731082837c2d19e"
        ],
        "v": 0,
        "r": HexBytes("0x4e5bdcbba31548cb8f9806491c5ced7b0f1eac78c7dc0677616c23ee0e469f74"),
        "s": HexBytes("0x31c98ea044c97225d83b9a3f0fa719e2299b9fbc6436e4b3eb096ea528de03ff"),
        "yParity": 0,
        "blobGasPrice": blob_gas_price,
        blob_gas_key: blob_gas_used,
        "contractAddress": None,
        "cumulativeGasUsed": 23085827,
        "effectiveGasPrice": 1000000008,
        "gasUsed": 219756,
        "logs": [],
        "logsBloom": HexBytes("0x00000000010002"),
        "status": 1,
        "transactionHash": HexBytes(
            "0xd2882bae0d79a6c8e0fbf0089bbcb4b2eef3a1365471ad9f779b06a41ba47d3c"
        ),
    }
    actual = ethereum.decode_receipt(data)
    assert isinstance(actual, SharedBlobReceipt)
    assert actual.blob_gas_price == int(blob_gas_price, 16)

    if blob_gas_used:
        # all test-values are this when they exist.
        assert actual.blob_gas_used == 131072
    else:
        # when None, should also default to 0.
        assert actual.blob_gas_used == 0

    # Show type=3 is required.
    data["type"] = 2
    actual = ethereum.decode_receipt(data)
    assert not isinstance(actual, SharedBlobReceipt)
    assert isinstance(actual, Receipt)


def test_decode_receipt_misleading_blob_receipt(ethereum):
    """
    Tests a strange situation (noticed on Tenderly nodes) where _some_
    of the keys indicate blob-related fields, set to ``0``, and others
    are missing, because it's not actually a blob receipt. In this case,
    don't use the blob-receipt class.
    """
    data = {
        "type": 2,
        "status": 1,
        "cumulativeGasUsed": 10565720,
        "logsBloom": HexBytes(
            "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
        ),
        "logs": [],
        "transactionHash": HexBytes(
            "0x62fc9991bc7fb0c76bc83faaa8d1c17fc5efb050542e58ac358932f80aa7a087"
        ),
        "from": "0x1f9090aaE28b8a3dCeaDf281B0F12828e676c326",
        "to": "0xeBec795c9c8bBD61FFc14A6662944748F299cAcf",
        "contractAddress": None,
        "gasUsed": 21055,
        "effectiveGasPrice": 7267406643,
        "blockHash": HexBytes("0xa47fc133f829183b751488c1146f1085451bcccd247db42066dc6c89eaf5ebac"),
        "blockNumber": 21051245,
        "transactionIndex": 130,
        "blobGasUsed": 0,
    }
    actual = ethereum.decode_receipt(data)
    assert not isinstance(actual, SharedBlobReceipt)
    assert isinstance(actual, Receipt)


def test_default_transaction_type_not_connected_used_default_network(project, ethereum, networks):
    value = TransactionType.STATIC.value
    config_dict = {"ethereum": {"mainnet_fork": {"default_transaction_type": value}}}
    assert ethereum.default_transaction_type == TransactionType.DYNAMIC

    with project.temp_config(**config_dict):
        ethereum._default_network = "mainnet-fork"
        provider = networks.active_provider

        # Disconnect so it uses default.
        networks.active_provider = None
        try:
            assert ethereum.default_network_name == "mainnet-fork"
            assert ethereum.default_transaction_type == TransactionType.STATIC
            ethereum._default_network = LOCAL_NETWORK_NAME
        finally:
            networks.active_provider = provider


def test_default_transaction_type_configured_from_local_network(
    eth_tester_provider, ethereum, project
):
    _ = eth_tester_provider  # Connection required so 'ethereum' knows the network.
    value = TransactionType.STATIC.value
    config = {"ethereum": {LOCAL_NETWORK_NAME: {"default_transaction_type": value}}}
    with project.temp_config(**config):
        assert ethereum.default_transaction_type == TransactionType.STATIC


def test_default_transaction_type_changed_at_class_level(ethereum):
    """
    Simulates an L2 plugin changing the default at the definition-level.
    """

    class L2NetworkConfig(BaseEthereumConfig):
        DEFAULT_TRANSACTION_TYPE: ClassVar[int] = TransactionType.STATIC.value

    config = L2NetworkConfig()
    assert config.local.default_transaction_type.value == 0
    assert config.mainnet.default_transaction_type.value == 0
    assert config.mainnet_fork.default_transaction_type.value == 0


@pytest.mark.parametrize("network_name", (LOCAL_NETWORK_NAME, "mainnet-fork", "mainnet_fork"))
def test_gas_limit_local_networks(ethereum, network_name):
    network = ethereum.get_network(network_name)
    network.__dict__.pop("gas_limit", None)  # Refresh in case was changed in another test.
    assert network.gas_limit == "max"


def test_gas_limit_live_networks(ethereum):
    network = ethereum.get_network("sepolia")
    assert network.gas_limit == "auto"


def test_encode_blueprint_contract(ethereum, vyper_contract_type):
    actual = ethereum.encode_contract_blueprint(vyper_contract_type)
    ct_bytes = vyper_contract_type.deployment_bytecode.to_bytes()

    # EIP-5202
    expected = (
        HexBytes("0x61")
        + (len(ct_bytes) + 3).to_bytes(2, "big")  # preface and length
        + HexBytes("0x3d81600a3d39f3")  # return stuff
        + BLUEPRINT_HEADER
        + HexBytes(0)
        + ct_bytes
    )

    assert actual.data == HexBytes(expected)


def test_decode_returndata(ethereum):
    abi = make_method_abi("doThing", outputs=[{"name": "", "type": "bool"}])
    data = HexBytes32.__eth_pydantic_validate__(0)
    actual = ethereum.decode_returndata(abi, data)
    assert actual == (False,)


def test_decode_returndata_non_empty_padding_bytes(ethereum):
    raw_data = HexBytes(
        "0x08c379a000000000000000000000000000000000000000000000000000000000000000200"
        "000000000000000000000000000000000000000000000000000000000000012696e73756666"
        "696369656e742066756e64730000000000000000000000000000"
    )
    abi = make_method_abi(
        "transfer",
        inputs=[
            {"name": "receiver", "type": "address"},
            {"name": "amount", "type": "uint256"},
        ],
        outputs=[{"name": "", "type": "bool"}],
    )
    with pytest.raises(DecodingError):
        ethereum.decode_returndata(abi, raw_data)


def test_decode_returndata_no_bytes_returns_zero(ethereum):
    abi = make_method_abi("doThing", outputs=[{"name": "", "type": "bool"}])
    actual = ethereum.decode_returndata(abi, b"")
    assert actual == (0,)


def test_decode_returndata_list_with_1_struct(ethereum):
    """
    Tests a condition where an array of a list with 1 struct
    would be turned into a raw tuple instead of the Struct class.
    """
    abi = make_method_abi(
        "getArrayOfStructs",
        outputs=[
            ABIType(
                name="",
                type="tuple[]",
                components=[
                    ABIType(name="a", type="address", components=None, internal_type=None),
                    ABIType(name="b", type="bytes32", components=None, internal_type=None),
                ],
                internal_type=None,
            )
        ],
    )
    raw_data = HexBytes(
        "0x000000000000000000000000000000000000000000000000000000000000002"
        "00000000000000000000000000000000000000000000000000000000000000001"
        "000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266f"
        "d91dc47b758f65fd0f6fe511566770c0ae3f94ff9999ceb23bfec3ac9fdc168"
    )
    actual = ethereum.decode_returndata(abi, raw_data)

    assert len(actual) == 1
    # The result should still be a list.
    # There was also a bug where it was mistakenly a tuple!
    assert isinstance(actual[0], list)
    assert len(actual[0]) == 1
    assert actual[0][0].a == "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
    assert actual[0][0].b == HexBytes(
        "0xfd91dc47b758f65fd0f6fe511566770c0ae3f94ff9999ceb23bfec3ac9fdc168"
    )


def test_decode_returndata_returns_str_comparable_ints(ethereum):
    abi = make_method_abi(
        "gimmeInts",
        outputs=[ABIType(name="", type="int", components=None, internal_type="int")],
    )
    raw_data = HexBytes("0x000000000000000000000000000000000000000000000000000000000000002")
    actual = ethereum.decode_returndata(abi, raw_data)
    assert isinstance(actual[0], int)
    assert isinstance(actual[0], CurrencyValueComparable)


def test_decode_returndata_bool(ethereum):
    abi = MethodABI(
        type="function",
        name="view_method",
        stateMutability="view",
        inputs=[],
        outputs=[ABIType(name="", type="bool", components=None, internal_type=None)],
    )
    raw_data = HexBytes("0x000000000000000000000000000000000000000000000000000000000000001")
    actual = ethereum.decode_returndata(abi, raw_data)[0]
    assert isinstance(actual, bool)


@pytest.mark.parametrize("tx_type", TransactionType)
def test_create_transaction_uses_network_gas_limit(tx_type, ethereum, eth_tester_provider, owner):
    tx = ethereum.create_transaction(type=tx_type.value, sender=owner.address)
    assert tx.type == tx_type.value
    assert tx.gas_limit == eth_tester_provider.max_gas


def test_create_transaction_with_none_values(ethereum, eth_tester_provider):
    """
    Tests against None being in place of values in kwargs,
    causing their actual defaults not to get used and ValidationErrors
    to occur.
    """
    static = ethereum.create_transaction(
        value=None, data=None, chain_id=None, gas_price=None, nonce=None, receiver=None
    )
    dynamic = ethereum.create_transaction(
        value=None,
        data=None,
        chain_id=None,
        max_fee=None,
        max_priority_fee=None,
        nonce=None,
        receiver=None,
    )
    assert isinstance(static, StaticFeeTransaction)  # Because used gas_price
    assert isinstance(dynamic, DynamicFeeTransaction)  # Because used max_fee

    for tx in (static, dynamic):
        assert tx.data == b""  # None is not allowed.
        assert tx.value == 0  # None is same as 0.
        assert tx.chain_id == eth_tester_provider.chain_id
        assert tx.nonce is None

    assert static.gas_price is None
    assert dynamic.max_fee is None
    assert dynamic.max_priority_fee is None


@pytest.mark.parametrize("kwarg_name", ("max_fee", "max_fee_per_gas", "maxFee", "maxFeePerGas"))
def test_create_transaction_max_fee_per_gas(kwarg_name, ethereum):
    fee = 1_000_000_000
    tx = ethereum.create_transaction(**{kwarg_name: fee})
    assert tx.max_fee == fee


@pytest.mark.parametrize("kwarg_name", ("gas_price", "gasPrice"))
def test_create_transaction_with_gas_price(kwarg_name, ethereum):
    price = 123
    tx = ethereum.create_transaction(**{kwarg_name: price})
    assert tx.gas_price == price


@pytest.mark.parametrize(
    "tx_kwargs",
    [{"type": 3}, {"max_fee_per_blob_gas": 123}, {"blob_versioned_hashes": [HexBytes(123)]}],
)
def test_create_transaction_shared_blob(tx_kwargs, ethereum):
    tx = ethereum.create_transaction(**tx_kwargs)
    assert isinstance(tx, SharedBlobTransaction)
    assert tx.type == TransactionType.SHARED_BLOB.value


@pytest.mark.parametrize(
    "kwarg_name,value", [("max_fee_per_blob_gas", 345), ("maxFeePerBlobGas", "0x2343")]
)
def test_create_transaction_max_fee_per_blob_gas(kwarg_name, value, ethereum):
    tx = ethereum.create_transaction(**{kwarg_name: value})
    assert isinstance(tx, SharedBlobTransaction)
    expected = value if isinstance(value, int) else int(value, 16)
    assert tx.max_fee_per_blob_gas == expected


@pytest.mark.parametrize(
    "kwarg_name,value",
    [("blob_versioned_hashes", "0x123"), ("blobVersionedHashes", HexBytes("0x123"))],
)
def test_create_transaction_blob_versioned_hashed(kwarg_name, value, ethereum):
    tx = ethereum.create_transaction(**{kwarg_name: [value]})
    assert isinstance(tx, SharedBlobTransaction)
    assert tx.blob_versioned_hashes == [HexBytes(value)]


@pytest.mark.parametrize(
    "tx_kwargs",
    [{"type": 4}, {"authorizationList": []}],
)
def test_create_transaction_set_code(tx_kwargs, ethereum):
    tx = ethereum.create_transaction(**tx_kwargs)
    assert isinstance(tx, SetCodeTransaction)
    assert tx.type == TransactionType.SET_CODE.value


@pytest.mark.parametrize("tx_type", TransactionType)
def test_encode_transaction(tx_type, ethereum, vyper_contract_instance, owner, eth_tester_provider):
    abi = vyper_contract_instance.contract_type.methods[0]
    actual = ethereum.encode_transaction(
        vyper_contract_instance.address, abi, sender=owner.address, type=tx_type.value
    )
    assert actual.gas_limit == eth_tester_provider.max_gas


def test_set_default_network_not_exists(ethereum):
    bad_network = "NOT_EXISTS"
    expected = f"No network in 'ethereum' named '{bad_network}'. Options:.*"
    with pytest.raises(NetworkNotFoundError, match=expected):
        ethereum.set_default_network(bad_network)


def test_networks(ethereum):
    actual = ethereum.networks

    with_forks = ("sepolia", "mainnet")
    without_forks = (LOCAL_NETWORK_NAME,)

    def assert_net(
        net: str,
    ):
        assert net in actual
        base_class = ForkedNetworkAPI if net.endswith("-fork") else NetworkAPI
        assert isinstance(actual[net], base_class)

    for net in with_forks:
        assert_net(net)
        assert_net(f"{net}-fork")
    for net in without_forks:
        assert_net(net)


def test_networks_includes_custom_networks(
    ethereum, custom_networks_config_dict, project, custom_network_name_0, custom_network_name_1
):
    with project.temp_config(**custom_networks_config_dict):
        actual = ethereum.networks
        for net in (
            "sepolia",
            "mainnet",
            LOCAL_NETWORK_NAME,
            custom_network_name_0,
            custom_network_name_1,
            # Show custom networks are auto-forked!
            f"{custom_network_name_0}-fork",
            f"{custom_network_name_1}-fork",
        ):
            assert net in actual
            assert isinstance(actual[net], NetworkAPI)


def test_networks_when_custom_ecosystem(
    configured_custom_ecosystem, networks, custom_network_name_0
):
    obj = networks.custom_ecosystem
    actual = obj.networks
    assert obj.name == CUSTOM_ECOSYSTEM_NAME
    assert custom_network_name_0 in actual
    assert "mainnet" not in actual


def test_networks_multiple_networks_with_same_name(custom_networks_config_dict, ethereum, project):
    data = copy.deepcopy(custom_networks_config_dict)
    data["networks"]["custom"][0]["name"] = "foonet"
    data["networks"]["custom"][1]["name"] = "foonet"
    expected = ".*More than one network named 'foonet' in ecosystem 'ethereum'.*"
    with project.temp_config(**data):
        with pytest.raises(NetworkError, match=expected):
            _ = ethereum.networks


def test_getattr(ethereum):
    assert ethereum.mainnet.name == "mainnet"
    assert isinstance(ethereum.mainnet, NetworkAPI)


def test_getattr_custom_networks(
    ethereum, custom_networks_config_dict, project, custom_network_name_0
):
    with project.temp_config(**custom_networks_config_dict):
        actual = getattr(ethereum, custom_network_name_0)
        assert actual.name == custom_network_name_0
        assert isinstance(actual, NetworkAPI)


def test_default_network(ethereum):
    assert ethereum.default_network_name == LOCAL_NETWORK_NAME


def test_default_network_when_custom_and_set_in_config(
    configured_custom_ecosystem, networks, custom_network_name_0
):
    ecosystem = networks.get_ecosystem(CUSTOM_ECOSYSTEM_NAME)
    # Force it to use config value (in case was set from previous test)
    ecosystem._default_network = None
    assert ecosystem.default_network_name == custom_network_name_0


def test_default_network_name_set_programmatically(ethereum):
    ethereum._default_network = "testnet"
    assert ethereum.default_network_name == "testnet"
    ethereum._default_network = None


def test_default_network_name_from_config(project, ethereum):
    cfg = {"ethereum": {"default_network": "sepolia"}}
    ethereum._default_network = None
    with project.temp_config(**cfg):
        assert ethereum.default_network_name == "sepolia"


def test_default_network_name_when_not_set_uses_local(project, ethereum):
    orig = project.config.ethereum
    data = orig if isinstance(orig, dict) else orig.model_dump()
    data = {k: v for k, v in data.items() if k not in ("default_network",)}
    data["default_network"] = None
    with project.temp_config(**data):
        ethereum._default_network = None
        assert ethereum.default_network_name == LOCAL_NETWORK_NAME


def test_default_network_name_when_not_set_and_no_local_uses_only(
    project, custom_networks_config_dict
):
    """
    Tests a condition that is rare but when a default network is
    not set but there is a single network. In this case, it
    should use the single network by default.
    """
    net = copy.deepcopy(custom_networks_config_dict["networks"]["custom"][0])

    # In a situation with an ecosystem with only a single network.
    ecosystem_name = "acustomeco"
    net["ecosystem"] = ecosystem_name

    only_network = "onlynet"  # More obvious name for test.
    net["name"] = only_network

    with project.temp_config(networks={"custom": [net]}):
        ecosystem = project.network_manager.get_ecosystem(ecosystem_name)
        ecosystem._default_network = None
        actual = ecosystem.default_network_name

        if actual == LOCAL_NETWORK_NAME:
            # For some reason, this test is flake-y. Offer more info
            # to try and debug when this happens (intermittent CI failure).
            all_nets = ", ".join([x for x in ecosystem.networks.keys()])
            pytest.fail(
                f"assert '{LOCAL_NETWORK_NAME}' == '{only_network}'. More info below:\n"
                f"ecosystem_name={ecosystem.name}\n"
                f"networks={all_nets}\n"
                f"type={type(ecosystem)}"
            )
        else:
            # This should pass.
            assert ecosystem.default_network_name == only_network


def test_decode_custom_error(chain, ethereum):
    data = HexBytes("0x6a12f104")
    abi = [ErrorABI(type="error", name="InsufficientETH")]
    contract_type = ContractType(abi=abi)
    addr = cast(AddressType, "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD")

    # Hack in contract-type.
    chain.contracts[addr] = contract_type

    actual = ethereum.decode_custom_error(data, addr)
    assert isinstance(actual, CustomError)


def test_decode_custom_error_contract_type_not_found(ethereum):
    data = HexBytes("0x6a12f104")
    # not known
    addr = cast(AddressType, "0x4fC92A3afd70395Cd496C647d5a6CC9D4B2b7FAD")
    actual = ethereum.decode_custom_error(data, addr)
    assert actual is None


def test_decode_custom_error_tx_unsigned(ethereum):
    data = HexBytes("0x6a12f104")
    # not known
    addr = cast(AddressType, "0x4fC92A3afd70395Cd496C647d5a6CC9D4B2b7FAD")
    actual = ethereum.decode_custom_error(data, addr)
    assert actual is None


def test_decode_custom_error_selector_not_found(chain, ethereum):
    data = HexBytes("0x6a12f104")
    abi: list = []
    contract_type = ContractType(abi=abi)
    addr = cast(AddressType, "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD")

    # Hack in contract-type.
    chain.contracts.contract_types[addr] = contract_type

    tx = ethereum.create_transaction()
    actual = ethereum.decode_custom_error(data, addr, txn=tx)
    assert actual is None


def test_enrich_trace(ethereum, vyper_contract_instance, owner):
    tx = vyper_contract_instance.setNumber(96247783, sender=owner)
    trace = TransactionTrace(transaction_hash=tx.txn_hash)
    actual = ethereum.enrich_trace(trace)
    assert isinstance(actual, TransactionTrace)
    assert actual._enriched_calltree is not None
    assert re.match(r"VyperContract\.setNumber\(num=\d*\) \[\d* gas]", repr(actual))


def test_enrich_trace_handles_call_type_enum(ethereum, vyper_contract_instance, owner):
    """
    Testing a custom trace whose call tree uses an Enum type instead of a str.
    """

    class PluginTxTrace(TransactionTrace):
        def get_calltree(self) -> CallTreeNode:
            call = super().get_calltree()
            # Force enum value instead of str.
            call.call_type = CallType.CALL
            return call

    tx = vyper_contract_instance.setNumber(96247783, sender=owner)
    trace = PluginTxTrace(transaction_hash=tx.txn_hash)
    actual = ethereum.enrich_trace(trace)
    assert isinstance(actual, PluginTxTrace)
    assert actual._enriched_calltree is not None
    assert re.match(r"VyperContract\.setNumber\(num=\d*\) \[\d* gas]", repr(actual))

    # Hook into helper method hackily with an enum.
    # Notice the mode is Python and not JSON here.
    call = trace.get_calltree().model_dump(by_alias=True)
    actual = ethereum._enrich_calltree(call)
    assert actual["call_type"] == CallType.CALL.value


def test_enrich_trace_handles_events(ethereum, vyper_contract_instance, owner):
    tx = vyper_contract_instance.setNumber(96247783, sender=owner)

    # Used Hardhat to get the data.
    events = [
        {
            "call_type": "EVENT",
            "data": "0x3ee0dda47a082585c3c66434c4b5b1b4558581efb5f4ee60a59a58bc1201404b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000744796e616d696300000000000000000000000000000000000000000000000000",
            "depth": 1,
            "topics": [
                "0xa84473122c11e32cd505595f246a28418b8ecd6cf819f4e3915363fad1b8f968",
                "0x000000000000000000000000000000000000000000000000000000000000007b",
                "0x9f3d45ac20ccf04b45028b8080bb191eab93e29f7898ed43acf480dd80bba94d",
            ],
        }
    ]

    calldata = "0x3fb5c1cb000000000000000000000000000000000000000000000000000000000000007b"
    call = {
        "events": events,
        "call_type": "CALL",
        "address": vyper_contract_instance.address,
        "calldata": calldata,
    }

    class MyTrace(TransactionTrace):
        def get_calltree(self) -> CallTreeNode:
            return CallTreeNode.model_validate(call)

    trace = MyTrace(transaction_hash=tx.txn_hash)
    actual = ethereum.enrich_trace(trace)
    assert "events" in actual._enriched_calltree, "is evm-trace updated?"
    events = actual._enriched_calltree["events"]
    expected = [
        {
            "name": "NumberChange",
            "calldata": {
                "b": "0x3ee0..404b",
                "prevNum": 0,
                "dynData": '"Dynamic"',
                "newNum": 123,
                "dynIndexed": "0x9f3d..a94d",
            },
        }
    ]
    assert events == expected


def test_enrich_trace_avoids_using_wrong_contract_type(
    ethereum, vyper_contract_instance, solidity_contract_instance, owner, chain
):
    tx = vyper_contract_instance.setNumber(96247783, sender=owner)
    trace = TransactionTrace(transaction_hash=tx.txn_hash)

    # Purposely passing in the wrong contract type here.
    actual = ethereum.enrich_trace(trace, contract_type=solidity_contract_instance.contract_type)

    assert isinstance(actual, TransactionTrace)
    assert actual._enriched_calltree is not None
    assert re.match(r"VyperContract\.setNumber\(num=\d*\) \[\d* gas]", repr(actual))

    # Ensure the contract type was not corrupted.
    ct = chain.contracts[vyper_contract_instance.address]
    assert ct == vyper_contract_instance.contract_type


def test_get_deployment_address(ethereum, owner, vyper_contract_container):
    actual = ethereum.get_deployment_address(owner.address, owner.nonce)
    expected = owner.deploy(vyper_contract_container, 490)
    assert actual == expected
