diff --git a/docs/filling_tests/test_ids.md b/docs/filling_tests/test_ids.md index 7005ab639b..069518bb06 100644 --- a/docs/filling_tests/test_ids.md +++ b/docs/filling_tests/test_ids.md @@ -55,8 +55,8 @@ The test framework can also generate blockchain tests containing blocks that spa Each Python test case is also typically parametrized by test type, respectively fixture format. For example, if the test is implemented as a `state_test`, the test framework will additionally generate the following blockchain test fixtures (consisting of a single block with a single transaction): -- a `blockchain_test` which can be tested via the Hive `eest/consume-rlp` simulator (or directly via a dedicated client interface). -- a `blockchain_engine_test` (for post-merge forks) which can be tested via the Hive `eest/consume-engine` simulator. +- a `blockchain_test` which can be tested via the Hive `eels/consume-rlp` simulator (or directly via a dedicated client interface). +- a `blockchain_engine_test` (for post-merge forks) which can be tested via the Hive `eels/consume-engine` simulator. ### Example: The Test IDs generated for `test_chainid` diff --git a/docs/running_tests/consume/exceptions.md b/docs/running_tests/consume/exceptions.md index 453284aedf..a5a82c499a 100644 --- a/docs/running_tests/consume/exceptions.md +++ b/docs/running_tests/consume/exceptions.md @@ -98,7 +98,7 @@ uv run consume engine --disable-strict-exception-matching=nimbus-el Enable verbose client output: ```bash -./hive --sim ethereum/eest/consume-engine \ +./hive --sim ethereum/eels/consume-engine \ --docker.output \ --sim.loglevel 5 ``` diff --git a/docs/running_tests/execute/hive.md b/docs/running_tests/execute/hive.md index 6af210f909..c4d89e48c0 100644 --- a/docs/running_tests/execute/hive.md +++ b/docs/running_tests/execute/hive.md @@ -2,7 +2,7 @@ Tests can be executed on a local hive-controlled single-client network by running the `execute hive` command. -## The `eest/execute-blobs` Simulator +## The `eels/execute-blobs` Simulator The `blob_transaction_test` execute test spec sends blob transactions to a running client. Blob transactions are fully supported in execute mode: diff --git a/docs/running_tests/hive/client_config.md b/docs/running_tests/hive/client_config.md index be04107f11..e2713780e9 100644 --- a/docs/running_tests/hive/client_config.md +++ b/docs/running_tests/hive/client_config.md @@ -102,17 +102,17 @@ cp -r /path/to/your/go-ethereum ./clients/go-ethereum/go-ethereum-local Force rebuild base images: ```bash -./hive --docker.pull --sim ethereum/eest/consume-engine +./hive --docker.pull --sim ethereum/eels/consume-engine ``` Force rebuild specific client: ```bash -./hive --docker.nocache "clients/go-ethereum" --sim ethereum/eest/consume-engine +./hive --docker.nocache "clients/go-ethereum" --sim ethereum/eels/consume-engine ``` Show the docker container build output: ```bash -./hive --docker.buildoutput --sim ethereum/eest/consume-engine +./hive --docker.buildoutput --sim ethereum/eels/consume-engine ``` diff --git a/docs/running_tests/hive/common_options.md b/docs/running_tests/hive/common_options.md index ff3d313252..2272e7d058 100644 --- a/docs/running_tests/hive/common_options.md +++ b/docs/running_tests/hive/common_options.md @@ -1,15 +1,15 @@ # Common Simulator Options -All EEST Hive simulators share common command-line options and patterns. +All execution-specs (EELS) Hive simulators share common command-line options and patterns. ## Basic Usage -While they may be omitted, it's recommended to specify the `fixtures` and `branch` simulator build arguments when running EEST simulators. +While they may be omitted, it's recommended to specify the `fixtures` and `branch` simulator build arguments when running execution-specs simulators. For example, this runs "stable" fixtures from the v4.3.0 [latest stable release](../releases.md#standard-releases) and builds the simulator at the v4.3.0 tag: ```bash -./hive --sim ethereum/eest/consume-engine \ +./hive --sim ethereum/eels/consume-engine \ --sim.buildarg fixtures=stable@v4.3.0 \ --sim.buildarg branch=v4.3.0 \ --client go-ethereum @@ -20,7 +20,7 @@ For example, this runs "stable" fixtures from the v4.3.0 [latest stable release] Run a subset of tests by filtering tests using `--sim.limit=` to perform a regular expression match against test IDs: ```bash -./hive --sim ethereum/eest/consume-engine --sim.limit ".*eip4844.*" +./hive --sim ethereum/eels/consume-engine --sim.limit ".*eip4844.*" ``` ### Collect Only/Dry-Run @@ -28,7 +28,7 @@ Run a subset of tests by filtering tests using `--sim.limit=` to perform The `collectonly:` prefix can be used to inspect which tests would match an expression (dry-run), `--docker.output` must be specified to see the simulator's collection result: ```bash -./hive --sim ethereum/eest/consume-engine \ +./hive --sim ethereum/eels/consume-engine \ --sim.buildarg fixtures=stable@v4.3.0 \ --sim.buildarg branch=v4.3.0 \ --docker.output \ @@ -40,7 +40,7 @@ The `collectonly:` prefix can be used to inspect which tests would match an expr The `id:` prefix can be used to select a single test via its ID (this will automatically escape any special characters in the test case ID): ```console -./hive --sim ethereum/eest/consume-engine \ +./hive --sim ethereum/eels/consume-engine \ --sim.buildarg fixtures=stable@v4.3.0 \ --sim.buildarg branch=v4.3.0 \ --docker.output \ @@ -52,7 +52,7 @@ The `id:` prefix can be used to select a single test via its ID (this will autom To run multiple tests in parallel, use `--sim.parallelism`: ```bash -./hive --sim ethereum/eest/consume-rlp --sim.parallelism 4 +./hive --sim ethereum/eels/consume-rlp --sim.parallelism 4 ``` ### Output Options @@ -60,7 +60,7 @@ To run multiple tests in parallel, use `--sim.parallelism`: See hive log output in the console: ```bash -./hive --sim ethereum/eest/consume-engine --sim.loglevel 5 +./hive --sim ethereum/eels/consume-engine --sim.loglevel 5 ``` ### Container Issues @@ -68,5 +68,5 @@ See hive log output in the console: Increase client timeout: ```bash -./hive --client.checktimelimit=180s --sim ethereum/eest/consume-engine +./hive --client.checktimelimit=180s --sim ethereum/eels/consume-engine ``` diff --git a/docs/running_tests/hive/dev_mode.md b/docs/running_tests/hive/dev_mode.md index 61b382ff44..965a7e1855 100644 --- a/docs/running_tests/hive/dev_mode.md +++ b/docs/running_tests/hive/dev_mode.md @@ -1,6 +1,6 @@ # Hive Development Mode -This section explains how to run EEST simulators using their EEST commands, e.g., `uv run consume engine`, against a Hive "development" server as apposed to using the standalone `./hive` command. +This section explains how to run EELS simulators using their Python-based commands, e.g., `uv run consume engine`, against a Hive "development" server as apposed to using the standalone `./hive` command. This avoids running the simulator in a dockerized environment and has several advantages: @@ -18,7 +18,7 @@ This avoids running the simulator in a dockerized environment and has several ad ### Prerequisites -- EEST is installed, see [Installation](../../getting_started/installation.md) +- The execution-specs repo is setup in development mode, see [Installation](../../getting_started/installation.md) - Hive is built, see [Hive](../hive/index.md#quick-start). ## Hive Dev Setup on Linux @@ -29,7 +29,7 @@ This avoids running the simulator in a dockerized environment and has several ad ./hive --dev --client go-ethereum --client-file clients.yaml --docker.output ``` -2. In a separate shell, configure environment for EEST: +2. In a separate shell, configure environment for execution-specs: === "bash/zsh" @@ -43,7 +43,7 @@ This avoids running the simulator in a dockerized environment and has several ad set -x HIVE_SIMULATOR http://127.0.0.1:3000 ``` -3. Run EEST consume commands +3. Run execution-specs `consume` commands ```bash uv run consume engine --input ./fixtures -k "test_chainid" @@ -56,37 +56,36 @@ Due to Docker running within a VM on macOS, the host machine and Docker containe 1. Linux VM: Run a Linux virtual machine on your macOS and execute the standard development workflow above from within the VM. 2. Remote Linux: SSH into a remote Linux environment (server or cloud instance) and run development mode there. -3. **Docker Development Image**: Create a containerized EEST environment that runs within Docker's network namespace (recommended). +3. **Docker Development Image**: Create a containerized execution-specs environment that runs within Docker's network namespace (recommended). The following section details the setup and usage of option 3. -### EEST Docker Development Image +### EELS Docker Development Image -Within the [`eest/`](https://github.com/ethereum/hive/tree/master/simulators/ethereum/eest) directory of hive, a new dockerfile must be created: `Dockerfile.dev`, with the following contents: +Within the [`eels/`](https://github.com/ethereum/hive/tree/master/simulators/ethereum/eels) directory of hive, a new dockerfile must be created: `Dockerfile.dev`, with the following contents: ```docker FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim -ARG branch=main -ENV GIT_REF=${branch} +ARG branch="" RUN apt-get update && apt-get install -y git -RUN git init execution-spec-tests && \ - cd execution-spec-tests && \ - git remote add origin https://github.com/ethereum/execution-spec-tests.git && \ - git fetch --depth 1 origin $GIT_REF && \ - git checkout FETCH_HEAD; - -WORKDIR /execution-spec-tests +RUN git clone --depth 1 https://github.com/ethereum/execution-specs.git && \ + cd execution-specs && \ + if [ -n "$branch" ]; then \ + git fetch --depth 1 origin "$branch" && \ + git checkout FETCH_HEAD; \ + fi +WORKDIR /execution-specs/packages/testing RUN uv sync ENTRYPOINT ["/bin/bash"] ``` -This dockerfile will be our entry point for running EEST commands. +This dockerfile will be our entry point for running simulator commands. -### `eest/` Hive Directory Structure +### `eels/` Hive Directory Structure ```tree -├── eest +├── eels │ ├── Dockerfile.dev │ ├── consume-block-rlp │ │ └── Dockerfile @@ -108,10 +107,10 @@ This dockerfile will be our entry point for running EEST commands. ./hive --dev --dev.addr :3000 --client go-ethereum --client-file clients.yaml ``` -3. In a separate terminal session, build the EEST development image: +3. In a separate terminal session, build the EELS development image: ```bash - cd simulators/ethereum/eest/ + cd simulators/ethereum/eels/ docker build -t macos-consume-dev -f Dockerfile.dev . ``` @@ -136,7 +135,7 @@ When Hive runs in dev mode: 3. Keeps the Hive Proxy container running between test executions. 4. Waits for external simulator connections via the API. -This allows EEST's consume commands to connect to the running Hive instance and execute tests interactively. +This allows the EELS's consume commands to connect to the running Hive instance and execute tests interactively. ## More Options Available diff --git a/docs/running_tests/hive/index.md b/docs/running_tests/hive/index.md index 81f75be5ee..aba902eb49 100644 --- a/docs/running_tests/hive/index.md +++ b/docs/running_tests/hive/index.md @@ -1,6 +1,6 @@ # Hive -@ethereum/hive is a containerized testing framework that helps orchestrate test execution against Ethereum clients. Hive is incredibly extensible; new test suites can be implemented in a module manner as "simulators" that interact with clients to test certain aspects of their behavior. EEST implements several simulators, see [Running Tests](../running.md) for an overview. +@ethereum/hive is a containerized testing framework that helps orchestrate test execution against Ethereum clients. Hive is incredibly extensible; new test suites can be implemented in a module manner as "simulators" that interact with clients to test certain aspects of their behavior. The execution-specs `testing` package implements several simulators, see [Running Tests](../running.md) for an overview. ## Quick Start diff --git a/docs/running_tests/releases.md b/docs/running_tests/releases.md index 2136f0401a..7f7ea06cef 100644 --- a/docs/running_tests/releases.md +++ b/docs/running_tests/releases.md @@ -7,10 +7,10 @@ | Format | Consumed by the client | Location in `.tar.gz` release | | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | | [State Tests](./test_formats/state_test.md) | - directly via a `statetest`-like command
(e.g., [go-ethereum/cmd/evm/staterunner.go](https://github.com/ethereum/go-ethereum/blob/4bb097b7ffc32256791e55ff16ca50ef83c4609b/cmd/evm/staterunner.go)) | `./fixtures/state_tests/` | -| [Blockchain Tests](./test_formats/blockchain_test.md) | - directly via a `blocktest`-like command
(e.g., [go-ethereum/cmd/evm/blockrunner.go](https://github.com/ethereum/go-ethereum/blob/4bb097b7ffc32256791e55ff16ca50ef83c4609b/cmd/evm/blockrunner.go))
- using the [RLPeest/consume-rlp Simulator](./running.md#rlp) via block import | `./fixtures/blockchain_tests/` | -| [Blockchain Engine Tests](./test_formats/blockchain_test_engine.md) | - using the [eest/consume-engine Simulator](./running.md#engine) and the Engine API | `./fixtures/blockchain_tests_engine/` | +| [Blockchain Tests](./test_formats/blockchain_test.md) | - directly via a `blocktest`-like command
(e.g., [go-ethereum/cmd/evm/blockrunner.go](https://github.com/ethereum/go-ethereum/blob/4bb097b7ffc32256791e55ff16ca50ef83c4609b/cmd/evm/blockrunner.go))
- using the [eels/consume-rlp Simulator](./running.md#rlp) via block import | `./fixtures/blockchain_tests/` | +| [Blockchain Engine Tests](./test_formats/blockchain_test_engine.md) | - using the [eels/consume-engine Simulator](./running.md#engine) and the Engine API | `./fixtures/blockchain_tests_engine/` | | [Transaction Tests](./test_formats/transaction_test.md) | - using a new simulator coming soon | None; executed directly from Python source,
using a release tag | -| Blob Transaction Tests | - using the [eest/execute-blobs Simulator](./execute/hive.md#the-eestexecute-blobs-simulator) and | None; executed directly from Python source,
using a release tag | +| Blob Transaction Tests | - using the [eels/execute-blobs Simulator](./execute/hive.md#the-eelsexecute-blobs-simulator) and | None; executed directly from Python source,
using a release tag | ## Release URLs and Tarballs diff --git a/docs/running_tests/running.md b/docs/running_tests/running.md index 94272d7d4e..efd98c340d 100644 --- a/docs/running_tests/running.md +++ b/docs/running_tests/running.md @@ -23,9 +23,9 @@ Both `consume` and `execute` provide sub-commands which correspond to different The following sections describe the different methods in more detail. -!!! note "`./hive --sim=eest/consume-engine` vs `consume engine`" +!!! note "`./hive --sim=eels/consume-engine` vs `consume engine`" - EEST simulators can be ran either standalone using the `./hive` command or via an EEST command against a `./hive --dev` backend, more details are [provided below](#two-methods-to-run-eest-simulators). + The execution-specs simulators can be ran either standalone using the `./hive` command or via a `uv`/Python-based command against a `./hive --dev` backend, more details are [provided below](#two-methods-to-run-eels-simulators). ## Direct @@ -48,7 +48,7 @@ The EEST `consume direct` command is a small wrapper around client direct interf | Nomenclature | | | -------------- | ------------------------ | | Command | `consume engine` | -| Simulator | `eest/consume-engine` | +| Simulator | `eels/consume-engine` | | Fixture format | `blockchain_test_engine` | The consume engine method tests execution clients via the Engine API by sending block payloads and verifying the response (post-merge forks only). This method provides the most realistic testing environment for production Ethereum client behavior, covering consensus integration, payload validation, and state synchronization. @@ -67,7 +67,7 @@ The `consume engine` command: | Nomenclature | | | -------------- | ------------------ | | Command | `consume rlp` | -| Simulator | `eest/consume-rlp` | +| Simulator | `eels/consume-rlp` | | Fixture format | `blockchain_test` | The RLP consumption method tests execution clients by providing them with RLP-encoded blocks to load upon startup, similar to the block import process during historical synchronization. This method tests the client's core block processing logic without the overhead of network protocols. @@ -103,15 +103,15 @@ The `consume sync` command: ## Engine vs RLP Simulator -The RLP Simulator (`eest/consume-rlp`) and the Engine Simulator (`eest/consume-engine`) should be seen as complimentary to one another. Although they execute the same underlying EVM test cases, the block validation logic is executed via different client code paths (using different [fixture formats](./test_formats/index.md)). Therefore, ideally, **both simulators should be executed for full coverage**. +The RLP Simulator (`eels/consume-rlp`) and the Engine Simulator (`eels/consume-engine`) should be seen as complimentary to one another. Although they execute the same underlying EVM test cases, the block validation logic is executed via different client code paths (using different [fixture formats](./test_formats/index.md)). Therefore, ideally, **both simulators should be executed for full coverage**. ### Code Path Choices -Clients consume fixtures in the `eest/consume-engine` simulator via the Engine API's `EngineNewPayloadv*` endpoint; a natural way to validate, respectively invalidate, block payloads. In this case, there is no flexibility in the choice of code path - it directly harnesses mainnet client functionality. The `eest/consume-rlp` Simulator, however, allows clients more freedom, as the rlp-encoded blocks are imported upon client startup. Clients are recommended to try and hook the block import into the code path used for historical syncing. +Clients consume fixtures in the `eels/consume-engine` simulator via the Engine API's `EngineNewPayloadv*` endpoint; a natural way to validate, respectively invalidate, block payloads. In this case, there is no flexibility in the choice of code path - it directly harnesses mainnet client functionality. The `eels/consume-rlp` Simulator, however, allows clients more freedom, as the rlp-encoded blocks are imported upon client startup. Clients are recommended to try and hook the block import into the code path used for historical syncing. ### Differences -| | `eest/consume-rlp` | `eest/consume-engine` | +| | `eels/consume-rlp` | `eels/consume-engine` | | ----------------------- | ----------------------------------------------------- | ------------------------------------------------------------------ | | **Fixture Format Used** | [`BlockchainTest`](./test_formats/blockchain_test.md) | [`BlockchainTestEngine`](./test_formats/blockchain_test_engine.md) | | **Fork support** | All forks (including pre-merge) | Post-merge forks only (Paris+) | @@ -128,9 +128,9 @@ Clients consume fixtures in the `eest/consume-engine` simulator via the Engine A See [Execute Command](./execute/index.md). -## Two Methods to Run EEST Simulators +## Two Methods to Run EELS Simulators -Many of the methods use the Hive Testing Environment to interact with clients and run tests against them. These methods are also called Hive simulators. While Hive is always necessary to run simulators, they can be called in two different ways. Both of these commands execute the same simulator code, but in different environments, we take the example of the `eest/consume-engine` simulator: +Many of the methods use the Hive Testing Environment to interact with clients and run tests against them. These methods are also called Hive simulators. While Hive is always necessary to run simulators, they can be called in two different ways. Both of these commands execute the same simulator code, but in different environments, we take the example of the `eels/consume-engine` simulator: -1. `./hive --sim=eest/consume-engine` is a standalone command that installs EEST and the `consume` command in a dockerized container managed by Hive. This is the standard method to execute EEST [fixture releases](./releases.md) against clients in CI environments and is the method to generate the results at [hive.ethpandaops.io](https://hive.ethpandaops.io). See [Hive](./hive/index.md) and its [Common Options](./hive/common_options.md) for help with this method. -2. `uv run consume engine` requires the user to clone and [install EEST](../getting_started/installation.md) and start a Hive server in [development mode](./hive/dev_mode.md). In this case, the simulator runs on the native system and communicate to the client via the Hive API. This is particularly useful during test development as fixtures on the local disk can be specified via `--input=fixtures/`. As the simulator runs natively, it is easy to drop into a debugger and inspect the simulator or client container state. See [Hive Developer Mode](./hive/dev_mode.md) for help with this method. +1. `./hive --sim=eels/consume-engine` is a standalone command that installs and configures execution-specs and its `consume` command in a dockerized container managed by Hive. This is the standard method to execute EEST [fixture releases](./releases.md) against clients in CI environments and is the method to generate the results at [hive.ethpandaops.io](https://hive.ethpandaops.io). See [Hive](./hive/index.md) and its [Common Options](./hive/common_options.md) for help with this method. +2. `uv run consume engine` requires the user to clone and [configure execution-specs](../getting_started/installation.md) and start a Hive server in [development mode](./hive/dev_mode.md). In this case, the simulator runs on the native system and communicate to the client via the Hive API. This is particularly useful during test development as fixtures on the local disk can be specified via `--input=fixtures/`. As the simulator runs natively, it is easy to drop into a debugger and inspect the simulator or client container state. See [Hive Developer Mode](./hive/dev_mode.md) for help with this method. diff --git a/tests/benchmark/stateful/bloatnet/stubs.json b/tests/benchmark/stateful/bloatnet/stubs.json new file mode 100644 index 0000000000..f10cc6c5e2 --- /dev/null +++ b/tests/benchmark/stateful/bloatnet/stubs.json @@ -0,0 +1,299 @@ +{ + "test_sload_empty_erc20_balanceof_30GB_ERC20": "0x19fc17d87D946BBA47ca276f7b06Ee5737c4679C", + "test_sload_empty_erc20_balanceof_XEN": "0x06450dEe7FD2Fb8E39061434BAbCFC05599a6Fb8", + "test_sload_empty_erc20_balanceof_USDT": "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "test_sload_empty_erc20_balanceof_USDC": "0xA0b86991C6218B36c1d19D4a2E9Eb0CE3606EB48", + "test_sload_empty_erc20_balanceof_LPT": "0x58b6A8a3302369DAEc383334672404Ee733AB239", + "test_sload_empty_erc20_balanceof_SHIB": "0x95aD61B0a150d79219dCF64E1E6Cc01f0B64C4cE", + "test_sload_empty_erc20_balanceof_WETH": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "test_sload_empty_erc20_balanceof_G-CRE": "0xa3Ee21c306A700E682AbcDfE9bAA6A08F3820419", + "test_sload_empty_erc20_balanceof_MEME": "0xB131F4A55907B10d1F0A50d8Ab8FA09EC342CD74", + "test_sload_empty_erc20_balanceof_OMG": "0xd26114cD6EE289AccF82350c8d8487fedB8A0C07", + "test_sload_empty_erc20_balanceof_MATIC": "0x7d1Afa7B718fb893DB30A3abc0Cfc608AaCfEbB0", + "test_sload_empty_erc20_balanceof_stETH": "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", + "test_sload_empty_erc20_balanceof_DAI": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "test_sload_empty_erc20_balanceof_PEPE": "0x6982508145454Ce325dDbE47a25d4eC3d2311933", + "test_sload_empty_erc20_balanceof_old": "0x0cf0ee63788A0849FE5297F3407f701E122CC023", + "test_sload_empty_erc20_balanceof_BAT": "0x0D8775F648430679A709E98d2b0Cb6250d2887EF", + "test_sload_empty_erc20_balanceof_UNI": "0x1F9840a85d5aF5bf1D1762F925BdADdC4201F984", + "test_sload_empty_erc20_balanceof_AMB": "0x4dc3643Dbc642b72C158E7F3d2FF232df61cB6CE", + "test_sload_empty_erc20_balanceof_HEX": "0x2b591e99afE9f32eAA6214f7B7629768c40eEb39", + "test_sload_empty_erc20_balanceof_CRO": "0xa0b73e1ff0b80914ab6fe0444e65848c4c34450b", + "test_sload_empty_erc20_balanceof_UCASH": "0x92e52a1A235d9A103D970901066CE910AAceFD37", + "test_sload_empty_erc20_balanceof_BNB": "0xB8c77482e45F1F44dE1745F52C74426C631bDd52", + "test_sload_empty_erc20_balanceof_GSE": "0xe530441f4f73bdb6dc2fa5af7c3fc5fd551ec838", + "test_sload_empty_erc20_balanceof_MANA": "0x0F5D2FB29fb7d3cFeE444A200298f468908cC942", + "test_sload_empty_erc20_balanceof_OCN": "0x4092678e4E78230F46A1534C0fBC8Fa39780892B", + "test_sload_empty_erc20_balanceof_EIGEN": "0xEC53BF9167F50cDEb3aE105F56099AaAb9061F83", + "test_sload_empty_erc20_balanceof_COMP": "0xc00e94Cb662C3520282E6f5717214004A7f26888", + "test_sload_empty_erc20_balanceof_cUSDC": "0x39AA39c021dfbaE8faC545936693aC917d5E7563", + "test_sload_empty_erc20_balanceof_sMEME": "0xc059A531B4234d05e9EF4aC51028f7E6156E2CcE", + "test_sload_empty_erc20_balanceof_SAND": "0x3845badade8e6dff049820680d1f14bd3903a5d0", + "test_sload_empty_erc20_balanceof_AAVE": "0x7Fc66500c84A76Ad7e9c93437bFc5AC33E2DDAe9", + "test_sload_empty_erc20_balanceof_ZRX": "0xE41d2489571d322189246DaFA5ebDe1F4699F498", + "test_sload_empty_erc20_balanceof_KOK": "0x9B9647431632AF44be02ddd22477Ed94d14AacAa", + "test_sload_empty_erc20_balanceof_APE": "0x4d224452801ACEd8B2F0aebe155379bb5D594381", + "test_sload_empty_erc20_balanceof_SAI": "0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359", + "test_sload_empty_erc20_balanceof_GRT": "0xc944E90C64B2c07662A292be6244BDf05Cda44a7", + "test_sload_empty_erc20_balanceof_LRC": "0xBBbbCA6A901c926F240b89EacB641d8Aec7AEafD", + "test_sload_empty_erc20_balanceof_ELON": "0x761D38e5DDf6ccf6Cf7C55759d5210750B5D60F3", + "test_sload_empty_erc20_balanceof_QNT": "0x4a220E6096B25EADb88358cb44068A3248254675", + "test_sload_empty_erc20_balanceof_ONDO": "0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3", + "test_sload_empty_erc20_balanceof_ENJ": "0xF629cBd94d3791c9250152BD8dfBDF380E2a3B9c", + "test_sload_empty_erc20_balanceof_FET": "0x1D287CC25dAD7cCaF76a26bc660c5F7C8E2a05BD", + "test_sload_empty_erc20_balanceof_eETH": "0x6c5024Cd4F8A59110119C56f8933403A539555EB", + "test_sload_empty_erc20_balanceof_XMX": "0x0F8c45B896784A1E408526B9300519ef8660209c", + "test_sload_empty_erc20_balanceof_FTI": "0x943ed852Dadb5C3938ECdC6883718df8142de4C8", + "test_sload_empty_erc20_balanceof_WBTC": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + "test_sload_empty_erc20_balanceof_LEND": "0x80fB784B7eD66730e8b1DBd9820aFD29931aab03", + "test_sload_empty_erc20_balanceof_ELEC": "0xd49ff13661451313ca1553fd6954bd1d9b6e02b9", + "test_sload_empty_erc20_balanceof_SUSHI": "0x6B3595068778DD592e39A122f4f5a5CF09C90fE2", + "test_sload_empty_erc20_balanceof_HOT": "0x6c6EE5e31d828De241282B9606C8e98Ea48526E2", + "test_sload_empty_erc20_balanceof_MITx": "0x4a527d8fc13c5203ab24ba0944f4cb14658d1db6", + "test_sload_empty_erc20_balanceof_1INCH": "0x111111111117dC0aa78b770fA6A738034120C302", + "test_sload_empty_erc20_balanceof_USDP": "0x1456688345527bE1f37E9e627DA0837D6f08C925", + "test_sload_empty_erc20_balanceof_ETHFI": "0xfe0c30065b384f05761f15d0cc899d4f9f9cc0eb", + "test_sload_empty_erc20_balanceof_POLY": "0x9992ec3cf6a55b00978cddf2b27bc6882d88d1ec", + "test_sload_empty_erc20_balanceof_AOA": "0x9ab165d795019b6d8b3e971dda91071421305e5a", + "test_sload_empty_erc20_balanceof_STORJ": "0xB64ef51C888972c908CFacf59B47C1AfBC0Ab8aC", + "test_sload_empty_erc20_balanceof_MKR": "0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2", + "test_sload_empty_erc20_balanceof_AMP": "0xfF20817765cB7F73d4Bde2e66e067e58d11095c2", + "test_sload_empty_erc20_balanceof_VRA": "0xF411903cbc70a74d22900a5DE66A2dda66507255", + "test_sload_empty_erc20_balanceof_GTC": "0xde30da39c46104798bb5aa3fe8b9e0e1f348163f", + "test_sload_empty_erc20_balanceof_FLOKI": "0x43F11c02439E2736800433B4594994Bd43Cd066D", + "test_sload_empty_erc20_balanceof_ALT": "0x8457CA5040ad67fdebbCC8EdCE889A335Bc0fbFB", + "test_sload_empty_erc20_balanceof_IMX": "0xf57e7e7c23978c3caec3c3548e3d615c346e79ff", + "test_sload_empty_erc20_balanceof_XYO": "0x55296f69f40ea6d20e478533c15A6b08B654E758", + "test_sload_empty_erc20_balanceof_REV": "0x2ef27bf41236bd859a95209e17a43fbd26851f92", + "test_sload_empty_erc20_balanceof_FUN": "0x419d0d8bdd9af5e606ae2232ed285aff190e711b", + "test_sload_empty_erc20_balanceof_CRV": "0xD533a949740bb3306d119CC777fa900bA034cd52", + "test_sload_empty_erc20_balanceof_CHZ": "0x3506424f91fd33084466f402d5d97f05f8e3b4af", + "test_sload_empty_erc20_balanceof_SMT": "0x78Eb8DC641077F049f910659b6d580E80dC4d237", + "test_sload_empty_erc20_balanceof_SNX": "0xC011A72400E58ecD99Ee497CF89E3775d4bd732F", + "test_sload_empty_erc20_balanceof_DENT": "0x3597bfD533a99c9aa083587B074434E61Eb0A258", + "test_sload_empty_erc20_balanceof_RNDR": "0x6De037ef9aD2725EB40118Bb1702EBb27e4Aeb24", + "test_sload_empty_erc20_balanceof_SNT": "0x744d70FDBe2Ba4CF95131626614a1763DF805B9E", + "test_sload_empty_erc20_balanceof_AXS": "0xBB0E17EF65F82Ab018d8EDd776e8DD940327B28b", + "test_sload_empty_erc20_balanceof_KNC": "0xdd974D5C2e2928deA5F71b9825b8b646686BD200", + "test_sload_empty_erc20_balanceof_WEPE": "0xccB365D2e11aE4D6d74715c680f56cf58bF4bF10", + "test_sload_empty_erc20_balanceof_ZETA": "0xf091867ec603a6628ed83d274e835539d82e9cc8", + "test_sload_empty_erc20_balanceof_LYM": "0xc690f7c7fcffa6a82b79fab7508c466fefdfc8c5", + "test_sload_empty_erc20_balanceof_nCASH": "0x809826cceAb68c387726af962713b64Cb5Cb3CCA", + "test_sload_empty_erc20_balanceof_LOOKS": "0xf4d2888d29D722226FafA5d9B24F9164c092421E", + "test_sload_empty_erc20_balanceof_Monfter/Monavale": "0x275f5ad03be0fa221b4c6649b8aee09a42d9412a", + "test_sload_empty_erc20_balanceof_cETH": "0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5", + "test_sload_empty_erc20_balanceof_SALT": "0x4156D3342D5c385a87D264F90653733592000581", + "test_sload_empty_erc20_balanceof_HOGE": "0xfAd45E47083e4607302aa43c65fB3106F1cd7607", + "test_sload_empty_erc20_balanceof_REN": "0x408e41876cCCDC0F92210600ef50372656052a38", + "test_sload_empty_erc20_balanceof_ENS": "0xC56b13EBBCffa67cfB7979b900B736b3fb480D78", + "test_sload_empty_erc20_balanceof_NEXO": "0xB62132e35a6c13ee1EE0f84dC5d40bad8d815206", + "test_sload_empty_erc20_balanceof_RFR": "0xD0929d411954c47438Dc1D871dd6081F5C5e149c", + "test_sload_empty_erc20_balanceof_COFI": "0x3137619705b5fc22a3048989F983905e456B59Ab", + "test_sload_empty_erc20_balanceof_SLP": "0xcc8fa225d80b9c7d42f96e9570156c65d6cAAa25", + "test_sload_empty_erc20_balanceof_FUEL": "0xea38eaa3c86c8f9b751533ba2e562deb9acded40", + "test_sload_empty_erc20_balanceof_ENA": "0x57e114B691Db790C35207b2e685D4A43181e6061", + "test_sload_empty_erc20_balanceof_AKITA": "0x3301Ee63Fb29F863f2333Bd4466acb46CD8323E6", + "test_sload_empty_erc20_balanceof_CVC": "0x41e5560054824ea6B0732E656e3Ad64E20e94e45", + "test_sload_empty_erc20_balanceof_IHT": "0xEda8B016efa8b1161208Cf041cD86972EEE0F31E", + "test_sload_empty_erc20_balanceof_ZSC": "0x7A41e0517a5ecA4FdbC7FbebA4D4c47B9fF6DC63", + "test_sload_empty_erc20_balanceof_cbETH": "0xBe9895146f7AF43049ca1c1AE358B0541Ea49704", + "test_sload_empty_erc20_balanceof_IMT": "0x13119e34e140097a507b07a5564bde1bc375d9e6", + "test_sstore_erc20_approve_30GB_ERC20": "0x19fc17d87D946BBA47ca276f7b06Ee5737c4679C", + "test_sstore_erc20_approve_XEN": "0x06450dEe7FD2Fb8E39061434BAbCFC05599a6Fb8", + "test_sstore_erc20_approve_USDT": "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "test_sstore_erc20_approve_USDC": "0xA0b86991C6218B36c1d19D4a2E9Eb0CE3606EB48", + "test_sstore_erc20_approve_LPT": "0x58b6A8a3302369DAEc383334672404Ee733AB239", + "test_sstore_erc20_approve_SHIB": "0x95aD61B0a150d79219dCF64E1E6Cc01f0B64C4cE", + "test_sstore_erc20_approve_WETH": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "test_sstore_erc20_approve_G-CRE": "0xa3Ee21c306A700E682AbcDfE9bAA6A08F3820419", + "test_sstore_erc20_approve_MEME": "0xB131F4A55907B10d1F0A50d8Ab8FA09EC342CD74", + "test_sstore_erc20_approve_OMG": "0xd26114cD6EE289AccF82350c8d8487fedB8A0C07", + "test_sstore_erc20_approve_MATIC": "0x7d1Afa7B718fb893DB30A3abc0Cfc608AaCfEbB0", + "test_sstore_erc20_approve_stETH": "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", + "test_sstore_erc20_approve_DAI": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "test_sstore_erc20_approve_PEPE": "0x6982508145454Ce325dDbE47a25d4eC3d2311933", + "test_sstore_erc20_approve_old": "0x0cf0ee63788A0849FE5297F3407f701E122CC023", + "test_sstore_erc20_approve_BAT": "0x0D8775F648430679A709E98d2b0Cb6250d2887EF", + "test_sstore_erc20_approve_UNI": "0x1F9840a85d5aF5bf1D1762F925BdADdC4201F984", + "test_sstore_erc20_approve_AMB": "0x4dc3643Dbc642b72C158E7F3d2FF232df61cB6CE", + "test_sstore_erc20_approve_HEX": "0x2b591e99afE9f32eAA6214f7B7629768c40eEb39", + "test_sstore_erc20_approve_CRO": "0xa0b73e1ff0b80914ab6fe0444e65848c4c34450b", + "test_sstore_erc20_approve_UCASH": "0x92e52a1A235d9A103D970901066CE910AAceFD37", + "test_sstore_erc20_approve_BNB": "0xB8c77482e45F1F44dE1745F52C74426C631bDd52", + "test_sstore_erc20_approve_GSE": "0xe530441f4f73bdb6dc2fa5af7c3fc5fd551ec838", + "test_sstore_erc20_approve_MANA": "0x0F5D2FB29fb7d3cFeE444A200298f468908cC942", + "test_sstore_erc20_approve_OCN": "0x4092678e4E78230F46A1534C0fBC8Fa39780892B", + "test_sstore_erc20_approve_EIGEN": "0xEC53BF9167F50cDEb3aE105F56099AaAb9061F83", + "test_sstore_erc20_approve_COMP": "0xc00e94Cb662C3520282E6f5717214004A7f26888", + "test_sstore_erc20_approve_cUSDC": "0x39AA39c021dfbaE8faC545936693aC917d5E7563", + "test_sstore_erc20_approve_sMEME": "0xc059A531B4234d05e9EF4aC51028f7E6156E2CcE", + "test_sstore_erc20_approve_SAND": "0x3845badade8e6dff049820680d1f14bd3903a5d0", + "test_sstore_erc20_approve_AAVE": "0x7Fc66500c84A76Ad7e9c93437bFc5AC33E2DDAe9", + "test_sstore_erc20_approve_ZRX": "0xE41d2489571d322189246DaFA5ebDe1F4699F498", + "test_sstore_erc20_approve_KOK": "0x9B9647431632AF44be02ddd22477Ed94d14AacAa", + "test_sstore_erc20_approve_APE": "0x4d224452801ACEd8B2F0aebe155379bb5D594381", + "test_sstore_erc20_approve_SAI": "0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359", + "test_sstore_erc20_approve_GRT": "0xc944E90C64B2c07662A292be6244BDf05Cda44a7", + "test_sstore_erc20_approve_LRC": "0xBBbbCA6A901c926F240b89EacB641d8Aec7AEafD", + "test_sstore_erc20_approve_ELON": "0x761D38e5DDf6ccf6Cf7C55759d5210750B5D60F3", + "test_sstore_erc20_approve_QNT": "0x4a220E6096B25EADb88358cb44068A3248254675", + "test_sstore_erc20_approve_ONDO": "0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3", + "test_sstore_erc20_approve_ENJ": "0xF629cBd94d3791c9250152BD8dfBDF380E2a3B9c", + "test_sstore_erc20_approve_FET": "0x1D287CC25dAD7cCaF76a26bc660c5F7C8E2a05BD", + "test_sstore_erc20_approve_eETH": "0x6c5024Cd4F8A59110119C56f8933403A539555EB", + "test_sstore_erc20_approve_XMX": "0x0F8c45B896784A1E408526B9300519ef8660209c", + "test_sstore_erc20_approve_FTI": "0x943ed852Dadb5C3938ECdC6883718df8142de4C8", + "test_sstore_erc20_approve_WBTC": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + "test_sstore_erc20_approve_LEND": "0x80fB784B7eD66730e8b1DBd9820aFD29931aab03", + "test_sstore_erc20_approve_ELEC": "0xd49ff13661451313ca1553fd6954bd1d9b6e02b9", + "test_sstore_erc20_approve_SUSHI": "0x6B3595068778DD592e39A122f4f5a5CF09C90fE2", + "test_sstore_erc20_approve_HOT": "0x6c6EE5e31d828De241282B9606C8e98Ea48526E2", + "test_sstore_erc20_approve_MITx": "0x4a527d8fc13c5203ab24ba0944f4cb14658d1db6", + "test_sstore_erc20_approve_1INCH": "0x111111111117dC0aa78b770fA6A738034120C302", + "test_sstore_erc20_approve_USDP": "0x1456688345527bE1f37E9e627DA0837D6f08C925", + "test_sstore_erc20_approve_ETHFI": "0xfe0c30065b384f05761f15d0cc899d4f9f9cc0eb", + "test_sstore_erc20_approve_POLY": "0x9992ec3cf6a55b00978cddf2b27bc6882d88d1ec", + "test_sstore_erc20_approve_AOA": "0x9ab165d795019b6d8b3e971dda91071421305e5a", + "test_sstore_erc20_approve_STORJ": "0xB64ef51C888972c908CFacf59B47C1AfBC0Ab8aC", + "test_sstore_erc20_approve_MKR": "0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2", + "test_sstore_erc20_approve_AMP": "0xfF20817765cB7F73d4Bde2e66e067e58d11095c2", + "test_sstore_erc20_approve_VRA": "0xF411903cbc70a74d22900a5DE66A2dda66507255", + "test_sstore_erc20_approve_GTC": "0xde30da39c46104798bb5aa3fe8b9e0e1f348163f", + "test_sstore_erc20_approve_FLOKI": "0x43F11c02439E2736800433B4594994Bd43Cd066D", + "test_sstore_erc20_approve_ALT": "0x8457CA5040ad67fdebbCC8EdCE889A335Bc0fbFB", + "test_sstore_erc20_approve_IMX": "0xf57e7e7c23978c3caec3c3548e3d615c346e79ff", + "test_sstore_erc20_approve_XYO": "0x55296f69f40ea6d20e478533c15A6b08B654E758", + "test_sstore_erc20_approve_REV": "0x2ef27bf41236bd859a95209e17a43fbd26851f92", + "test_sstore_erc20_approve_FUN": "0x419d0d8bdd9af5e606ae2232ed285aff190e711b", + "test_sstore_erc20_approve_CRV": "0xD533a949740bb3306d119CC777fa900bA034cd52", + "test_sstore_erc20_approve_CHZ": "0x3506424f91fd33084466f402d5d97f05f8e3b4af", + "test_sstore_erc20_approve_SMT": "0x78Eb8DC641077F049f910659b6d580E80dC4d237", + "test_sstore_erc20_approve_SNX": "0xC011A72400E58ecD99Ee497CF89E3775d4bd732F", + "test_sstore_erc20_approve_DENT": "0x3597bfD533a99c9aa083587B074434E61Eb0A258", + "test_sstore_erc20_approve_RNDR": "0x6De037ef9aD2725EB40118Bb1702EBb27e4Aeb24", + "test_sstore_erc20_approve_SNT": "0x744d70FDBe2Ba4CF95131626614a1763DF805B9E", + "test_sstore_erc20_approve_AXS": "0xBB0E17EF65F82Ab018d8EDd776e8DD940327B28b", + "test_sstore_erc20_approve_KNC": "0xdd974D5C2e2928deA5F71b9825b8b646686BD200", + "test_sstore_erc20_approve_WEPE": "0xccB365D2e11aE4D6d74715c680f56cf58bF4bF10", + "test_sstore_erc20_approve_ZETA": "0xf091867ec603a6628ed83d274e835539d82e9cc8", + "test_sstore_erc20_approve_LYM": "0xc690f7c7fcffa6a82b79fab7508c466fefdfc8c5", + "test_sstore_erc20_approve_nCASH": "0x809826cceAb68c387726af962713b64Cb5Cb3CCA", + "test_sstore_erc20_approve_LOOKS": "0xf4d2888d29D722226FafA5d9B24F9164c092421E", + "test_sstore_erc20_approve_Monfter/Monavale": "0x275f5ad03be0fa221b4c6649b8aee09a42d9412a", + "test_sstore_erc20_approve_cETH": "0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5", + "test_sstore_erc20_approve_SALT": "0x4156D3342D5c385a87D264F90653733592000581", + "test_sstore_erc20_approve_HOGE": "0xfAd45E47083e4607302aa43c65fB3106F1cd7607", + "test_sstore_erc20_approve_REN": "0x408e41876cCCDC0F92210600ef50372656052a38", + "test_sstore_erc20_approve_ENS": "0xC56b13EBBCffa67cfB7979b900B736b3fb480D78", + "test_sstore_erc20_approve_NEXO": "0xB62132e35a6c13ee1EE0f84dC5d40bad8d815206", + "test_sstore_erc20_approve_RFR": "0xD0929d411954c47438Dc1D871dd6081F5C5e149c", + "test_sstore_erc20_approve_COFI": "0x3137619705b5fc22a3048989F983905e456B59Ab", + "test_sstore_erc20_approve_SLP": "0xcc8fa225d80b9c7d42f96e9570156c65d6cAAa25", + "test_sstore_erc20_approve_FUEL": "0xea38eaa3c86c8f9b751533ba2e562deb9acded40", + "test_sstore_erc20_approve_ENA": "0x57e114B691Db790C35207b2e685D4A43181e6061", + "test_sstore_erc20_approve_AKITA": "0x3301Ee63Fb29F863f2333Bd4466acb46CD8323E6", + "test_sstore_erc20_approve_CVC": "0x41e5560054824ea6B0732E656e3Ad64E20e94e45", + "test_sstore_erc20_approve_IHT": "0xEda8B016efa8b1161208Cf041cD86972EEE0F31E", + "test_sstore_erc20_approve_ZSC": "0x7A41e0517a5ecA4FdbC7FbebA4D4c47B9fF6DC63", + "test_sstore_erc20_approve_cbETH": "0xBe9895146f7AF43049ca1c1AE358B0541Ea49704", + "test_sstore_erc20_approve_IMT": "0x13119e34e140097a507b07a5564bde1bc375d9e6", + "test_mixed_sload_sstore_30GB_ERC20": "0x19fc17d87D946BBA47ca276f7b06Ee5737c4679C", + "test_mixed_sload_sstore_XEN": "0x06450dEe7FD2Fb8E39061434BAbCFC05599a6Fb8", + "test_mixed_sload_sstore_USDT": "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "test_mixed_sload_sstore_USDC": "0xA0b86991C6218B36c1d19D4a2E9Eb0CE3606EB48", + "test_mixed_sload_sstore_LPT": "0x58b6A8a3302369DAEc383334672404Ee733AB239", + "test_mixed_sload_sstore_SHIB": "0x95aD61B0a150d79219dCF64E1E6Cc01f0B64C4cE", + "test_mixed_sload_sstore_WETH": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "test_mixed_sload_sstore_G-CRE": "0xa3Ee21c306A700E682AbcDfE9bAA6A08F3820419", + "test_mixed_sload_sstore_MEME": "0xB131F4A55907B10d1F0A50d8Ab8FA09EC342CD74", + "test_mixed_sload_sstore_OMG": "0xd26114cD6EE289AccF82350c8d8487fedB8A0C07", + "test_mixed_sload_sstore_MATIC": "0x7d1Afa7B718fb893DB30A3abc0Cfc608AaCfEbB0", + "test_mixed_sload_sstore_stETH": "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", + "test_mixed_sload_sstore_DAI": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "test_mixed_sload_sstore_PEPE": "0x6982508145454Ce325dDbE47a25d4eC3d2311933", + "test_mixed_sload_sstore_old": "0x0cf0ee63788A0849FE5297F3407f701E122CC023", + "test_mixed_sload_sstore_BAT": "0x0D8775F648430679A709E98d2b0Cb6250d2887EF", + "test_mixed_sload_sstore_UNI": "0x1F9840a85d5aF5bf1D1762F925BdADdC4201F984", + "test_mixed_sload_sstore_AMB": "0x4dc3643Dbc642b72C158E7F3d2FF232df61cB6CE", + "test_mixed_sload_sstore_HEX": "0x2b591e99afE9f32eAA6214f7B7629768c40eEb39", + "test_mixed_sload_sstore_CRO": "0xa0b73e1ff0b80914ab6fe0444e65848c4c34450b", + "test_mixed_sload_sstore_UCASH": "0x92e52a1A235d9A103D970901066CE910AAceFD37", + "test_mixed_sload_sstore_BNB": "0xB8c77482e45F1F44dE1745F52C74426C631bDd52", + "test_mixed_sload_sstore_GSE": "0xe530441f4f73bdb6dc2fa5af7c3fc5fd551ec838", + "test_mixed_sload_sstore_MANA": "0x0F5D2FB29fb7d3cFeE444A200298f468908cC942", + "test_mixed_sload_sstore_OCN": "0x4092678e4E78230F46A1534C0fBC8Fa39780892B", + "test_mixed_sload_sstore_EIGEN": "0xEC53BF9167F50cDEb3aE105F56099AaAb9061F83", + "test_mixed_sload_sstore_COMP": "0xc00e94Cb662C3520282E6f5717214004A7f26888", + "test_mixed_sload_sstore_cUSDC": "0x39AA39c021dfbaE8faC545936693aC917d5E7563", + "test_mixed_sload_sstore_sMEME": "0xc059A531B4234d05e9EF4aC51028f7E6156E2CcE", + "test_mixed_sload_sstore_SAND": "0x3845badade8e6dff049820680d1f14bd3903a5d0", + "test_mixed_sload_sstore_AAVE": "0x7Fc66500c84A76Ad7e9c93437bFc5AC33E2DDAe9", + "test_mixed_sload_sstore_ZRX": "0xE41d2489571d322189246DaFA5ebDe1F4699F498", + "test_mixed_sload_sstore_KOK": "0x9B9647431632AF44be02ddd22477Ed94d14AacAa", + "test_mixed_sload_sstore_APE": "0x4d224452801ACEd8B2F0aebe155379bb5D594381", + "test_mixed_sload_sstore_SAI": "0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359", + "test_mixed_sload_sstore_GRT": "0xc944E90C64B2c07662A292be6244BDf05Cda44a7", + "test_mixed_sload_sstore_LRC": "0xBBbbCA6A901c926F240b89EacB641d8Aec7AEafD", + "test_mixed_sload_sstore_ELON": "0x761D38e5DDf6ccf6Cf7C55759d5210750B5D60F3", + "test_mixed_sload_sstore_QNT": "0x4a220E6096B25EADb88358cb44068A3248254675", + "test_mixed_sload_sstore_ONDO": "0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3", + "test_mixed_sload_sstore_ENJ": "0xF629cBd94d3791c9250152BD8dfBDF380E2a3B9c", + "test_mixed_sload_sstore_FET": "0x1D287CC25dAD7cCaF76a26bc660c5F7C8E2a05BD", + "test_mixed_sload_sstore_eETH": "0x6c5024Cd4F8A59110119C56f8933403A539555EB", + "test_mixed_sload_sstore_XMX": "0x0F8c45B896784A1E408526B9300519ef8660209c", + "test_mixed_sload_sstore_FTI": "0x943ed852Dadb5C3938ECdC6883718df8142de4C8", + "test_mixed_sload_sstore_WBTC": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + "test_mixed_sload_sstore_LEND": "0x80fB784B7eD66730e8b1DBd9820aFD29931aab03", + "test_mixed_sload_sstore_ELEC": "0xd49ff13661451313ca1553fd6954bd1d9b6e02b9", + "test_mixed_sload_sstore_SUSHI": "0x6B3595068778DD592e39A122f4f5a5CF09C90fE2", + "test_mixed_sload_sstore_HOT": "0x6c6EE5e31d828De241282B9606C8e98Ea48526E2", + "test_mixed_sload_sstore_MITx": "0x4a527d8fc13c5203ab24ba0944f4cb14658d1db6", + "test_mixed_sload_sstore_1INCH": "0x111111111117dC0aa78b770fA6A738034120C302", + "test_mixed_sload_sstore_USDP": "0x1456688345527bE1f37E9e627DA0837D6f08C925", + "test_mixed_sload_sstore_ETHFI": "0xfe0c30065b384f05761f15d0cc899d4f9f9cc0eb", + "test_mixed_sload_sstore_POLY": "0x9992ec3cf6a55b00978cddf2b27bc6882d88d1ec", + "test_mixed_sload_sstore_AOA": "0x9ab165d795019b6d8b3e971dda91071421305e5a", + "test_mixed_sload_sstore_STORJ": "0xB64ef51C888972c908CFacf59B47C1AfBC0Ab8aC", + "test_mixed_sload_sstore_MKR": "0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2", + "test_mixed_sload_sstore_AMP": "0xfF20817765cB7F73d4Bde2e66e067e58d11095c2", + "test_mixed_sload_sstore_VRA": "0xF411903cbc70a74d22900a5DE66A2dda66507255", + "test_mixed_sload_sstore_GTC": "0xde30da39c46104798bb5aa3fe8b9e0e1f348163f", + "test_mixed_sload_sstore_FLOKI": "0x43F11c02439E2736800433B4594994Bd43Cd066D", + "test_mixed_sload_sstore_ALT": "0x8457CA5040ad67fdebbCC8EdCE889A335Bc0fbFB", + "test_mixed_sload_sstore_IMX": "0xf57e7e7c23978c3caec3c3548e3d615c346e79ff", + "test_mixed_sload_sstore_XYO": "0x55296f69f40ea6d20e478533c15A6b08B654E758", + "test_mixed_sload_sstore_REV": "0x2ef27bf41236bd859a95209e17a43fbd26851f92", + "test_mixed_sload_sstore_FUN": "0x419d0d8bdd9af5e606ae2232ed285aff190e711b", + "test_mixed_sload_sstore_CRV": "0xD533a949740bb3306d119CC777fa900bA034cd52", + "test_mixed_sload_sstore_CHZ": "0x3506424f91fd33084466f402d5d97f05f8e3b4af", + "test_mixed_sload_sstore_SMT": "0x78Eb8DC641077F049f910659b6d580E80dC4d237", + "test_mixed_sload_sstore_SNX": "0xC011A72400E58ecD99Ee497CF89E3775d4bd732F", + "test_mixed_sload_sstore_DENT": "0x3597bfD533a99c9aa083587B074434E61Eb0A258", + "test_mixed_sload_sstore_RNDR": "0x6De037ef9aD2725EB40118Bb1702EBb27e4Aeb24", + "test_mixed_sload_sstore_SNT": "0x744d70FDBe2Ba4CF95131626614a1763DF805B9E", + "test_mixed_sload_sstore_AXS": "0xBB0E17EF65F82Ab018d8EDd776e8DD940327B28b", + "test_mixed_sload_sstore_KNC": "0xdd974D5C2e2928deA5F71b9825b8b646686BD200", + "test_mixed_sload_sstore_WEPE": "0xccB365D2e11aE4D6d74715c680f56cf58bF4bF10", + "test_mixed_sload_sstore_ZETA": "0xf091867ec603a6628ed83d274e835539d82e9cc8", + "test_mixed_sload_sstore_LYM": "0xc690f7c7fcffa6a82b79fab7508c466fefdfc8c5", + "test_mixed_sload_sstore_nCASH": "0x809826cceAb68c387726af962713b64Cb5Cb3CCA", + "test_mixed_sload_sstore_LOOKS": "0xf4d2888d29D722226FafA5d9B24F9164c092421E", + "test_mixed_sload_sstore_Monfter/Monavale": "0x275f5ad03be0fa221b4c6649b8aee09a42d9412a", + "test_mixed_sload_sstore_cETH": "0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5", + "test_mixed_sload_sstore_SALT": "0x4156D3342D5c385a87D264F90653733592000581", + "test_mixed_sload_sstore_HOGE": "0xfAd45E47083e4607302aa43c65fB3106F1cd7607", + "test_mixed_sload_sstore_REN": "0x408e41876cCCDC0F92210600ef50372656052a38", + "test_mixed_sload_sstore_ENS": "0xC56b13EBBCffa67cfB7979b900B736b3fb480D78", + "test_mixed_sload_sstore_NEXO": "0xB62132e35a6c13ee1EE0f84dC5d40bad8d815206", + "test_mixed_sload_sstore_RFR": "0xD0929d411954c47438Dc1D871dd6081F5C5e149c", + "test_mixed_sload_sstore_COFI": "0x3137619705b5fc22a3048989F983905e456B59Ab", + "test_mixed_sload_sstore_SLP": "0xcc8fa225d80b9c7d42f96e9570156c65d6cAAa25", + "test_mixed_sload_sstore_FUEL": "0xea38eaa3c86c8f9b751533ba2e562deb9acded40", + "test_mixed_sload_sstore_ENA": "0x57e114B691Db790C35207b2e685D4A43181e6061", + "test_mixed_sload_sstore_AKITA": "0x3301Ee63Fb29F863f2333Bd4466acb46CD8323E6", + "test_mixed_sload_sstore_CVC": "0x41e5560054824ea6B0732E656e3Ad64E20e94e45", + "test_mixed_sload_sstore_IHT": "0xEda8B016efa8b1161208Cf041cD86972EEE0F31E", + "test_mixed_sload_sstore_ZSC": "0x7A41e0517a5ecA4FdbC7FbebA4D4c47B9fF6DC63", + "test_mixed_sload_sstore_cbETH": "0xBe9895146f7AF43049ca1c1AE358B0541Ea49704", + "test_mixed_sload_sstore_IMT": "0x13119e34e140097a507b07a5564bde1bc375d9e6" +} diff --git a/tests/benchmark/stateful/bloatnet/test_multi_opcode.py b/tests/benchmark/stateful/bloatnet/test_multi_opcode.py index 75e0875988..0521462066 100755 --- a/tests/benchmark/stateful/bloatnet/test_multi_opcode.py +++ b/tests/benchmark/stateful/bloatnet/test_multi_opcode.py @@ -6,6 +6,10 @@ operations. """ +import json +import math +from pathlib import Path + import pytest from execution_testing import ( Account, @@ -18,9 +22,6 @@ Transaction, While, ) -from execution_testing.cli.pytest_commands.plugins.execute.pre_alloc import ( - AddressStubs, -) REFERENCE_SPEC_GIT_PATH = "DUMMY/bloatnet.md" REFERENCE_SPEC_VERSION = "1.0" @@ -62,6 +63,7 @@ def test_bloatnet_balance_extcodesize( pre: Alloc, fork: Fork, gas_benchmark_value: int, + tx_gas_limit: int, balance_first: bool, ) -> None: """ @@ -96,12 +98,6 @@ def test_bloatnet_balance_extcodesize( + 10 # While loop overhead ) - # Calculate how many contracts to access based on available gas - available_gas = ( - gas_benchmark_value - intrinsic_gas - 1000 - ) # Reserve for cleanup - contracts_needed = int(available_gas // cost_per_contract) - # Deploy factory using stub contract - NO HARDCODED VALUES # The stub "bloatnet_factory" must be provided via --address-stubs flag # The factory at that address MUST have: @@ -112,10 +108,21 @@ def test_bloatnet_balance_extcodesize( stub="bloatnet_factory", ) + # Calculate number of transactions needed (EIP-7825 compliance) + num_txs = max(1, math.ceil(gas_benchmark_value / tx_gas_limit)) + + # Calculate how many contracts to access based on available gas + total_available_gas = ( + gas_benchmark_value - (intrinsic_gas * num_txs) - 1000 + ) + total_contracts = int(total_available_gas // cost_per_contract) + contracts_per_tx = total_contracts // num_txs + # Log test requirements - deployed count read from factory storage print( - f"Test needs {contracts_needed} contracts for " - f"{gas_benchmark_value / 1_000_000:.1f}M gas. " + f"Test needs {total_contracts} contracts for " + f"{gas_benchmark_value / 1_000_000:.1f}M gas " + f"across {num_txs} transaction(s). " f"Factory storage will be checked during execution." ) @@ -128,80 +135,100 @@ def test_bloatnet_balance_extcodesize( else (extcodesize_op + balance_op) ) - # Build attack contract that reads config from factory and performs attack - attack_code = ( - # Call getConfig() on factory to get num_deployed and init_code_hash - Op.STATICCALL( - gas=Op.GAS, - address=factory_address, - args_offset=0, - args_size=0, - ret_offset=96, - ret_size=64, + # Build transactions + txs = [] + post = {} + contracts_remaining = total_contracts + salt_offset = 0 + + for i in range(num_txs): + # Last tx gets remaining contracts + tx_contracts = ( + contracts_per_tx if i < num_txs - 1 else contracts_remaining ) - # Check if call succeeded - + Op.ISZERO - + Op.PUSH2(0x1000) # Jump to error handler if failed (far jump) - + Op.JUMPI - # Load results from memory - # Memory[96:128] = num_deployed_contracts - # Memory[128:160] = init_code_hash - + Op.MLOAD(96) # Load num_deployed_contracts - + Op.MLOAD(128) # Load init_code_hash - # Setup memory for CREATE2 address generation - # Memory layout at 0: 0xFF + factory_addr(20) + salt(32) + hash(32) - + Op.MSTORE( - 0, factory_address - ) # Store factory address at memory position 0 - + Op.MSTORE8(11, 0xFF) # Store 0xFF prefix at position (32 - 20 - 1) - + Op.MSTORE(32, 0) # Store salt at position 32 - # Stack now has: [num_contracts, init_code_hash] - + Op.PUSH1(64) # Push memory position - + Op.MSTORE # Store init_code_hash at memory[64] - # Stack now has: [num_contracts] - # Main attack loop - iterate through all deployed contracts - + While( - body=( - # Generate CREATE2 addr: keccak256(0xFF+factory+salt+hash) - Op.SHA3(11, 85) # Generate CREATE2 address from memory[11:96] - # The address is now on the stack - + Op.DUP1 # Duplicate for second operation - + benchmark_ops # Execute operations in specified order - # Increment salt for next iteration - + Op.MSTORE( - 32, Op.ADD(Op.MLOAD(32), 1) - ) # Increment and store salt - ), - # Continue while we haven't reached the limit - condition=Op.DUP1 - + Op.PUSH1(1) - + Op.SWAP1 - + Op.SUB - + Op.DUP1 + contracts_remaining -= tx_contracts + + # Build attack contract that reads config from factory + attack_code = ( + # Call getConfig() on factory to get config + Op.STATICCALL( + gas=Op.GAS, + address=factory_address, + args_offset=0, + args_size=0, + ret_offset=96, + ret_size=64, + ) + # Check if call succeeded + Op.ISZERO - + Op.ISZERO, + + Op.PUSH2(0x1000) # Jump to error handler if failed (far jump) + + Op.JUMPI + # Load results from memory + # Memory[96:128] = num_deployed_contracts + # Memory[128:160] = init_code_hash + + Op.MLOAD(128) # Load init_code_hash + # Setup memory for CREATE2 address generation + # Memory layout at 0: 0xFF + factory_addr(20) + salt(32) + hash(32) + + Op.MSTORE( + 0, factory_address + ) # Store factory address at memory position 0 + + Op.MSTORE8(11, 0xFF) # Store 0xFF prefix at byte 11 + + Op.MSTORE(32, salt_offset) # Store starting salt at position 32 + # Stack now has: [init_code_hash] + + Op.PUSH1(64) # Push memory position + + Op.MSTORE # Store init_code_hash at memory[64] + # Push our iteration count onto stack + + Op.PUSH4(tx_contracts) + # Main attack loop - iterate through contracts for this tx + + While( + body=( + # Generate CREATE2 addr: keccak256(0xFF+factory+salt+hash) + Op.SHA3(11, 85) # CREATE2 addr from memory[11:96] + # The address is now on the stack + + Op.DUP1 # Duplicate for second operation + + benchmark_ops # Execute operations in specified order + # Increment salt for next iteration + + Op.MSTORE( + 32, Op.ADD(Op.MLOAD(32), 1) + ) # Increment and store salt + ), + # Continue while we haven't reached the limit + condition=Op.DUP1 + + Op.PUSH1(1) + + Op.SWAP1 + + Op.SUB + + Op.DUP1 + + Op.ISZERO + + Op.ISZERO, + ) + + Op.POP # Clean up counter ) - + Op.POP # Clean up counter - ) - # Deploy attack contract - attack_address = pre.deploy_contract(code=attack_code) + # Deploy attack contract for this tx + attack_address = pre.deploy_contract(code=attack_code) - # Run the attack - attack_tx = Transaction( - to=attack_address, - gas_limit=gas_benchmark_value, - sender=pre.fund_eoa(), - ) + # Calculate gas for this transaction + this_tx_gas = min( + tx_gas_limit, gas_benchmark_value - (i * tx_gas_limit) + ) + + txs.append( + Transaction( + to=attack_address, + gas_limit=this_tx_gas, + sender=pre.fund_eoa(), + ) + ) + + # Add to post-state + post[attack_address] = Account(storage={}) - # Post-state: just verify attack contract exists - post = { - attack_address: Account(storage={}), - } + # Update salt offset for next transaction + salt_offset += tx_contracts blockchain_test( pre=pre, - blocks=[Block(txs=[attack_tx])], + blocks=[Block(txs=txs)], post=post, ) @@ -217,6 +244,7 @@ def test_bloatnet_balance_extcodecopy( pre: Alloc, fork: Fork, gas_benchmark_value: int, + tx_gas_limit: int, balance_first: bool, ) -> None: """ @@ -253,10 +281,6 @@ def test_bloatnet_balance_extcodecopy( + 10 # While loop overhead ) - # Calculate how many contracts to access - available_gas = gas_benchmark_value - intrinsic_gas - 1000 - contracts_needed = int(available_gas // cost_per_contract) - # Deploy factory using stub contract - NO HARDCODED VALUES # The stub "bloatnet_factory" must be provided via --address-stubs flag # The factory at that address MUST have: @@ -267,10 +291,21 @@ def test_bloatnet_balance_extcodecopy( stub="bloatnet_factory", ) + # Calculate number of transactions needed (EIP-7825 compliance) + num_txs = max(1, math.ceil(gas_benchmark_value / tx_gas_limit)) + + # Calculate how many contracts to access + total_available_gas = ( + gas_benchmark_value - (intrinsic_gas * num_txs) - 1000 + ) + total_contracts = int(total_available_gas // cost_per_contract) + contracts_per_tx = total_contracts // num_txs + # Log test requirements - deployed count read from factory storage print( - f"Test needs {contracts_needed} contracts for " - f"{gas_benchmark_value / 1_000_000:.1f}M gas. " + f"Test needs {total_contracts} contracts for " + f"{gas_benchmark_value / 1_000_000:.1f}M gas " + f"across {num_txs} transaction(s). " f"Factory storage will be checked during execution." ) @@ -290,80 +325,99 @@ def test_bloatnet_balance_extcodecopy( else (extcodecopy_op + balance_op) ) - # Build attack contract that reads config from factory and performs attack - attack_code = ( - # Call getConfig() on factory to get num_deployed and init_code_hash - Op.STATICCALL( - gas=Op.GAS, - address=factory_address, - args_offset=0, - args_size=0, - ret_offset=96, - ret_size=64, + # Build transactions + txs = [] + post = {} + contracts_remaining = total_contracts + salt_offset = 0 + + for i in range(num_txs): + # Last tx gets remaining contracts + tx_contracts = ( + contracts_per_tx if i < num_txs - 1 else contracts_remaining ) - # Check if call succeeded - + Op.ISZERO - + Op.PUSH2(0x1000) # Jump to error handler if failed (far jump) - + Op.JUMPI - # Load results from memory - # Memory[96:128] = num_deployed_contracts - # Memory[128:160] = init_code_hash - + Op.MLOAD(96) # Load num_deployed_contracts - + Op.MLOAD(128) # Load init_code_hash - # Setup memory for CREATE2 address generation - # Memory layout at 0: 0xFF + factory_addr(20) + salt(32) + hash(32) - + Op.MSTORE( - 0, factory_address - ) # Store factory address at memory position 0 - + Op.MSTORE8(11, 0xFF) # Store 0xFF prefix at position (32 - 20 - 1) - + Op.MSTORE(32, 0) # Store salt at position 32 - # Stack now has: [num_contracts, init_code_hash] - + Op.PUSH1(64) # Push memory position - + Op.MSTORE # Store init_code_hash at memory[64] - # Stack now has: [num_contracts] - # Main attack loop - iterate through all deployed contracts - + While( - body=( - # Generate CREATE2 address - Op.SHA3(11, 85) # Generate CREATE2 address from memory[11:96] - # The address is now on the stack - + Op.DUP1 # Duplicate for later operations - + benchmark_ops # Execute operations in specified order - # Increment salt for next iteration - + Op.MSTORE( - 32, Op.ADD(Op.MLOAD(32), 1) - ) # Increment and store salt - ), - # Continue while counter > 0 - condition=Op.DUP1 - + Op.PUSH1(1) - + Op.SWAP1 - + Op.SUB - + Op.DUP1 + contracts_remaining -= tx_contracts + + # Build attack contract that reads config from factory + attack_code = ( + # Call getConfig() on factory to get config + Op.STATICCALL( + gas=Op.GAS, + address=factory_address, + args_offset=0, + args_size=0, + ret_offset=96, + ret_size=64, + ) + # Check if call succeeded + Op.ISZERO - + Op.ISZERO, + + Op.PUSH2(0x1000) # Jump to error handler if failed (far jump) + + Op.JUMPI + # Load results from memory + # Memory[128:160] = init_code_hash + + Op.MLOAD(128) # Load init_code_hash + # Setup memory for CREATE2 address generation + # Memory layout at 0: 0xFF + factory_addr(20) + salt(32) + hash(32) + + Op.MSTORE( + 0, factory_address + ) # Store factory address at memory position 0 + + Op.MSTORE8(11, 0xFF) # Store 0xFF prefix at byte 11 + + Op.MSTORE(32, salt_offset) # Store starting salt at position 32 + # Stack now has: [init_code_hash] + + Op.PUSH1(64) # Push memory position + + Op.MSTORE # Store init_code_hash at memory[64] + # Push our iteration count onto stack + + Op.PUSH4(tx_contracts) + # Main attack loop - iterate through contracts for this tx + + While( + body=( + # Generate CREATE2 address + Op.SHA3(11, 85) # CREATE2 addr from memory[11:96] + # The address is now on the stack + + Op.DUP1 # Duplicate for later operations + + benchmark_ops # Execute operations in specified order + # Increment salt for next iteration + + Op.MSTORE( + 32, Op.ADD(Op.MLOAD(32), 1) + ) # Increment and store salt + ), + # Continue while counter > 0 + condition=Op.DUP1 + + Op.PUSH1(1) + + Op.SWAP1 + + Op.SUB + + Op.DUP1 + + Op.ISZERO + + Op.ISZERO, + ) + + Op.POP # Clean up counter ) - + Op.POP # Clean up counter - ) - # Deploy attack contract - attack_address = pre.deploy_contract(code=attack_code) + # Deploy attack contract for this tx + attack_address = pre.deploy_contract(code=attack_code) - # Run the attack - attack_tx = Transaction( - to=attack_address, - gas_limit=gas_benchmark_value, - sender=pre.fund_eoa(), - ) + # Calculate gas for this transaction + this_tx_gas = min( + tx_gas_limit, gas_benchmark_value - (i * tx_gas_limit) + ) - # Post-state - post = { - attack_address: Account(storage={}), - } + txs.append( + Transaction( + to=attack_address, + gas_limit=this_tx_gas, + sender=pre.fund_eoa(), + ) + ) + + # Add to post-state + post[attack_address] = Account(storage={}) + + # Update salt offset for next transaction + salt_offset += tx_contracts blockchain_test( pre=pre, - blocks=[Block(txs=[attack_tx])], + blocks=[Block(txs=txs)], post=post, ) @@ -379,6 +433,7 @@ def test_bloatnet_balance_extcodehash( pre: Alloc, fork: Fork, gas_benchmark_value: int, + tx_gas_limit: int, balance_first: bool, ) -> None: """ @@ -413,22 +468,27 @@ def test_bloatnet_balance_extcodehash( + 10 # While loop overhead ) - # Calculate how many contracts to access based on available gas - available_gas = ( - gas_benchmark_value - intrinsic_gas - 1000 - ) # Reserve for cleanup - contracts_needed = int(available_gas // cost_per_contract) - # Deploy factory using stub contract factory_address = pre.deploy_contract( code=Bytecode(), stub="bloatnet_factory", ) + # Calculate number of transactions needed (EIP-7825 compliance) + num_txs = max(1, math.ceil(gas_benchmark_value / tx_gas_limit)) + + # Calculate how many contracts to access based on available gas + total_available_gas = ( + gas_benchmark_value - (intrinsic_gas * num_txs) - 1000 + ) + total_contracts = int(total_available_gas // cost_per_contract) + contracts_per_tx = total_contracts // num_txs + # Log test requirements print( - f"Test needs {contracts_needed} contracts for " - f"{gas_benchmark_value / 1_000_000:.1f}M gas. " + f"Test needs {total_contracts} contracts for " + f"{gas_benchmark_value / 1_000_000:.1f}M gas " + f"across {num_txs} transaction(s). " f"Factory storage will be checked during execution." ) @@ -441,69 +501,90 @@ def test_bloatnet_balance_extcodehash( else (extcodehash_op + balance_op) ) - # Build attack contract that reads config from factory and performs attack - attack_code = ( - # Call getConfig() on factory to get num_deployed and init_code_hash - Op.STATICCALL( - gas=Op.GAS, - address=factory_address, - args_offset=0, - args_size=0, - ret_offset=96, - ret_size=64, + # Build transactions + txs = [] + post = {} + contracts_remaining = total_contracts + salt_offset = 0 + + for i in range(num_txs): + # Last tx gets remaining contracts + tx_contracts = ( + contracts_per_tx if i < num_txs - 1 else contracts_remaining ) - # Check if call succeeded - + Op.ISZERO - + Op.PUSH2(0x1000) # Jump to error handler if failed - + Op.JUMPI - # Load results from memory - + Op.MLOAD(96) # Load num_deployed_contracts - + Op.MLOAD(128) # Load init_code_hash - # Setup memory for CREATE2 address generation - + Op.MSTORE(0, factory_address) - + Op.MSTORE8(11, 0xFF) - + Op.MSTORE(32, 0) # Initial salt - + Op.PUSH1(64) - + Op.MSTORE # Store init_code_hash - # Main attack loop - + While( - body=( - # Generate CREATE2 address - Op.SHA3(11, 85) - + Op.DUP1 # Duplicate for second operation - + benchmark_ops # Execute operations in specified order - # Increment salt - + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)) - ), - condition=Op.DUP1 - + Op.PUSH1(1) - + Op.SWAP1 - + Op.SUB - + Op.DUP1 + contracts_remaining -= tx_contracts + + # Build attack contract that reads config from factory + attack_code = ( + # Call getConfig() on factory to get config + Op.STATICCALL( + gas=Op.GAS, + address=factory_address, + args_offset=0, + args_size=0, + ret_offset=96, + ret_size=64, + ) + # Check if call succeeded + Op.ISZERO - + Op.ISZERO, + + Op.PUSH2(0x1000) # Jump to error handler if failed + + Op.JUMPI + # Load results from memory + + Op.MLOAD(128) # Load init_code_hash + # Setup memory for CREATE2 address generation + + Op.MSTORE(0, factory_address) + + Op.MSTORE8(11, 0xFF) + + Op.MSTORE(32, salt_offset) # Starting salt for this tx + + Op.PUSH1(64) + + Op.MSTORE # Store init_code_hash + # Push our iteration count onto stack + + Op.PUSH4(tx_contracts) + # Main attack loop + + While( + body=( + # Generate CREATE2 address + Op.SHA3(11, 85) + + Op.DUP1 # Duplicate for second operation + + benchmark_ops # Execute operations in specified order + # Increment salt + + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)) + ), + condition=Op.DUP1 + + Op.PUSH1(1) + + Op.SWAP1 + + Op.SUB + + Op.DUP1 + + Op.ISZERO + + Op.ISZERO, + ) + + Op.POP # Clean up counter ) - + Op.POP # Clean up counter - ) - # Deploy attack contract - attack_address = pre.deploy_contract(code=attack_code) + # Deploy attack contract for this tx + attack_address = pre.deploy_contract(code=attack_code) - # Run the attack - attack_tx = Transaction( - to=attack_address, - gas_limit=gas_benchmark_value, - sender=pre.fund_eoa(), - ) + # Calculate gas for this transaction + this_tx_gas = min( + tx_gas_limit, gas_benchmark_value - (i * tx_gas_limit) + ) + + txs.append( + Transaction( + to=attack_address, + gas_limit=this_tx_gas, + sender=pre.fund_eoa(), + ) + ) + + # Add to post-state + post[attack_address] = Account(storage={}) - # Post-state - post = { - attack_address: Account(storage={}), - } + # Update salt offset for next transaction + salt_offset += tx_contracts blockchain_test( pre=pre, - blocks=[Block(txs=[attack_tx])], + blocks=[Block(txs=txs)], post=post, ) @@ -512,9 +593,21 @@ def test_bloatnet_balance_extcodehash( BALANCEOF_SELECTOR = 0x70A08231 # balanceOf(address) APPROVE_SELECTOR = 0x095EA7B3 # approve(address,uint256) +# Load token names from stubs.json for test parametrization +_STUBS_FILE = Path(__file__).parent / "stubs.json" +with open(_STUBS_FILE) as f: + _STUBS = json.load(f) + +# Extract unique token names for mixed sload/sstore tests +MIXED_TOKENS = [ + k.replace("test_mixed_sload_sstore_", "") + for k in _STUBS.keys() + if k.startswith("test_mixed_sload_sstore_") +] + @pytest.mark.valid_from("Prague") -@pytest.mark.parametrize("num_contracts", [1, 5, 10, 20, 100]) +@pytest.mark.parametrize("token_name", MIXED_TOKENS) @pytest.mark.parametrize( "sload_percent,sstore_percent", [ @@ -530,49 +623,22 @@ def test_mixed_sload_sstore( pre: Alloc, fork: Fork, gas_benchmark_value: int, - address_stubs: AddressStubs | None, - num_contracts: int, + tx_gas_limit: int, + token_name: str, sload_percent: int, sstore_percent: int, - request: pytest.FixtureRequest, ) -> None: """ BloatNet mixed SLOAD/SSTORE benchmark with configurable operation ratios. This test: - 1. Filters stubs matching test name prefix - (e.g., test_mixed_sload_sstore_*) - 2. Uses first N contracts based on num_contracts parameter - 3. Divides gas budget evenly across all selected contracts - 4. For each contract, divides gas into SLOAD and SSTORE portions by - percentage - 5. Executes balanceOf (SLOAD) and approve (SSTORE) calls per the ratio - 6. Stresses clients with combined read/write operations on large - contracts + 1. Uses a single ERC20 contract specified by token_name parameter + 2. Allocates full gas budget to that contract + 3. Divides gas into SLOAD and SSTORE portions by percentage + 4. Executes balanceOf (SLOAD) and approve (SSTORE) calls per the ratio + 5. Stresses clients with combined read/write operations on large contracts """ - # Extract test function name for stub filtering - # Remove parametrization suffix - test_name = request.node.name.split("[")[0] - - # Filter stubs that match the test name prefix - matching_stubs = [] - if address_stubs is not None: - matching_stubs = [ - stub_name - for stub_name in address_stubs.root.keys() - if stub_name.startswith(test_name) - ] - - # Validate we have enough stubs - if len(matching_stubs) < num_contracts: - pytest.fail( - f"Not enough matching stubs for test '{test_name}'. " - f"Required: {num_contracts}, Found: {len(matching_stubs)}. " - f"Matching stubs: {matching_stubs}" - ) - - # Select first N stubs - selected_stubs = matching_stubs[:num_contracts] + stub_name = f"test_mixed_sload_sstore_{token_name}" gas_costs = fork.gas_costs() # Calculate gas costs @@ -642,14 +708,6 @@ def test_mixed_sload_sstore( + gas_costs.G_VERY_LOW # PUSH1 0 for return offset (3) ) - # Calculate gas budget per contract - available_gas = gas_benchmark_value - intrinsic_gas - gas_per_contract = available_gas // num_contracts - - # For each contract, split gas by percentage - sload_gas_per_contract = (gas_per_contract * sload_percent) // 100 - sstore_gas_per_contract = (gas_per_contract * sstore_percent) // 100 - # Account for cold/warm transitions in CALL costs # First SLOAD call is COLD (2600), rest are WARM (100) sload_warm_cost = ( @@ -660,9 +718,6 @@ def test_mixed_sload_sstore( cold_warm_diff = ( gas_costs.G_COLD_ACCOUNT_ACCESS - gas_costs.G_WARM_ACCOUNT_ACCESS ) - sload_calls_per_contract = int( - (sload_gas_per_contract - cold_warm_diff) // sload_warm_cost - ) # First SSTORE call is COLD (2600), rest are WARM (100) sstore_warm_cost = ( @@ -670,47 +725,64 @@ def test_mixed_sload_sstore( + gas_costs.G_WARM_ACCOUNT_ACCESS + sstore_erc20_internal ) - sstore_calls_per_contract = int( - (sstore_gas_per_contract - cold_warm_diff) // sstore_warm_cost + + # Deploy ERC20 contract using stub + erc20_address = pre.deploy_contract( + code=Bytecode(), + stub=stub_name, ) - # Deploy selected ERC20 contracts using stubs - erc20_addresses = [] - for stub_name in selected_stubs: - addr = pre.deploy_contract( - code=Bytecode(), - stub=stub_name, - ) - erc20_addresses.append(addr) + # Calculate number of transactions needed (EIP-7825 compliance) + num_txs = max(1, math.ceil(gas_benchmark_value / tx_gas_limit)) + + # Calculate total available gas and split by percentage + total_available_gas = gas_benchmark_value - (intrinsic_gas * num_txs) + sload_gas = (total_available_gas * sload_percent) // 100 + sstore_gas = (total_available_gas * sstore_percent) // 100 + + # Calculate total calls for each operation type + total_sload_calls = int((sload_gas - cold_warm_diff) // sload_warm_cost) + total_sstore_calls = int((sstore_gas - cold_warm_diff) // sstore_warm_cost) + + # Distribute calls across transactions + sload_calls_per_tx = total_sload_calls // num_txs + sstore_calls_per_tx = total_sstore_calls // num_txs # Log test requirements print( - f"Total gas budget: {gas_benchmark_value / 1_000_000:.1f}M gas. " - f"~{gas_per_contract / 1_000_000:.1f}M gas per contract " + f"Token: {token_name}, " + f"Total gas budget: {gas_benchmark_value / 1_000_000:.1f}M gas " f"({sload_percent}% SLOAD, {sstore_percent}% SSTORE). " - f"Per contract: {sload_calls_per_contract} balanceOf calls, " - f"{sstore_calls_per_contract} approve calls." + f"{total_sload_calls} balanceOf, {total_sstore_calls} approve " + f"across {num_txs} tx(s)." ) - # Build attack code that loops through each contract - attack_code: Bytecode = ( - Op.JUMPDEST # Entry point - # Store selector once for all contracts - + Op.MSTORE(offset=0, value=BALANCEOF_SELECTOR) - ) + # Build transactions + txs = [] + post = {} + sload_remaining = total_sload_calls + sstore_remaining = total_sstore_calls - for erc20_address in erc20_addresses: - # For each contract, execute SLOAD operations (balanceOf) - attack_code += ( - # Initialize counter in memory[32] = number of balanceOf calls - Op.MSTORE(offset=32, value=sload_calls_per_contract) - # Loop for balanceOf calls + for i in range(num_txs): + # Last tx gets remaining calls + tx_sload_calls = ( + sload_calls_per_tx if i < num_txs - 1 else sload_remaining + ) + tx_sstore_calls = ( + sstore_calls_per_tx if i < num_txs - 1 else sstore_remaining + ) + sload_remaining -= tx_sload_calls + sstore_remaining -= tx_sstore_calls + + # Build attack code for this transaction + attack_code: Bytecode = ( + Op.JUMPDEST # Entry point + + Op.MSTORE(offset=0, value=BALANCEOF_SELECTOR) + # SLOAD operations (balanceOf) + + Op.MSTORE(offset=32, value=tx_sload_calls) + While( condition=Op.MLOAD(32) + Op.ISZERO + Op.ISZERO, body=( - # Call balanceOf(address) on ERC20 contract - # args_offset=28 reads: selector from MEM[28:32] + address - # from MEM[32:64] Op.CALL( address=erc20_address, value=0, @@ -719,32 +791,17 @@ def test_mixed_sload_sstore( ret_offset=0, ret_size=0, ) - + Op.POP # Discard CALL success status - # Decrement counter + + Op.POP + Op.MSTORE(offset=32, value=Op.SUB(Op.MLOAD(32), 1)) ), ) - ) - - # For each contract, execute SSTORE operations (approve) - # Reuse the same memory layout as balanceOf - attack_code += ( - # Store approve selector at memory[0] (reusing same slot) - Op.MSTORE(offset=0, value=APPROVE_SELECTOR) - # Initialize counter in memory[32] = number of approve calls - # (reusing same slot) - + Op.MSTORE(offset=32, value=sstore_calls_per_contract) - # Loop for approve calls + # SSTORE operations (approve) + + Op.MSTORE(offset=0, value=APPROVE_SELECTOR) + + Op.MSTORE(offset=32, value=tx_sstore_calls) + While( condition=Op.MLOAD(32) + Op.ISZERO + Op.ISZERO, body=( - # Store spender at memory[64] (counter as spender/amount) Op.MSTORE(offset=64, value=Op.MLOAD(32)) - # Call approve(spender, amount) on ERC20 contract - # args_offset=28 reads: selector from MEM[28:32] + - # spender from MEM[32:64] + amount from MEM[64:96] - # Note: counter at MEM[32:64] is reused as spender, - # and value at MEM[64:96] serves as the amount + Op.CALL( address=erc20_address, value=0, @@ -753,30 +810,33 @@ def test_mixed_sload_sstore( ret_offset=0, ret_size=0, ) - + Op.POP # Discard CALL success status - # Decrement counter + + Op.POP + Op.MSTORE(offset=32, value=Op.SUB(Op.MLOAD(32), 1)) ), ) ) - # Deploy attack contract - attack_address = pre.deploy_contract(code=attack_code) + # Deploy attack contract for this tx + attack_address = pre.deploy_contract(code=attack_code) - # Run the attack - attack_tx = Transaction( - to=attack_address, - gas_limit=gas_benchmark_value, - sender=pre.fund_eoa(), - ) + # Calculate gas for this transaction + this_tx_gas = min( + tx_gas_limit, gas_benchmark_value - (i * tx_gas_limit) + ) + + txs.append( + Transaction( + to=attack_address, + gas_limit=this_tx_gas, + sender=pre.fund_eoa(), + ) + ) - # Post-state - post = { - attack_address: Account(storage={}), - } + # Add to post-state + post[attack_address] = Account(storage={}) blockchain_test( pre=pre, - blocks=[Block(txs=[attack_tx])], + blocks=[Block(txs=txs)], post=post, ) diff --git a/tests/benchmark/stateful/bloatnet/test_single_opcode.py b/tests/benchmark/stateful/bloatnet/test_single_opcode.py index 34e2c46434..664ba48db0 100644 --- a/tests/benchmark/stateful/bloatnet/test_single_opcode.py +++ b/tests/benchmark/stateful/bloatnet/test_single_opcode.py @@ -7,6 +7,10 @@ to benchmark specific state-handling bottlenecks. """ +import json +import math +from pathlib import Path + import pytest from execution_testing import ( Account, @@ -19,9 +23,6 @@ Transaction, While, ) -from execution_testing.cli.pytest_commands.plugins.execute.pre_alloc import ( - AddressStubs, -) REFERENCE_SPEC_GIT_PATH = "DUMMY/bloatnet.md" REFERENCE_SPEC_VERSION = "1.0" @@ -31,6 +32,23 @@ APPROVE_SELECTOR = 0x095EA7B3 # approve(address,uint256) ALLOWANCE_SELECTOR = 0xDD62ED3E # allowance(address,address) +# Load token names from stubs.json for test parametrization +_STUBS_FILE = Path(__file__).parent / "stubs.json" +with open(_STUBS_FILE) as f: + _STUBS = json.load(f) + +# Extract unique token names for each test type +SLOAD_TOKENS = [ + k.replace("test_sload_empty_erc20_balanceof_", "") + for k in _STUBS.keys() + if k.startswith("test_sload_empty_erc20_balanceof_") +] +SSTORE_TOKENS = [ + k.replace("test_sstore_erc20_approve_", "") + for k in _STUBS.keys() + if k.startswith("test_sstore_erc20_approve_") +] + # SLOAD BENCHMARK ARCHITECTURE: # @@ -78,51 +96,28 @@ @pytest.mark.valid_from("Prague") -@pytest.mark.parametrize("num_contracts", [1, 5, 10, 20, 100]) +@pytest.mark.parametrize("token_name", SLOAD_TOKENS) def test_sload_empty_erc20_balanceof( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, gas_benchmark_value: int, - address_stubs: AddressStubs | None, - num_contracts: int, - request: pytest.FixtureRequest, + tx_gas_limit: int, + token_name: str, ) -> None: """ BloatNet SLOAD benchmark using ERC20 balanceOf queries on random addresses. This test: - 1. Filters stubs matching test name prefix - (e.g., test_sload_empty_erc20_balanceof_*) - 2. Uses first N contracts based on num_contracts parameter - 3. Splits gas budget evenly across the selected contracts - 4. Queries balanceOf() incrementally starting by 0 and increasing by 1 + 1. Uses a single ERC20 contract specified by token_name parameter + 2. Allocates full gas budget to that contract + 3. Queries balanceOf() incrementally starting by 0 and increasing by 1 (thus forcing SLOADs to non-existing addresses) + 4. Splits into multiple transactions if gas_benchmark_value > tx_gas_limit + (EIP-7825 compliance) """ - # Extract test function name for stub filtering - # Remove parametrization suffix - test_name = request.node.name.split("[")[0] - - # Filter stubs that match the test name prefix - matching_stubs = [] - if address_stubs is not None: - matching_stubs = [ - stub_name - for stub_name in address_stubs.root.keys() - if stub_name.startswith(test_name) - ] - - # Validate we have enough stubs - if len(matching_stubs) < num_contracts: - pytest.fail( - f"Not enough matching stubs for test '{test_name}'. " - f"Required: {num_contracts}, Found: {len(matching_stubs)}. " - f"Matching stubs: {matching_stubs}" - ) - - # Select first N stubs - selected_stubs = matching_stubs[:num_contracts] + stub_name = f"test_sload_empty_erc20_balanceof_{token_name}" gas_costs = fork.gas_costs() # Calculate gas costs @@ -154,14 +149,7 @@ def test_sload_empty_erc20_balanceof( # RETURN costs 0 gas ) - # Calculate gas budget per contract - available_gas = gas_benchmark_value - intrinsic_gas - gas_per_contract = available_gas // num_contracts - - # For each contract: first call is COLD (2600), subsequent are WARM (100) - # Solve for calls_per_contract: - # gas_per_contract = cold_call + (calls-1) * warm_call - # Simplifies to: gas = cold_warm_diff + calls * warm_call_cost + # First call is COLD (2600), subsequent are WARM (100) warm_call_cost = ( loop_overhead + gas_costs.G_WARM_ACCOUNT_ACCESS + erc20_internal_gas ) @@ -169,49 +157,47 @@ def test_sload_empty_erc20_balanceof( gas_costs.G_COLD_ACCOUNT_ACCESS - gas_costs.G_WARM_ACCOUNT_ACCESS ) - calls_per_contract = int( - (gas_per_contract - cold_warm_diff) // warm_call_cost + # Deploy ERC20 contract using stub + # In execute mode: stub points to already-deployed contract on chain + # In fill mode: empty bytecode is deployed as placeholder + erc20_address = pre.deploy_contract( + code=Bytecode(), + stub=stub_name, ) - # Deploy selected ERC20 contracts using stubs - # In execute mode: stubs point to already-deployed contracts on chain - # In fill mode: empty bytecode is deployed as placeholder - erc20_addresses = [] - for stub_name in selected_stubs: - addr = pre.deploy_contract( - # Required parameter, ignored for stubs in execute mode - code=Bytecode(), - stub=stub_name, - ) - erc20_addresses.append(addr) + # Calculate number of transactions needed (EIP-7825 compliance) + num_txs = max(1, math.ceil(gas_benchmark_value / tx_gas_limit)) + + # Calculate total calls based on full gas budget + total_available_gas = gas_benchmark_value - (intrinsic_gas * num_txs) + total_calls = int((total_available_gas - cold_warm_diff) // warm_call_cost) + calls_per_tx = total_calls // num_txs # Log test requirements print( - f"Total gas budget: {gas_benchmark_value / 1_000_000:.1f}M gas. " - f"~{gas_per_contract / 1_000_000:.1f}M gas per contract, " - f"{calls_per_contract} balanceOf calls per contract." - ) - - # Build attack code that loops through each contract - attack_code: Bytecode = ( - Op.JUMPDEST # Entry point - # Store selector once for all contracts - + Op.MSTORE(offset=0, value=BALANCEOF_SELECTOR) + f"Token: {token_name}, " + f"Total gas budget: {gas_benchmark_value / 1_000_000:.1f}M gas, " + f"{total_calls} balanceOf calls across {num_txs} transaction(s)." ) - for erc20_address in erc20_addresses: - # For each contract, initialize counter and loop - attack_code += ( - # Initialize counter in memory[32] = number of calls - Op.MSTORE(offset=32, value=calls_per_contract) - # Loop for this specific contract + # Build transactions + txs = [] + post = {} + calls_remaining = total_calls + + for i in range(num_txs): + # Last tx gets remaining calls + tx_calls = calls_per_tx if i < num_txs - 1 else calls_remaining + calls_remaining -= tx_calls + + # Build attack code for this transaction + attack_code: Bytecode = ( + Op.JUMPDEST # Entry point + + Op.MSTORE(offset=0, value=BALANCEOF_SELECTOR) + + Op.MSTORE(offset=32, value=tx_calls) + While( - # Continue while counter > 0 condition=Op.MLOAD(32) + Op.ISZERO + Op.ISZERO, body=( - # Call balanceOf(address) on ERC20 contract - # args_offset=28 reads: selector from MEM[28:32] + address - # from MEM[32:64] Op.CALL( address=erc20_address, value=0, @@ -220,98 +206,65 @@ def test_sload_empty_erc20_balanceof( ret_offset=0, ret_size=0, ) - + Op.POP # Discard CALL success status - # Decrement counter: counter - 1 + + Op.POP + Op.MSTORE(offset=32, value=Op.SUB(Op.MLOAD(32), 1)) ), ) ) - # Deploy attack contract - attack_address = pre.deploy_contract(code=attack_code) + # Deploy attack contract for this tx + attack_address = pre.deploy_contract(code=attack_code) - # Run the attack - attack_tx = Transaction( - to=attack_address, - gas_limit=gas_benchmark_value, - sender=pre.fund_eoa(), - ) + # Calculate gas for this transaction + this_tx_gas = min( + tx_gas_limit, gas_benchmark_value - (i * tx_gas_limit) + ) + + txs.append( + Transaction( + to=attack_address, + gas_limit=this_tx_gas, + sender=pre.fund_eoa(), + ) + ) - # Post-state - post = { - attack_address: Account(storage={}), - } + # Add to post-state + post[attack_address] = Account(storage={}) blockchain_test( pre=pre, - blocks=[Block(txs=[attack_tx])], + blocks=[Block(txs=txs)], post=post, ) @pytest.mark.valid_from("Prague") -@pytest.mark.parametrize("num_contracts", [1, 5, 10, 20, 100]) +@pytest.mark.parametrize("token_name", SSTORE_TOKENS) def test_sstore_erc20_approve( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, gas_benchmark_value: int, - address_stubs: AddressStubs | None, - num_contracts: int, - request: pytest.FixtureRequest, + tx_gas_limit: int, + token_name: str, ) -> None: """ BloatNet SSTORE benchmark using ERC20 approve to write to storage. This test: - 1. Filters stubs matching test name prefix - (e.g., test_sstore_erc20_approve_*) - 2. Uses first N contracts based on num_contracts parameter - 3. Splits gas budget evenly across the selected contracts - 4. Calls approve(spender, amount) incrementally (counter as spender) - 5. Forces SSTOREs to allowance mapping storage slots + 1. Uses a single ERC20 contract specified by token_name parameter + 2. Allocates full gas budget to that contract + 3. Calls approve(spender, amount) incrementally (counter as spender) + 4. Forces SSTOREs to allowance mapping storage slots + 5. Splits into multiple transactions if gas_benchmark_value > tx_gas_limit + (EIP-7825 compliance) """ - # Extract test function name for stub filtering - # Remove parametrization suffix - test_name = request.node.name.split("[")[0] - - # Filter stubs that match the test name prefix - matching_stubs = [] - if address_stubs is not None: - matching_stubs = [ - stub_name - for stub_name in address_stubs.root.keys() - if stub_name.startswith(test_name) - ] - - # Validate we have enough stubs - if len(matching_stubs) < num_contracts: - pytest.fail( - f"Not enough matching stubs for test '{test_name}'. " - f"Required: {num_contracts}, Found: {len(matching_stubs)}. " - f"Matching stubs: {matching_stubs}" - ) - - # Select first N stubs - selected_stubs = matching_stubs[:num_contracts] + stub_name = f"test_sstore_erc20_approve_{token_name}" gas_costs = fork.gas_costs() # Calculate gas costs intrinsic_gas = fork.transaction_intrinsic_cost_calculator()(calldata=b"") - # Per-contract fixed overhead (setup + teardown) - memory_expansion_cost = 15 # Memory expansion to 160 bytes (5 words) - overhead_per_contract = ( - gas_costs.G_VERY_LOW # MSTORE to initialize counter (3) - + memory_expansion_cost # Memory expansion (15) - + gas_costs.G_JUMPDEST # JUMPDEST at loop start (1) - + gas_costs.G_LOW # MLOAD for While condition check (5) - + gas_costs.G_BASE # ISZERO (2) - + gas_costs.G_BASE # ISZERO (2) - + gas_costs.G_MID # JUMPI (8) - + gas_costs.G_BASE # POP to clean up counter at end (2) - ) # = 38 - # Fixed overhead per iteration (loop mechanics, independent of warm/cold) loop_overhead = ( # Attack contract loop body operations @@ -353,12 +306,7 @@ def test_sstore_erc20_approve( # RETURN costs 0 gas ) - # Calculate total gas needed - total_overhead = intrinsic_gas + (overhead_per_contract * num_contracts) - available_gas_for_iterations = gas_benchmark_value - total_overhead - - # For each contract: first call is COLD (2600), subsequent are WARM (100) - # Solve for calls per contract accounting for cold/warm transition + # First call is COLD (2600), subsequent are WARM (100) warm_call_cost = ( loop_overhead + gas_costs.G_WARM_ACCOUNT_ACCESS + erc20_internal_gas ) @@ -366,55 +314,48 @@ def test_sstore_erc20_approve( gas_costs.G_COLD_ACCOUNT_ACCESS - gas_costs.G_WARM_ACCOUNT_ACCESS ) - # Per contract: gas_available = cold_warm_diff + calls * warm_call_cost - gas_per_contract = available_gas_for_iterations // num_contracts - calls_per_contract = int( - (gas_per_contract - cold_warm_diff) // warm_call_cost + # Deploy ERC20 contract using stub + erc20_address = pre.deploy_contract( + code=Bytecode(), + stub=stub_name, ) - # Deploy selected ERC20 contracts using stubs - erc20_addresses = [] - for stub_name in selected_stubs: - addr = pre.deploy_contract( - code=Bytecode(), - stub=stub_name, - ) - erc20_addresses.append(addr) + # Calculate number of transactions needed (EIP-7825 compliance) + num_txs = max(1, math.ceil(gas_benchmark_value / tx_gas_limit)) + + # Calculate total calls based on full gas budget + total_available_gas = gas_benchmark_value - (intrinsic_gas * num_txs) + total_calls = int((total_available_gas - cold_warm_diff) // warm_call_cost) + calls_per_tx = total_calls // num_txs # Log test requirements print( - f"Total gas budget: {gas_benchmark_value / 1_000_000:.1f}M gas. " - f"Intrinsic: {intrinsic_gas}, " - f"Overhead per contract: {overhead_per_contract}, " - f"Warm call cost: {warm_call_cost}. " - f"{calls_per_contract} approve calls per contract " - f"({num_contracts} contracts)." - ) - - # Build attack code that loops through each contract - attack_code: Bytecode = ( - Op.JUMPDEST # Entry point - # Store selector once for all contracts - + Op.MSTORE(offset=0, value=APPROVE_SELECTOR) + f"Token: {token_name}, " + f"Total gas budget: {gas_benchmark_value / 1_000_000:.1f}M gas, " + f"{total_calls} approve calls across {num_txs} transaction(s)." ) - for erc20_address in erc20_addresses: - # For each contract, initialize counter and loop - attack_code += ( - # Initialize counter in memory[32] = number of calls - Op.MSTORE(offset=32, value=calls_per_contract) - # Loop for this specific contract + # Build transactions + txs = [] + post = {} + calls_remaining = total_calls + + for i in range(num_txs): + # Last tx gets remaining calls + tx_calls = calls_per_tx if i < num_txs - 1 else calls_remaining + calls_remaining -= tx_calls + + # Build attack code for this transaction + attack_code: Bytecode = ( + Op.JUMPDEST # Entry point + + Op.MSTORE(offset=0, value=APPROVE_SELECTOR) + + Op.MSTORE(offset=32, value=tx_calls) + While( - # Continue while counter > 0 condition=Op.MLOAD(32) + Op.ISZERO + Op.ISZERO, body=( # Store spender at memory[64] (counter as spender/amount) Op.MSTORE(offset=64, value=Op.MLOAD(32)) # Call approve(spender, amount) on ERC20 contract - # args_offset=28 reads: selector from MEM[28:32] + - # spender from MEM[32:64] + amount from MEM[64:96] - # Note: counter at MEM[32:64] is reused as spender, - # and value at MEM[64:96] serves as the amount + Op.CALL( address=erc20_address, value=0, @@ -423,30 +364,33 @@ def test_sstore_erc20_approve( ret_offset=0, ret_size=0, ) - + Op.POP # Discard CALL success status - # Decrement counter + + Op.POP + Op.MSTORE(offset=32, value=Op.SUB(Op.MLOAD(32), 1)) ), ) ) - # Deploy attack contract - attack_address = pre.deploy_contract(code=attack_code) + # Deploy attack contract for this tx + attack_address = pre.deploy_contract(code=attack_code) - # Run the attack - attack_tx = Transaction( - to=attack_address, - gas_limit=gas_benchmark_value, - sender=pre.fund_eoa(), - ) + # Calculate gas for this transaction + this_tx_gas = min( + tx_gas_limit, gas_benchmark_value - (i * tx_gas_limit) + ) + + txs.append( + Transaction( + to=attack_address, + gas_limit=this_tx_gas, + sender=pre.fund_eoa(), + ) + ) - # Post-state - post = { - attack_address: Account(storage={}), - } + # Add to post-state + post[attack_address] = Account(storage={}) blockchain_test( pre=pre, - blocks=[Block(txs=[attack_tx])], + blocks=[Block(txs=txs)], post=post, )