From d6a8f97e74a8600989a6e157b2072ae6579b5bee Mon Sep 17 00:00:00 2001 From: Alexey Sharov Date: Thu, 13 Aug 2026 10:18:17 +0700 Subject: [PATCH] evmone v0.23.0 Picks up fixed-window (ipsilon/evmone#1618) and sliding-window (ipsilon/evmone#1631) exponentiation in the modexp precompile. intx stays at v0.15.0. --- evmone/.bumpversion.cfg | 2 +- evmone/CHANGELOG.md | 234 ++++ evmone/CMakeLists.txt | 13 +- evmone/circle.yml | 144 ++- evmone/cmake/Hunter/config.cmake | 6 +- evmone/cmake/blst.cmake | 4 +- .../cmake/toolchains/clang-libcxx-debug.cmake | 2 +- evmone/codecov.yml | 1 - evmone/evmc/include/evmc/evmc.h | 210 +-- evmone/evmc/include/evmc/evmc.hpp | 59 +- evmone/evmc/include/evmc/helpers.h | 67 +- evmone/evmc/include/evmc/mocked_host.hpp | 11 + evmone/evmc/include/evmc/tooling.hpp | 19 - evmone/evmc/lib/CMakeLists.txt | 1 - evmone/evmc/lib/tooling/CMakeLists.txt | 14 - evmone/lib/evmone/CMakeLists.txt | 2 + evmone/lib/evmone/advanced_execution.cpp | 8 +- evmone/lib/evmone/advanced_instructions.cpp | 4 + evmone/lib/evmone/baseline.hpp | 18 +- evmone/lib/evmone/baseline_execution.cpp | 11 +- evmone/lib/evmone/constants.hpp | 19 +- evmone/lib/evmone/create_address.cpp | 62 + evmone/lib/evmone/create_address.hpp | 38 + evmone/lib/evmone/execution_state.hpp | 28 +- evmone/lib/evmone/instructions.hpp | 124 +- evmone/lib/evmone/instructions_calls.cpp | 44 +- evmone/lib/evmone/instructions_opcodes.hpp | 5 + evmone/lib/evmone/instructions_storage.cpp | 5 +- evmone/lib/evmone/instructions_traits.hpp | 39 +- evmone/lib/evmone/instructions_xmacro.hpp | 8 +- evmone/lib/evmone/tracing.cpp | 4 +- evmone/lib/evmone/vm.cpp | 6 - evmone/lib/evmone_precompiles/CMakeLists.txt | 8 + evmone/lib/evmone_precompiles/blake2b.cpp | 3 +- evmone/lib/evmone_precompiles/bls.cpp | 40 +- evmone/lib/evmone_precompiles/bn254.cpp | 4 +- evmone/lib/evmone_precompiles/bn254.hpp | 31 +- evmone/lib/evmone_precompiles/ecc.hpp | 74 +- evmone/lib/evmone_precompiles/keccak.c | 27 +- evmone/lib/evmone_precompiles/kzg.cpp | 100 +- .../kzg_precomputed_lines.cpp | 1130 +++++++++++++++++ .../kzg_precomputed_lines.hpp | 16 + evmone/lib/evmone_precompiles/modexp.cpp | 133 +- evmone/lib/evmone_precompiles/mulmod.cpp | 41 +- .../pairing/bn254/fields.hpp | 70 +- .../pairing/bn254/pairing.cpp | 43 +- .../pairing/bn254/utils.hpp | 207 +-- .../pairing/field_template.hpp | 31 +- evmone/lib/evmone_precompiles/secp256k1.cpp | 14 +- evmone/lib/evmone_precompiles/secp256k1.hpp | 18 +- evmone/lib/evmone_precompiles/sha256.cpp | 4 +- evmone/test/CMakeLists.txt | 8 +- evmone/test/bench/helpers.hpp | 6 +- .../blockchaintest/blockchaintest_runner.cpp | 353 ++--- evmone/test/fuzzer/fuzzer.cpp | 16 +- evmone/test/integration/CMakeLists.txt | 10 + .../integration/blockchaintest/CMakeLists.txt | 16 + .../blockchaintest/eip7778_block_gas.json | 135 ++ .../unrecovered_sender_blob_gas.json | 86 ++ .../unrecovered_sender_deposit_log.json | 92 ++ .../test/integration/statetest/CMakeLists.txt | 20 +- .../statetest/tx/invalid_encoding.json | 43 + .../statetest/tx/invalid_signature.json | 43 + evmone/test/integration/t8n/CMakeLists.txt | 102 +- evmone/test/precompiles_bench/CMakeLists.txt | 2 +- .../precompiles_bench/precompiles_bench.cpp | 12 + evmone/test/state/CMakeLists.txt | 6 + evmone/test/state/account.hpp | 30 +- evmone/test/state/authorization.cpp | 40 + evmone/test/state/authorization.hpp | 36 + evmone/test/state/block.hpp | 6 + evmone/test/state/bloom_filter.cpp | 4 +- evmone/test/state/bloom_filter.hpp | 2 +- evmone/test/state/errors.hpp | 130 +- evmone/test/state/ethash_difficulty.cpp | 1 - evmone/test/state/host.cpp | 254 ++-- evmone/test/state/host.hpp | 34 +- evmone/test/state/precompiles.cpp | 28 +- evmone/test/state/rlp_common.hpp | 20 + evmone/test/state/rlp_decode.cpp | 117 ++ evmone/test/state/rlp_decode.hpp | 149 +++ evmone/test/state/state.cpp | 260 ++-- evmone/test/state/state.hpp | 43 +- evmone/test/state/system_contracts.cpp | 33 +- evmone/test/state/system_contracts.hpp | 15 +- evmone/test/state/transaction.cpp | 176 +++ evmone/test/state/transaction.hpp | 50 +- evmone/test/statetest/statetest_runner.cpp | 46 +- evmone/test/t8n/CMakeLists.txt | 7 - evmone/test/t8n/t8n.cpp | 326 ----- evmone/test/unittests/CMakeLists.txt | 20 +- .../test/unittests/baseline_analysis_test.cpp | 5 +- .../unittests/blockchaintest_loader_test.cpp | 85 ++ ...dress_test.cpp => create_address_test.cpp} | 16 +- evmone/test/unittests/evm_calls_test.cpp | 41 +- .../unittests/evm_eip3860_initcode_test.cpp | 5 +- .../unittests/evm_eip7843_slotnum_test.cpp | 42 + .../evm_eip8024_swapn_dupn_exchange_test.cpp | 234 ++++ evmone/test/unittests/evm_other_test.cpp | 5 +- evmone/test/unittests/evm_state_test.cpp | 4 +- evmone/test/unittests/evm_storage_test.cpp | 21 +- evmone/test/unittests/evm_test.cpp | 28 +- .../evm_undefined_instructions_test.cpp | 6 +- .../unittests/evmmax_bn254_pairing_test.cpp | 51 +- .../test/unittests/evmmax_secp256k1_test.cpp | 51 +- evmone/test/unittests/evmone_test.cpp | 7 - evmone/test/unittests/instructions_test.cpp | 2 + .../unittests/precompiles_expmod_test.cpp | 68 + .../test/unittests/precompiles_kzg_test.cpp | 53 + .../unittests/state_authorization_test.cpp | 40 + .../test/unittests/state_rlp_decode_test.cpp | 684 ++++++++++ .../test/unittests/state_system_call_test.cpp | 8 +- evmone/test/unittests/state_transition.cpp | 27 + evmone/test/unittests/state_transition.hpp | 19 + .../state_transition_create_test.cpp | 92 ++ .../state_transition_eip7702_test.cpp | 129 +- ...tate_transition_eip7778_block_gas_test.cpp | 26 + .../state_transition_selfdestruct_test.cpp | 117 ++ .../unittests/state_transition_touch_test.cpp | 82 ++ .../unittests/state_transition_tx_test.cpp | 218 +++- evmone/test/unittests/state_tx_test.cpp | 34 +- .../statetest_loader_block_info_test.cpp | 26 + .../unittests/statetest_loader_tx_test.cpp | 47 + evmone/test/unittests/tooling_run_test.cpp | 40 + evmone/test/unittests/tooling_t8n_test.cpp | 289 +++++ evmone/test/unittests/tracing_test.cpp | 5 +- evmone/test/utils/CMakeLists.txt | 14 +- evmone/test/utils/blob_schedule.cpp | 14 +- evmone/test/utils/blob_schedule.hpp | 5 +- evmone/test/utils/block_transition.cpp | 120 ++ evmone/test/utils/block_transition.hpp | 74 ++ evmone/test/utils/blockchaintest.hpp | 5 +- evmone/test/utils/blockchaintest_loader.cpp | 57 +- evmone/test/utils/error_matching.cpp | 169 +++ evmone/test/utils/error_matching.hpp | 36 + evmone/test/utils/rlp.hpp | 24 +- evmone/test/utils/rlp_encode.cpp | 2 +- .../{evmc/lib/tooling => test/utils}/run.cpp | 44 +- evmone/test/utils/run.hpp | 13 + evmone/test/utils/statetest.hpp | 37 +- evmone/test/utils/statetest_export.cpp | 68 +- evmone/test/utils/statetest_loader.cpp | 121 +- evmone/test/utils/t8n.cpp | 216 ++++ evmone/test/utils/t8n.hpp | 52 + evmone/test/utils/test_state.cpp | 11 +- evmone/test/utils/test_state.hpp | 4 +- evmone/test/utils/utils.cpp | 12 +- evmone/tools/evmone/CMakeLists.txt | 2 +- evmone/tools/evmone/main.cpp | 139 +- 149 files changed, 7586 insertions(+), 2243 deletions(-) delete mode 100644 evmone/evmc/include/evmc/tooling.hpp delete mode 100644 evmone/evmc/lib/tooling/CMakeLists.txt create mode 100644 evmone/lib/evmone/create_address.cpp create mode 100644 evmone/lib/evmone/create_address.hpp create mode 100644 evmone/lib/evmone_precompiles/kzg_precomputed_lines.cpp create mode 100644 evmone/lib/evmone_precompiles/kzg_precomputed_lines.hpp create mode 100644 evmone/test/integration/blockchaintest/eip7778_block_gas.json create mode 100644 evmone/test/integration/blockchaintest/unrecovered_sender_blob_gas.json create mode 100644 evmone/test/integration/blockchaintest/unrecovered_sender_deposit_log.json create mode 100644 evmone/test/integration/statetest/tx/invalid_encoding.json create mode 100644 evmone/test/integration/statetest/tx/invalid_signature.json create mode 100644 evmone/test/state/authorization.cpp create mode 100644 evmone/test/state/authorization.hpp create mode 100644 evmone/test/state/rlp_common.hpp create mode 100644 evmone/test/state/rlp_decode.cpp create mode 100644 evmone/test/state/rlp_decode.hpp create mode 100644 evmone/test/state/transaction.cpp delete mode 100644 evmone/test/t8n/CMakeLists.txt delete mode 100644 evmone/test/t8n/t8n.cpp rename evmone/test/unittests/{state_new_account_address_test.cpp => create_address_test.cpp} (93%) create mode 100644 evmone/test/unittests/evm_eip7843_slotnum_test.cpp create mode 100644 evmone/test/unittests/evm_eip8024_swapn_dupn_exchange_test.cpp create mode 100644 evmone/test/unittests/state_authorization_test.cpp create mode 100644 evmone/test/unittests/state_rlp_decode_test.cpp create mode 100644 evmone/test/unittests/state_transition_eip7778_block_gas_test.cpp create mode 100644 evmone/test/unittests/tooling_run_test.cpp create mode 100644 evmone/test/unittests/tooling_t8n_test.cpp create mode 100644 evmone/test/utils/block_transition.cpp create mode 100644 evmone/test/utils/block_transition.hpp create mode 100644 evmone/test/utils/error_matching.cpp create mode 100644 evmone/test/utils/error_matching.hpp rename evmone/{evmc/lib/tooling => test/utils}/run.cpp (78%) create mode 100644 evmone/test/utils/run.hpp create mode 100644 evmone/test/utils/t8n.cpp create mode 100644 evmone/test/utils/t8n.hpp diff --git a/evmone/.bumpversion.cfg b/evmone/.bumpversion.cfg index 40e5e27..5196dc3 100644 --- a/evmone/.bumpversion.cfg +++ b/evmone/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.21.0 +current_version = 0.23.0 tag = True sign_tags = True tag_message = evmone {new_version} diff --git a/evmone/CHANGELOG.md b/evmone/CHANGELOG.md index 4ffdca4..d34d16b 100644 --- a/evmone/CHANGELOG.md +++ b/evmone/CHANGELOG.md @@ -5,6 +5,224 @@ Documentation of all notable changes to the **evmone** project. The format is based on [Keep a Changelog], and this project adheres to [Semantic Versioning]. +## [0.23.0] — 2026-08-11 + +This release continues the implementation of the Amsterdam EVM revision +and reshapes the EVMC interface around it: the CREATE address is now computed +by the VM and unused parts of the API have been dropped. + +### Added + +- **Amsterdam EVM revision**: next set of EIPs. + - [EIP-7708]: ETH transfers emit a log. + [#1573](https://github.com/ipsilon/evmone/pull/1573) + - [EIP-7954]: Increase Maximum Contract Size — the code limit is raised + to 0x10000 ([EIP-170]) and the init code limit to 0x20000 ([EIP-3860]). + [#1575](https://github.com/ipsilon/evmone/pull/1575) + - [EIP-8246]: Remove SELFDESTRUCT Burn. + [#1572](https://github.com/ipsilon/evmone/pull/1572) +- **`get_nonce()` in the EVMC Host interface**, a live query needed by the VM + to compute the CREATE address. + [#1588](https://github.com/ipsilon/evmone/pull/1588) +- **Transaction decoding in the state library**: `state::decode_transaction()`, + the inverse of the RLP encoder for legacy and [EIP-2718] typed transactions. + [#1580](https://github.com/ipsilon/evmone/pull/1580) + [#1581](https://github.com/ipsilon/evmone/pull/1581) +- **Transaction sender recovery from the signature**, used by the state test + runner instead of the fixture's `sender` field. + [#1615](https://github.com/ipsilon/evmone/pull/1615) +- Non-malleable (strict) mode of the secp256k1 signature recovery, accepting + only `s` values from the lower half of the curve order ([EIP-2]). + [#1612](https://github.com/ipsilon/evmone/pull/1612) +- Test runners now check _why_ a transaction or block was rejected, not merely + that it was. Block-level reasons stay unchecked for legacy exception names, + blocks with ommers and unverified transaction signatures. + [#1621](https://github.com/ipsilon/evmone/pull/1621) + [#1623](https://github.com/ipsilon/evmone/pull/1623) + [#1624](https://github.com/ipsilon/evmone/pull/1624) + [#1632](https://github.com/ipsilon/evmone/pull/1632) + +### Changed + +- **EVMC ABI version bumped to 18** by the incompatible API changes below. + [#1587](https://github.com/ipsilon/evmone/pull/1587) + [#1588](https://github.com/ipsilon/evmone/pull/1588) + [#1589](https://github.com/ipsilon/evmone/pull/1589) + [#1593](https://github.com/ipsilon/evmone/pull/1593) + [#1596](https://github.com/ipsilon/evmone/pull/1596) +- **The CREATE address is computed by the VM**, not by the Host: the creating + frame derives it from the sender's nonce, warms it ([EIP-2929]) and passes it + down in `msg.recipient`. The [EIP-2681] nonce-overflow light failure moves + there too. + [#1589](https://github.com/ipsilon/evmone/pull/1589) + [#1590](https://github.com/ipsilon/evmone/pull/1590) + [#1591](https://github.com/ipsilon/evmone/pull/1591) + [#1592](https://github.com/ipsilon/evmone/pull/1592) +- The `evmc_access_status` enum has a `bool` underlying type. + [#1596](https://github.com/ipsilon/evmone/pull/1596) +- EVM revisions are identified by name rather than by number: `evmone run --rev` + takes a name and the `evmc_revision` enumerators lost their explicit values. + [#1586](https://github.com/ipsilon/evmone/pull/1586) +- **`modexp` windowed exponentiation**: the Montgomery path uses a sliding + window sized from the exponent's bit width, up to 1.5x faster. + [#1618](https://github.com/ipsilon/evmone/pull/1618) + [#1631](https://github.com/ipsilon/evmone/pull/1631) +- **State library performance**: warm storage accesses are no longer journaled, + the storage journal holds slot pointers instead of keys, the account-flag + entries are merged into one, `Account` is smaller and repeated account + lookups are gone. + [#1585](https://github.com/ipsilon/evmone/pull/1585) + [#1594](https://github.com/ipsilon/evmone/pull/1594) + [#1597](https://github.com/ipsilon/evmone/pull/1597) + [#1598](https://github.com/ipsilon/evmone/pull/1598) + [#1600](https://github.com/ipsilon/evmone/pull/1600) + [#1601](https://github.com/ipsilon/evmone/pull/1601) + [#1602](https://github.com/ipsilon/evmone/pull/1602) + [#1603](https://github.com/ipsilon/evmone/pull/1603) + [#1605](https://github.com/ipsilon/evmone/pull/1605) + [#1606](https://github.com/ipsilon/evmone/pull/1606) + [#1607](https://github.com/ipsilon/evmone/pull/1607) + [#1609](https://github.com/ipsilon/evmone/pull/1609) +- BN254 pairing arithmetic cleanups with small instruction-count wins: G2 + doubling reuses the generic `ecc::dbl` and G2 addition a repeated term. + [#1633](https://github.com/ipsilon/evmone/pull/1633) + [#1635](https://github.com/ipsilon/evmone/pull/1635) + [#1636](https://github.com/ipsilon/evmone/pull/1636) + [#1641](https://github.com/ipsilon/evmone/pull/1641) + [#1642](https://github.com/ipsilon/evmone/pull/1642) + [#1643](https://github.com/ipsilon/evmone/pull/1643) +- The cryptography code (`evmone_precompiles`) is built with `-O1` in Debug + configurations, keeping the precompiles usable there. + [#1619](https://github.com/ipsilon/evmone/pull/1619) +- Keccak absorption of the final input bytes reworked, preventing a compiler + from replacing the tail copy with a `memcpy` call. + [#1620](https://github.com/ipsilon/evmone/pull/1620) +- Fork names are spelled `TangerineWhistle` and `SpuriousDragon`, matching + [execution-specs]. + [#1576](https://github.com/ipsilon/evmone/pull/1576) +- The [Execution Spec Tests] fixtures are taken from [execution-specs]: + `tests@v20.0.1` and the `glamsterdam-devnet` pin for the Amsterdam work. + [#1577](https://github.com/ipsilon/evmone/pull/1577) + [#1579](https://github.com/ipsilon/evmone/pull/1579) +- The state test runner takes each transaction from its `txbytes` encoding, and + the codec is checked against every encoding a fixture carries, including the + transactions in a valid block's RLP. + [#1614](https://github.com/ipsilon/evmone/pull/1614) + [#1617](https://github.com/ipsilon/evmone/pull/1617) +- The `CHAINID` opcode returns the configured chain id instead of a hardcoded 1. + [#1610](https://github.com/ipsilon/evmone/pull/1610) +- Frame exit result construction is shared between the Baseline and Advanced + interpreters. + [#1578](https://github.com/ipsilon/evmone/pull/1578) +- The blockchain test runner reuses the state root of the canonical chain tip + instead of rebuilding the whole trie for the post-state check. + [#1595](https://github.com/ipsilon/evmone/pull/1595) + +### Removed + +- **The Constantinople revision**: it never activated on Mainnet (superseded by + [Petersburg]) and no live testnet runs it. Its opcodes retarget to Petersburg. + [#1587](https://github.com/ipsilon/evmone/pull/1587) +- **The EVMC capabilities feature**: eWASM is gone and precompiles-only VMs + did not take off. + [#1593](https://github.com/ipsilon/evmone/pull/1593) +- `evmc_result::create_address` and `evmc_message::create2_salt`, left without + readers by the VM-side CREATE address computation. + [#1589](https://github.com/ipsilon/evmone/pull/1589) + +### Fixed + +- **An [EIP-7702] authorization signature was never verified**: only its shape + was checked and the authority came from a non-standard `signer` field, so any + address could be given a delegation designation. + [#1611](https://github.com/ipsilon/evmone/pull/1611) +- Transaction chain ids above 255 and [EIP-155] `v` values above 0xff were + rejected by the transaction loader, so `evmone t8n` failed on common networks + such as Sepolia. + [#1570](https://github.com/ipsilon/evmone/pull/1570) + [#1571](https://github.com/ipsilon/evmone/pull/1571) +- A transaction with a mismatched chain id is rejected, and the [EIP-155] + protected form is recognized, so one signed for chain 0 is no longer accepted + on every chain. + [#1610](https://github.com/ipsilon/evmone/pull/1610) + [#1616](https://github.com/ipsilon/evmone/pull/1616) +- The presence of the block header's `slotNumber` ([EIP-7843]) is validated; + a post-fork block missing it and a pre-fork block carrying it were accepted. + [#1626](https://github.com/ipsilon/evmone/pull/1626) +- The JSON loaders conflated an absent optional key with a zero value, e.g. + an env without `blobGasUsed` produced an engaged optional holding 0. + [#1622](https://github.com/ipsilon/evmone/pull/1622) + +## [0.22.0] — 2026-06-13 + +This release starts the implementation of the Amsterdam EVM revision. + +### Added + +- **Amsterdam EVM revision** with the implementation of the initial set of EIPs. + [#1508](https://github.com/ipsilon/evmone/pull/1508) + - [EIP-7778]: Block Gas Accounting without Refunds. + [#1526](https://github.com/ipsilon/evmone/pull/1526) + - [EIP-7843]: SLOTNUM opcode. + [#1517](https://github.com/ipsilon/evmone/pull/1517) + - [EIP-7976]: Increase Calldata Floor Cost. + [#1519](https://github.com/ipsilon/evmone/pull/1519) + - [EIP-7981]: Increase Access List Cost. + [#1524](https://github.com/ipsilon/evmone/pull/1524) + - [EIP-8024]: Backward compatible SWAPN, DUPN, EXCHANGE. + [#1429](https://github.com/ipsilon/evmone/pull/1429) + +### Changed + +- **EVMC ABI version bumped to 13**: the `evmc_tx_context` layout changed + (the SLOTNUM addition and the removals listed below) along with other + incompatible EVMC API changes. + [#1567](https://github.com/ipsilon/evmone/pull/1567) +- **BN254 pairing (`ecpairing`) optimizations**: line evaluation optimized, + dedicated `Fq2` squaring, points migrated to affine/projective representations. + [#1539](https://github.com/ipsilon/evmone/pull/1539) + [#1541](https://github.com/ipsilon/evmone/pull/1541) + [#1542](https://github.com/ipsilon/evmone/pull/1542) + [#1545](https://github.com/ipsilon/evmone/pull/1545) + [#1546](https://github.com/ipsilon/evmone/pull/1546) + [#1547](https://github.com/ipsilon/evmone/pull/1547) +- **KZG point evaluation precompile optimizations**: joint G1 multi-scalar multiplication, + precomputed Miller-loop lines for the trusted setup point and the G2 generator. + [#1536](https://github.com/ipsilon/evmone/pull/1536) + [#1537](https://github.com/ipsilon/evmone/pull/1537) + [#1549](https://github.com/ipsilon/evmone/pull/1549) +- The t8n tool has been folded into the `evmone` CLI as the `t8n` subcommand. + [#1531](https://github.com/ipsilon/evmone/pull/1531) + [#1533](https://github.com/ipsilon/evmone/pull/1533) + [#1535](https://github.com/ipsilon/evmone/pull/1535) + [#1551](https://github.com/ipsilon/evmone/pull/1551) +- Blockchain test runner now matches expected block- and transaction-level exceptions. + [#1556](https://github.com/ipsilon/evmone/pull/1556) + [#1560](https://github.com/ipsilon/evmone/pull/1560) + [#1561](https://github.com/ipsilon/evmone/pull/1561) +- Upgraded [blst] to 0.3.16. + [#1534](https://github.com/ipsilon/evmone/pull/1534) +- `modexp` 256-bit Almost Montgomery Multiplication keeps its accumulator in + registers by avoiding output/input aliasing (~6% faster on 256-bit benchmarks). + [#1477](https://github.com/ipsilon/evmone/pull/1477) + +### Removed + +- Standalone `evmone-t8n` tool — use the `evmone t8n` subcommand instead. + [#1551](https://github.com/ipsilon/evmone/pull/1551) +- EOF leftovers dropped from the EVMC API: the `EOFCREATE` call kind + and the `initcodes` transaction context. + [#1514](https://github.com/ipsilon/evmone/pull/1514) + [#1515](https://github.com/ipsilon/evmone/pull/1515) +- The `evmc_result` optional storage. + [#1529](https://github.com/ipsilon/evmone/pull/1529) + +### Fixed + +- The instruction counting tracer (`--opcode.count`) kept a dangling reference + to the output path. + [#1554](https://github.com/ipsilon/evmone/pull/1554) + ## [0.21.0] — 2026-04-09 This release merges the required contents of the former [EVMC] project @@ -1247,6 +1465,9 @@ It delivers fully-compatible and high-speed EVM implementation. - Exposes [EVMC] 6 ABI. - The [intx 0.2.0](https://github.com/chfast/intx/releases/tag/v0.2.0) library is used for 256-bit precision arithmetic. +[0.23.0]: https://github.com/ipsilon/evmone/releases/tag/v0.23.0 +[0.22.0]: https://github.com/ipsilon/evmone/releases/tag/v0.22.0 +[0.21.0]: https://github.com/ipsilon/evmone/releases/tag/v0.21.0 [0.20.0]: https://github.com/ethereum/evmone/releases/tag/v0.20.0 [0.19.0]: https://github.com/ethereum/evmone/releases/tag/v0.19.0 [0.18.0]: https://github.com/ethereum/evmone/releases/tag/v0.18.0 @@ -1274,6 +1495,8 @@ It delivers fully-compatible and high-speed EVM implementation. [0.1.1]: https://github.com/ethereum/evmone/releases/tag/v0.1.1 [0.1.0]: https://github.com/ethereum/evmone/releases/tag/v0.1.0 +[EIP-2]: https://eips.ethereum.org/EIPS/eip-2 +[EIP-155]: https://eips.ethereum.org/EIPS/eip-155 [EIP-170]: https://eips.ethereum.org/EIPS/eip-170 [EIP-663]: https://eips.ethereum.org/EIPS/eip-663 [EIP-1153]: https://eips.ethereum.org/EIPS/eip-1153 @@ -1281,6 +1504,8 @@ It delivers fully-compatible and high-speed EVM implementation. [EIP-1344]: https://eips.ethereum.org/EIPS/eip-1344 [EIP-2200]: https://eips.ethereum.org/EIPS/eip-2200 [EIP-2537]: https://eips.ethereum.org/EIPS/eip-2537 +[EIP-2681]: https://eips.ethereum.org/EIPS/eip-2681 +[EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 [EIP-2929]: https://eips.ethereum.org/EIPS/eip-2929 [EIP-2935]: https://eips.ethereum.org/EIPS/eip-2935 [EIP-3155]: https://eips.ethereum.org/EIPS/eip-3155 @@ -1308,6 +1533,9 @@ It delivers fully-compatible and high-speed EVM implementation. [EIP-7691]: https://eips.ethereum.org/EIPS/eip-7691 [EIP-7692]: https://eips.ethereum.org/EIPS/eip-7692 [EIP-7702]: https://eips.ethereum.org/EIPS/eip-7702 +[EIP-7708]: https://eips.ethereum.org/EIPS/eip-7708 +[EIP-7778]: https://eips.ethereum.org/EIPS/eip-7778 +[EIP-7843]: https://eips.ethereum.org/EIPS/eip-7843 [EIP-7594]: https://eips.ethereum.org/EIPS/eip-7594 [EIP-7823]: https://eips.ethereum.org/EIPS/eip-7823 [EIP-7825]: https://eips.ethereum.org/EIPS/eip-7825 @@ -1318,6 +1546,11 @@ It delivers fully-compatible and high-speed EVM implementation. [EIP-7934]: https://eips.ethereum.org/EIPS/eip-7934 [EIP-7939]: https://eips.ethereum.org/EIPS/eip-7939 [EIP-7951]: https://eips.ethereum.org/EIPS/eip-7951 +[EIP-7954]: https://eips.ethereum.org/EIPS/eip-7954 +[EIP-7976]: https://eips.ethereum.org/EIPS/eip-7976 +[EIP-7981]: https://eips.ethereum.org/EIPS/eip-7981 +[EIP-8024]: https://eips.ethereum.org/EIPS/eip-8024 +[EIP-8246]: https://eips.ethereum.org/EIPS/eip-8246 [Spurious Dragon]: https://eips.ethereum.org/EIPS/eip-607 [Petersburg]: https://eips.ethereum.org/EIPS/eip-1716 @@ -1365,6 +1598,7 @@ It delivers fully-compatible and high-speed EVM implementation. [tests 8.0.4]: https://github.com/ethereum/tests/releases/tag/8.0.4 [Execution Spec Tests]: https://github.com/ethereum/execution-spec-tests +[execution-specs]: https://github.com/ethereum/execution-specs [Execution Spec Tests 5.3.0]: https://github.com/ethereum/execution-spec-tests/releases/tag/v5.3.0 [Execution Spec Tests 3.0.0]: https://github.com/ethereum/execution-spec-tests/releases/tag/v3.0.0 [Execution Spec Tests 1.0.6]: https://github.com/ethereum/execution-spec-tests/releases/tag/v1.0.6 diff --git a/evmone/CMakeLists.txt b/evmone/CMakeLists.txt index 5cb62c3..17b1470 100644 --- a/evmone/CMakeLists.txt +++ b/evmone/CMakeLists.txt @@ -23,7 +23,7 @@ cable_set_build_type(DEFAULT Release CONFIGURATION_TYPES Release Debug) include(Hunter/init) project(evmone LANGUAGES CXX C) -set(PROJECT_VERSION 0.21.0) +set(PROJECT_VERSION 0.23.0) option(EVMONE_TOOLS "Build evmone tools" ${PROJECT_IS_TOP_LEVEL}) @@ -53,7 +53,9 @@ if(CABLE_COMPILER_GNULIKE) $<$:-Wextra-semi> $<$:-Wno-missing-field-initializers> - $<$:-Wno-attributes> + $<$,$,12>>:-Wno-attributes=clang::> + $<$,$,12>>:-Wno-attributes=msvc::> + $<$,$,12>>:-Wno-attributes> $<$:-Wduplicated-cond> $<$:-Wlogical-op> @@ -153,10 +155,17 @@ set(include_dir ${CMAKE_CURRENT_SOURCE_DIR}/include) add_subdirectory(lib) if(EVMONE_TOOLS) + hunter_add_package(nlohmann_json) + find_package(nlohmann_json CONFIG REQUIRED) + add_subdirectory(test/state) + add_subdirectory(test/utils) add_subdirectory(tools) endif() if(EVMONE_TESTING) + if(NOT EVMONE_TOOLS) + message(FATAL_ERROR "EVMONE_TESTING requires EVMONE_TOOLS") + endif() enable_testing() add_subdirectory(test) endif() diff --git a/evmone/circle.yml b/evmone/circle.yml index b3913d5..dd98db5 100644 --- a/evmone/circle.yml +++ b/evmone/circle.yml @@ -1,6 +1,6 @@ version: 2.1 orbs: - codecov: codecov/codecov@5.4.3 + codecov: codecov/codecov@6.0.0 win: circleci/windows@5.0 executors: @@ -53,21 +53,23 @@ executors: linux-base: docker: - image: cimg/base:edge-22.04 - resource_class: small + resource_class: medium environment: CMAKE_BUILD_PARALLEL_LEVEL: 2 macos: - resource_class: macos.m1.medium.gen1 + resource_class: m4pro.medium macos: - xcode: 16.4.0 + xcode: 26.5 environment: - CMAKE_BUILD_PARALLEL_LEVEL: 6 + # m4pro.medium has 6 CPUs, but 12 works better. + CMAKE_BUILD_PARALLEL_LEVEL: 12 macos-xcode-min: - resource_class: macos.m1.medium.gen1 + resource_class: m4pro.medium macos: - xcode: 16.3.0 + xcode: 16.4.0 environment: - CMAKE_BUILD_PARALLEL_LEVEL: 6 + # m4pro.medium has 6 CPUs, but 12 works better. + CMAKE_BUILD_PARALLEL_LEVEL: 12 commands: install_cmake: @@ -133,41 +135,55 @@ commands: parameters: repo: type: string - default: ethereum/execution-spec-tests + default: ethereum/execution-specs release: type: string fixtures_suffix: type: string - default: stable + default: "" steps: - run: - name: "Download execution-spec-tests: <>" + name: "Download test fixtures: <> <>" working_directory: ~/spec-tests command: | find . -delete - curl -L --retry 3 -C - --output-dir /tmp -O https://github.com/<>/releases/download/<>/fixtures_<>.tar.gz - tar -xzf /tmp/fixtures_*.tar.gz + rm -f /tmp/fixtures<>.tar.gz + curl -L --fail --retry 3 -C - --output-dir /tmp -O https://github.com/<>/releases/download/<>/fixtures<>.tar.gz + tar -xzf /tmp/fixtures<>.tar.gz ls -l + run_execution_spec_tests: + parameters: + repo: + type: string + default: ethereum/execution-specs + release: + type: string + fixtures_suffix: + type: string + default: "" + filter: + type: string + default: "*" steps: - download_execution_spec_tests: - release: v5.4.0 - # develop includes stable - fixtures_suffix: develop + repo: <> + release: <> + fixtures_suffix: <> - run: - name: "Execution spec tests (develop, state_tests)" + name: "Execution spec tests (<>, state_tests)" # Tests for in-development EVM revision currently passing. working_directory: ~/build command: > LLVM_PROFILE_FILE=state_tests.profraw - bin/evmone-statetest ~/spec-tests/fixtures/state_tests + bin/evmone-statetest --gtest_filter='<>' ~/spec-tests/fixtures/state_tests - run: - name: "Execution spec tests (develop, blockchain_tests)" + name: "Execution spec tests (<>, blockchain_tests)" # Tests for in-development EVM revision currently passing. working_directory: ~/build command: > LLVM_PROFILE_FILE=blockchain_tests.profraw - bin/evmone-blockchaintest ~/spec-tests/fixtures/blockchain_tests + bin/evmone-blockchaintest --gtest_filter='<>' ~/spec-tests/fixtures/blockchain_tests build: description: "Build" @@ -244,15 +260,17 @@ commands: collect_coverage_clang: description: "Collect coverage data (clang)" parameters: + flags: + type: string ignore_filename_regex: type: string default: "" binaries: type: string - default: evmone evmone-unittests evmone-statetest evmone-blockchaintest evmone-t8n + default: evmone evmone-unittests evmone-statetest evmone-blockchaintest steps: - run: - name: "Collect coverage data (clang)" + name: "Collect coverage data (clang, <>)" working_directory: ~/build command: | IGNORE_FILENAME_REGEX='include/evmc<<#parameters.ignore_filename_regex>>|<><>' @@ -272,14 +290,7 @@ commands: llvm-cov report $ARGS -use-color -show-mcdc-summary - store_artifacts: path: ~/coverage - destination: coverage - - upload_coverage: - description: "Upload coverage data" - parameters: - flags: - type: string - steps: + destination: <> - codecov/upload: plugins: noop disable_search: true @@ -321,7 +332,7 @@ jobs: name: "Check code format" command: | clang-format --version - find include lib test tools -name '*.hpp' -o -name '*.cpp' -o -name '*.h' -o -name '*.c' | xargs clang-format -i + find evmc include lib test tools -name '*.hpp' -o -name '*.cpp' -o -name '*.h' -o -name '*.c' | xargs clang-format -i git diff --color --exit-code - run: name: "Check spelling" @@ -412,12 +423,20 @@ jobs: CMAKE_OPTIONS: -DCOVERAGE=1 steps: - build - - run_execution_spec_tests + - run_execution_spec_tests: + release: tests-glamsterdam-devnet@v6.1.1 + fixtures_suffix: _glamsterdam-devnet + filter: "-for_amsterdam/*:for_bpo2toamsterdamattime15k/*" - collect_coverage_clang: - ignore_filename_regex: lib/evmone/(advanced|cpu_check|eof|lru_cache|tracing|vm)|test/(experimental|t8n|unittests|utils) - binaries: evmone-statetest evmone-blockchaintest - - upload_coverage: flags: eest-develop + ignore_filename_regex: lib/evmone/(advanced|cpu_check|eof|lru_cache|tracing|vm)|test/(experimental|unittests) + binaries: evmone-statetest evmone-blockchaintest + - run_execution_spec_tests: + release: tests@v20.0.1 + - collect_coverage_clang: + flags: eest-stable + ignore_filename_regex: lib/evmone/(advanced|cpu_check|eof|lru_cache|tracing|vm)|test/(experimental|unittests) + binaries: evmone-statetest evmone-blockchaintest ethereum-tests: executor: linux-clang-latest @@ -430,25 +449,24 @@ jobs: - download_execution_tests: rev: v17.2 - run: - name: "State tests" - working_directory: ~/build - environment: - # Filter out long-running and incorrect tests. - GTEST_FILTER: "-stCreateTest.CreateOOGafterMaxCodesize:\ - stQuadraticComplexityTest.Call50000_sha256:\ - stTimeConsuming.*:\ - VMTests/vmPerformance.*:\ - stExample.*:\ - stTransactionTest.*" - command: > - bin/evmone-statetest - --gtest_filter=$GTEST_FILTER - ~/tests/LegacyTests/Cancun/GeneralStateTests - ~/tests/LegacyTests/Constantinople/GeneralStateTests + name: "Drop unsupported Constantinople test cases" + working_directory: ~/tests + command: | + # evmone no longer supports the Constantinople revision (it never + # activated on Mainnet and was superseded by Petersburg). Strip the + # Constantinople-only cases from the fixtures so the other forks in + # each file still run. ConstantinopleFix (== Petersburg) is kept. + # Only the directories actually run below are scanned. + files=$(grep -rlE '"network"[[:space:]]*:[[:space:]]*"Constantinople"' BlockchainTests/ValidBlocks BlockchainTests/InvalidBlocks LegacyTests/Cancun/BlockchainTests/ValidBlocks LegacyTests/Cancun/BlockchainTests/InvalidBlocks || true) + for f in $files; do + python3 -c "import json,sys; p=sys.argv[1]; d=json.load(open(p)); json.dump({k:v for k,v in d.items() if v.get('network')!='Constantinople'}, open(p,'w'), indent=1)" "$f" + echo "filtered $f" + done - run: name: "Blockchain tests (ValidBlocks)" working_directory: ~/build command: > + LLVM_PROFILE_FILE=blockchain_tests_valid.profraw bin/evmone-blockchaintest --gtest_filter='-bcValidBlockTest.SimpleTx3LowS' ~/tests/BlockchainTests/ValidBlocks @@ -457,12 +475,12 @@ jobs: name: "Blockchain tests (InvalidBlocks)" working_directory: ~/build command: > + LLVM_PROFILE_FILE=blockchain_tests_invalid.profraw bin/evmone-blockchaintest --gtest_filter='-bc4895-withdrawals.shanghaiWithoutWithdrawalsRLP:bcInvalidHeaderTest.*:bcUncleHeaderValidity.gasLimitTooLowExactBound' ~/tests/BlockchainTests/InvalidBlocks ~/tests/LegacyTests/Cancun/BlockchainTests/InvalidBlocks - - collect_coverage_clang - - upload_coverage: + - collect_coverage_clang: flags: eest-legacy precompiles-libsecp256k1: @@ -473,15 +491,13 @@ jobs: steps: - build - download_execution_spec_tests: - release: v5.4.0 - fixtures_suffix: stable + release: tests@v20.0.1 - run: name: "Execution spec tests (state_tests)" working_directory: ~/build command: > bin/evmone-statetest ~/spec-tests/fixtures/state_tests - - collect_coverage_clang - - upload_coverage: + - collect_coverage_clang: flags: eest-libsecp256k1 precompiles-gmp: @@ -495,21 +511,17 @@ jobs: command: sudo apt-get -q update && sudo apt-get -qy install libgmp-dev - build - download_execution_spec_tests: - release: v5.4.0 - fixtures_suffix: stable + release: tests@v20.0.1 - run: name: "Execution spec tests (state_tests)" working_directory: ~/build command: > bin/evmone-statetest ~/spec-tests/fixtures/state_tests - - collect_coverage_clang - - upload_coverage: + - collect_coverage_clang: flags: eest-develop-gmp gcc-min: executor: linux-gcc-min - environment: - CMAKE_OPTIONS: -DEVMONE_TOOLS=OFF steps: - build - test @@ -548,12 +560,13 @@ jobs: executor: linux-clang-selfhosted environment: TOOLCHAIN: clang-libcxx-debug - CMAKE_OPTIONS: -DBUILD_SHARED_LIBS=NO -DASSERTIONS=ON -DSANITIZE=address,undefined,shift-exponent,implicit-conversion,nullability + CMAKE_OPTIONS: -DBUILD_SHARED_LIBS=NO -DASSERTIONS=ON -DSANITIZE=address,undefined,implicit-conversion,local-bounds,nullability UBSAN_OPTIONS: halt_on_error=1 steps: - build - test - - run_execution_spec_tests + - run_execution_spec_tests: + release: tests@v20.0.1 clang-tidy: executor: linux-clang-selfhosted @@ -575,8 +588,7 @@ jobs: steps: - build - test - - collect_coverage_clang - - upload_coverage: + - collect_coverage_clang: flags: evmone-unittests fuzzing: diff --git a/evmone/cmake/Hunter/config.cmake b/evmone/cmake/Hunter/config.cmake index aec80a5..5a9452f 100644 --- a/evmone/cmake/Hunter/config.cmake +++ b/evmone/cmake/Hunter/config.cmake @@ -38,8 +38,8 @@ option(BENCHMARK_ENABLE_LIBPFM "Enable performance counters provided by libpfm" hunter_config( benchmark - VERSION 1.9.4 + VERSION 1.9.5 CMAKE_ARGS BENCHMARK_ENABLE_LIBPFM=${BENCHMARK_ENABLE_LIBPFM} - URL https://github.com/google/benchmark/archive/v1.9.4.tar.gz - SHA1 46984dfbfc5fbfa42a0b60bfd3a962ef0d7d1c93 + URL https://github.com/google/benchmark/archive/v1.9.5.tar.gz + SHA1 1923d665fc134fca137fea0951403d043b5b7732 ) diff --git a/evmone/cmake/blst.cmake b/evmone/cmake/blst.cmake index a3724ae..3401763 100644 --- a/evmone/cmake/blst.cmake +++ b/evmone/cmake/blst.cmake @@ -24,8 +24,8 @@ ExternalProject_Add( blst EXCLUDE_FROM_ALL TRUE PREFIX ${PROJECT_BINARY_DIR}/deps - URL https://github.com/supranational/blst/archive/refs/tags/v0.3.15.tar.gz - URL_HASH SHA256=9e503ff6b50e044efb075d260c81c751702b3ed6f2e45394b0833834e71c3afa + URL https://github.com/supranational/blst/archive/refs/tags/v0.3.16.tar.gz + URL_HASH SHA256=e04805b7d6ef9e1d89b7f511a5b86136c57b455d97924d7324da2305a864673f DOWNLOAD_NO_PROGRESS TRUE CONFIGURE_COMMAND "" BUILD_COMMAND ${BLST_BUILD_SCRIPT} diff --git a/evmone/cmake/toolchains/clang-libcxx-debug.cmake b/evmone/cmake/toolchains/clang-libcxx-debug.cmake index 6c54e0e..ba187bc 100644 --- a/evmone/cmake/toolchains/clang-libcxx-debug.cmake +++ b/evmone/cmake/toolchains/clang-libcxx-debug.cmake @@ -5,4 +5,4 @@ set(CMAKE_C_COMPILER clang) set(CMAKE_CXX_COMPILER clang++) -set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++ -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG") +set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++ -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG -D_LIBCPP_REMOVE_TRANSITIVE_INCLUDES=1") diff --git a/evmone/codecov.yml b/evmone/codecov.yml index 7ceb512..10161ea 100644 --- a/evmone/codecov.yml +++ b/evmone/codecov.yml @@ -23,7 +23,6 @@ component_management: - test/eofparse/** - test/eoftest/** - test/statetest/** - - test/t8n/** - test/utils/** - component_id: tests name: tests diff --git a/evmone/evmc/include/evmc/evmc.h b/evmone/evmc/include/evmc/evmc.h index 1786db8..2807838 100644 --- a/evmone/evmc/include/evmc/evmc.h +++ b/evmone/evmc/include/evmc/evmc.h @@ -39,12 +39,13 @@ enum /** * The EVMC ABI version number of the interface declared in this file. * - * The EVMC ABI version always equals the major version number of the EVMC project. + * The ABI version is incremented on every incompatible change of the EVMC API or ABI + * (up to EVMC 12 it equaled the major version number of the standalone EVMC project). * The Host SHOULD check if the ABI versions match when dynamically loading VMs. * * @see @ref versioning */ - EVMC_ABI_VERSION = 12 + EVMC_ABI_VERSION = 18 }; @@ -79,8 +80,7 @@ enum evmc_call_kind The value param ignored. */ EVMC_CALLCODE = 2, /**< Request CALLCODE. */ EVMC_CREATE = 3, /**< Request CREATE. */ - EVMC_CREATE2 = 4, /**< Request CREATE2. Valid since Constantinople.*/ - EVMC_EOFCREATE = 5 /**< Request EOFCREATE. Valid since Osaka.*/ + EVMC_CREATE2 = 4, /**< Request CREATE2. Valid since Petersburg. */ }; /** The flags for ::evmc_message. */ @@ -128,6 +128,7 @@ struct evmc_message * message value evmc_message::value is going to be transferred. * For ::EVMC_CALLCODE or ::EVMC_DELEGATECALL, this may be different from * the evmc_message::code_address. + * For ::EVMC_CREATE and ::EVMC_CREATE2 this is the address of the account to be created. * * Defined as `r` in the Yellow Paper. */ @@ -167,25 +168,13 @@ struct evmc_message */ evmc_uint256be value; - /** - * The optional value used in new contract address construction. - * - * Needed only for a Host to calculate created address when kind is ::EVMC_CREATE2 or - * ::EVMC_EOFCREATE. - * Ignored in evmc_execute_fn(). - */ - evmc_bytes32 create2_salt; - /** * The address of the code to be executed. * * For ::EVMC_CALLCODE or ::EVMC_DELEGATECALL this may be different from * the evmc_message::recipient. * Not required when invoking evmc_execute_fn(), only when invoking evmc_call_fn(). - * Ignored if kind is ::EVMC_CREATE, ::EVMC_CREATE2 or ::EVMC_EOFCREATE. - * - * In case of ::EVMC_CAPABILITY_PRECOMPILES implementation, this fields should be inspected - * to identify the requested precompile. + * Ignored if kind is ::EVMC_CREATE or ::EVMC_CREATE2. * * Defined as `c` in the Yellow Paper. */ @@ -202,31 +191,22 @@ struct evmc_message size_t code_size; }; -/** The hashed initcode used for TXCREATE instruction. */ -typedef struct evmc_tx_initcode -{ - evmc_bytes32 hash; /**< The initcode hash. */ - const uint8_t* code; /**< The code. */ - size_t code_size; /**< The length of the code. */ -} evmc_tx_initcode; - /** The transaction and block data for execution. */ struct evmc_tx_context { - evmc_uint256be tx_gas_price; /**< The transaction gas price. */ - evmc_address tx_origin; /**< The transaction origin account. */ - evmc_address block_coinbase; /**< The miner of the block. */ - int64_t block_number; /**< The block number. */ - int64_t block_timestamp; /**< The block timestamp. */ - int64_t block_gas_limit; /**< The block gas limit. */ - evmc_uint256be block_prev_randao; /**< The block previous RANDAO (EIP-4399). */ - evmc_uint256be chain_id; /**< The blockchain's ChainID. */ - evmc_uint256be block_base_fee; /**< The block base fee per gas (EIP-1559, EIP-3198). */ - evmc_uint256be blob_base_fee; /**< The blob base fee (EIP-7516). */ - const evmc_bytes32* blob_hashes; /**< The array of blob hashes (EIP-4844). */ - size_t blob_hashes_count; /**< The number of blob hashes (EIP-4844). */ - const evmc_tx_initcode* initcodes; /**< The array of transaction initcodes (TXCREATE). */ - size_t initcodes_count; /**< The number of transaction initcodes (TXCREATE). */ + evmc_uint256be tx_gas_price; /**< The transaction gas price. */ + evmc_address tx_origin; /**< The transaction origin account. */ + evmc_address block_coinbase; /**< The miner of the block. */ + int64_t block_number; /**< The block number. */ + int64_t block_timestamp; /**< The block timestamp. */ + int64_t block_gas_limit; /**< The block gas limit. */ + evmc_uint256be block_prev_randao; /**< The block previous RANDAO (EIP-4399). */ + evmc_uint256be chain_id; /**< The blockchain's ChainID. */ + evmc_uint256be block_base_fee; /**< The block base fee per gas (EIP-1559, EIP-3198). */ + evmc_uint256be blob_base_fee; /**< The blob base fee (EIP-7516). */ + const evmc_bytes32* blob_hashes; /**< The array of blob hashes (EIP-4844). */ + size_t blob_hashes_count; /**< The number of blob hashes (EIP-4844). */ + uint64_t block_slot_number; /**< The beacon chain slot number (EIP-7843). */ }; /** @@ -475,29 +455,6 @@ struct evmc_result * function to the result itself allows VM composition. */ evmc_release_result_fn release; - - /** - * The address of the possibly created contract. - * - * The create address may be provided even though the contract creation has failed - * (evmc_result::status_code is not ::EVMC_SUCCESS). This is useful in situations - * when the address is observable, e.g. access to it remains warm. - * In all other cases the address MUST be null bytes. - */ - evmc_address create_address; - - /** - * Reserved data that MAY be used by a evmc_result object creator. - * - * This reserved 4 bytes together with 20 bytes from create_address form - * 24 bytes of memory called "optional data" within evmc_result struct - * to be optionally used by the evmc_result object creator. - * - * @see evmc_result_optional_data, evmc_get_optional_data(). - * - * Also extends the size of the evmc_result to 64 bytes (full cache line). - */ - uint8_t padding[4]; }; @@ -692,6 +649,18 @@ typedef void (*evmc_set_transient_storage_fn)(struct evmc_host_context* context, typedef evmc_uint256be (*evmc_get_balance_fn)(struct evmc_host_context* context, const evmc_address* address); +/** + * Get nonce callback function. + * + * This callback function is used by a VM to query the nonce of the given account in the state. + * + * @param context The pointer to the Host execution context. + * @param address The address of the account. + * @return The nonce of the given account or 0 if the account does not exist. + */ +typedef uint64_t (*evmc_get_nonce_fn)(struct evmc_host_context* context, + const evmc_address* address); + /** * Get code size callback function. * @@ -781,17 +750,17 @@ typedef void (*evmc_emit_log_fn)(struct evmc_host_context* context, /** * Access status per EIP-2929: Gas cost increases for state access opcodes. */ -enum evmc_access_status +enum evmc_access_status : bool { /** * The entry hasn't been accessed before – it's the first access. */ - EVMC_ACCESS_COLD = 0, + EVMC_ACCESS_COLD, /** * The entry is already in accessed_addresses or accessed_storage_keys. */ - EVMC_ACCESS_WARM = 1 + EVMC_ACCESS_WARM }; /** @@ -856,6 +825,9 @@ struct evmc_host_interface /** Get balance callback function. */ evmc_get_balance_fn get_balance; + /** Get nonce callback function. */ + evmc_get_nonce_fn get_nonce; + /** Get code size callback function. */ evmc_get_code_size_fn get_code_size; @@ -945,42 +917,35 @@ enum evmc_revision * * The one Ethereum launched with. */ - EVMC_FRONTIER = 0, + EVMC_FRONTIER, /** * The Homestead revision. * * https://eips.ethereum.org/EIPS/eip-606 */ - EVMC_HOMESTEAD = 1, + EVMC_HOMESTEAD, /** * The Tangerine Whistle revision. * * https://eips.ethereum.org/EIPS/eip-608 */ - EVMC_TANGERINE_WHISTLE = 2, + EVMC_TANGERINE_WHISTLE, /** * The Spurious Dragon revision. * * https://eips.ethereum.org/EIPS/eip-607 */ - EVMC_SPURIOUS_DRAGON = 3, + EVMC_SPURIOUS_DRAGON, /** * The Byzantium revision. * * https://eips.ethereum.org/EIPS/eip-609 */ - EVMC_BYZANTIUM = 4, - - /** - * The Constantinople revision. - * - * https://eips.ethereum.org/EIPS/eip-1013 - */ - EVMC_CONSTANTINOPLE = 5, + EVMC_BYZANTIUM, /** * The Petersburg revision. @@ -989,69 +954,76 @@ enum evmc_revision * * https://eips.ethereum.org/EIPS/eip-1716 */ - EVMC_PETERSBURG = 6, + EVMC_PETERSBURG, /** * The Istanbul revision. * * https://eips.ethereum.org/EIPS/eip-1679 */ - EVMC_ISTANBUL = 7, + EVMC_ISTANBUL, /** * The Berlin revision. * * https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/berlin.md */ - EVMC_BERLIN = 8, + EVMC_BERLIN, /** * The London revision. * * https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/london.md */ - EVMC_LONDON = 9, + EVMC_LONDON, /** * The Paris revision (aka The Merge). * * https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/paris.md */ - EVMC_PARIS = 10, + EVMC_PARIS, /** * The Shanghai revision. * * https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md */ - EVMC_SHANGHAI = 11, + EVMC_SHANGHAI, /** * The Cancun revision. * * https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/cancun.md */ - EVMC_CANCUN = 12, + EVMC_CANCUN, /** * The Prague / Pectra revision. * * https://eips.ethereum.org/EIPS/eip-7600 */ - EVMC_PRAGUE = 13, + EVMC_PRAGUE, /** * The Osaka / Fusaka revision. * * https://eips.ethereum.org/EIPS/eip-7607 */ - EVMC_OSAKA = 14, + EVMC_OSAKA, + + /** + * The Amsterdam / Glamsterdam revision. + * + * https://eips.ethereum.org/EIPS/eip-7773 + */ + EVMC_AMSTERDAM, /** * The unspecified EVM revision used for EVM implementations to expose * experimental features. */ - EVMC_EXPERIMENTAL = 15, + EVMC_EXPERIMENTAL, /** The maximum EVM revision supported. */ EVMC_MAX_REVISION = EVMC_EXPERIMENTAL, @@ -1061,7 +1033,7 @@ enum evmc_revision * * This is handy for EVM tools to always use the latest revision available. */ - EVMC_LATEST_STABLE_REVISION = EVMC_CANCUN + EVMC_LATEST_STABLE_REVISION = EVMC_OSAKA }; @@ -1071,8 +1043,7 @@ enum evmc_revision * This function MAY be invoked multiple times for a single VM instance. * * @param vm The VM instance. This argument MUST NOT be NULL. - * @param host The Host interface. This argument MUST NOT be NULL unless - * the @p vm has the ::EVMC_CAPABILITY_PRECOMPILES capability. + * @param host The Host interface. This argument MUST NOT be NULL. * @param context The opaque pointer to the Host execution context. * This argument MAY be NULL. The VM MUST pass the same * pointer to the methods of the @p host interface. @@ -1091,53 +1062,6 @@ typedef struct evmc_result (*evmc_execute_fn)(struct evmc_vm* vm, uint8_t const* code, size_t code_size); -/** - * Possible capabilities of a VM. - */ -enum evmc_capabilities -{ - /** - * The VM is capable of executing EVM1 bytecode. - */ - EVMC_CAPABILITY_EVM1 = (1u << 0), - - /** - * The VM is capable of executing ewasm bytecode. - */ - EVMC_CAPABILITY_EWASM = (1u << 1), - - /** - * The VM is capable of executing the precompiled contracts - * defined for the range of code addresses. - * - * The EIP-1352 (https://eips.ethereum.org/EIPS/eip-1352) specifies - * the range 0x000...0000 - 0x000...ffff of addresses - * reserved for precompiled and system contracts. - * - * This capability is **experimental** and MAY be removed without notice. - */ - EVMC_CAPABILITY_PRECOMPILES = (1u << 2) -}; - -/** - * Alias for unsigned integer representing a set of bit flags of EVMC capabilities. - * - * @see evmc_capabilities - */ -typedef uint32_t evmc_capabilities_flagset; - -/** - * Return the supported capabilities of the VM instance. - * - * This function MAY be invoked multiple times for a single VM instance, - * and its value MAY be influenced by calls to evmc_vm::set_option. - * - * @param vm The VM instance. - * @return The supported capabilities of the VM. @see evmc_capabilities. - */ -typedef evmc_capabilities_flagset (*evmc_get_capabilities_fn)(struct evmc_vm* vm); - - /** * The VM instance. * @@ -1183,18 +1107,6 @@ struct evmc_vm */ evmc_execute_fn execute; - /** - * A method returning capabilities supported by the VM instance. - * - * The value returned MAY change when different options are set via the set_option() method. - * - * A Client SHOULD only rely on the value returned if it has queried it after - * it has called the set_option(). - * - * This is a mandatory method and MUST NOT be set to NULL. - */ - evmc_get_capabilities_fn get_capabilities; - /** * Optional pointer to function modifying VM's options. * diff --git a/evmone/evmc/include/evmc/evmc.hpp b/evmone/evmc/include/evmc/evmc.hpp index 2dfabf8..6eeb912 100644 --- a/evmone/evmc/include/evmc/evmc.hpp +++ b/evmone/evmc/include/evmc/evmc.hpp @@ -328,7 +328,6 @@ constexpr auto make_result = evmc_make_result; class Result : private evmc_result { public: - using evmc_result::create_address; using evmc_result::gas_left; using evmc_result::gas_refund; using evmc_result::output_data; @@ -364,21 +363,6 @@ class Result : private evmc_result : evmc_result{make_result(_status_code, _gas_left, _gas_refund, nullptr, 0)} {} - /// Creates the result of contract creation. - /// - /// @param _status_code The status code. - /// @param _gas_left The amount of gas left. - /// @param _gas_refund The amount of refunded gas. - /// @param _create_address The address of the possibly created account. - explicit Result(evmc_status_code _status_code, - int64_t _gas_left, - int64_t _gas_refund, - const evmc_address& _create_address) noexcept - : evmc_result{make_result(_status_code, _gas_left, _gas_refund, nullptr, 0)} - { - create_address = _create_address; - } - /// Converting constructor from raw evmc_result. /// /// This object takes ownership of the resources of @p res. @@ -454,6 +438,9 @@ class HostInterface /// @copydoc evmc_host_interface::get_balance virtual uint256be get_balance(const address& addr) const noexcept = 0; + /// @copydoc evmc_host_interface::get_nonce + virtual uint64_t get_nonce(const address& addr) const noexcept = 0; + /// @copydoc evmc_host_interface::get_code_size virtual size_t get_code_size(const address& addr) const noexcept = 0; @@ -543,6 +530,11 @@ class HostContext : public HostInterface return host->get_balance(context, &address); } + uint64_t get_nonce(const address& address) const noexcept final + { + return host->get_nonce(context, &address); + } + size_t get_code_size(const address& address) const noexcept final { return host->get_code_size(context, &address); @@ -695,18 +687,6 @@ class VM /// @copydoc evmc_vm::version char const* version() const noexcept { return m_instance->version; } - /// Checks if the VM has the given capability. - bool has_capability(evmc_capabilities capability) const noexcept - { - return (get_capabilities() & static_cast(capability)) != 0; - } - - /// @copydoc evmc_vm::get_capabilities - evmc_capabilities_flagset get_capabilities() const noexcept - { - return m_instance->get_capabilities(m_instance); - } - /// @copydoc evmc_set_option() evmc_set_option_result set_option(const char name[], const char value[]) noexcept { @@ -734,23 +714,6 @@ class VM return execute(Host::get_interface(), host.to_context(), rev, msg, code, code_size); } - /// Executes code without the Host context. - /// - /// The same as - /// execute(const evmc_host_interface&, evmc_host_context*, evmc_revision, - /// const evmc_message&, const uint8_t*, size_t), - /// but without providing the Host context and interface. - /// This method is for experimental precompiles support where execution is - /// guaranteed not to require any Host access. - Result execute(evmc_revision rev, - const evmc_message& msg, - const uint8_t* code, - size_t code_size) noexcept - { - return Result{ - m_instance->execute(m_instance, nullptr, nullptr, rev, &msg, code, code_size)}; - } - /// Returns the pointer to C EVMC struct representing the VM. /// /// Gives access to the C EVMC VM struct to allow advanced interaction with the VM not supported @@ -799,6 +762,11 @@ inline evmc_uint256be get_balance(evmc_host_context* h, const evmc_address* addr return Host::from_context(h)->get_balance(*addr); } +inline uint64_t get_nonce(evmc_host_context* h, const evmc_address* addr) noexcept +{ + return Host::from_context(h)->get_nonce(*addr); +} + inline size_t get_code_size(evmc_host_context* h, const evmc_address* addr) noexcept { return Host::from_context(h)->get_code_size(*addr); @@ -886,6 +854,7 @@ inline const evmc_host_interface& Host::get_interface() noexcept ::evmc::internal::get_storage, ::evmc::internal::set_storage, ::evmc::internal::get_balance, + ::evmc::internal::get_nonce, ::evmc::internal::get_code_size, ::evmc::internal::get_code_hash, ::evmc::internal::copy_code, diff --git a/evmone/evmc/include/evmc/helpers.h b/evmone/evmc/include/evmc/helpers.h index c237f69..ce9d2e0 100644 --- a/evmone/evmc/include/evmc/helpers.h +++ b/evmone/evmc/include/evmc/helpers.h @@ -50,16 +50,6 @@ static inline const char* evmc_vm_version(struct evmc_vm* vm) return vm->version; } -/** - * Checks if the VM has the given capability. - * - * @see evmc_get_capabilities_fn - */ -static inline bool evmc_vm_has_capability(struct evmc_vm* vm, enum evmc_capabilities capability) -{ - return (vm->get_capabilities(vm) & (evmc_capabilities_flagset)capability) != 0; -} - /** * Destroys the VM instance. * @@ -168,55 +158,6 @@ static inline void evmc_release_result(struct evmc_result* result) result->release(result); } - -/** - * Helpers for optional storage of evmc_result. - * - * In some contexts (i.e. evmc_result::create_address is unused) objects of - * type evmc_result contains a memory storage that MAY be used by the object - * owner. This group defines helper types and functions for accessing - * the optional storage. - * - * @defgroup result_optional_storage Result Optional Storage - * @{ - */ - -/** - * The union representing evmc_result "optional storage". - * - * The evmc_result struct contains 24 bytes of optional storage that can be - * reused by the object creator if the object does not contain - * evmc_result::create_address. - * - * A VM implementation MAY use this memory to keep additional data - * when returning result from evmc_execute_fn(). - * The host application MAY use this memory to keep additional data - * when returning result of performed calls from evmc_call_fn(). - * - * @see evmc_get_optional_storage(), evmc_get_const_optional_storage(). - */ -union evmc_result_optional_storage -{ - uint8_t bytes[24]; /**< 24 bytes of optional storage. */ - void* pointer; /**< Optional pointer. */ -}; - -/** Provides read-write access to evmc_result "optional storage". */ -static inline union evmc_result_optional_storage* evmc_get_optional_storage( - struct evmc_result* result) -{ - return (union evmc_result_optional_storage*)&result->create_address; -} - -/** Provides read-only access to evmc_result "optional storage". */ -static inline const union evmc_result_optional_storage* evmc_get_const_optional_storage( - const struct evmc_result* result) -{ - return (const union evmc_result_optional_storage*)&result->create_address; -} - -/** @} */ - /** Returns text representation of the ::evmc_status_code. */ static inline const char* evmc_status_code_to_string(enum evmc_status_code status_code) { @@ -278,13 +219,11 @@ static inline const char* evmc_revision_to_string(enum evmc_revision rev) case EVMC_HOMESTEAD: return "Homestead"; case EVMC_TANGERINE_WHISTLE: - return "Tangerine Whistle"; + return "TangerineWhistle"; case EVMC_SPURIOUS_DRAGON: - return "Spurious Dragon"; + return "SpuriousDragon"; case EVMC_BYZANTIUM: return "Byzantium"; - case EVMC_CONSTANTINOPLE: - return "Constantinople"; case EVMC_PETERSBURG: return "Petersburg"; case EVMC_ISTANBUL: @@ -303,6 +242,8 @@ static inline const char* evmc_revision_to_string(enum evmc_revision rev) return "Prague"; case EVMC_OSAKA: return "Osaka"; + case EVMC_AMSTERDAM: + return "Amsterdam"; case EVMC_EXPERIMENTAL: return "Experimental"; } diff --git a/evmone/evmc/include/evmc/mocked_host.hpp b/evmone/evmc/include/evmc/mocked_host.hpp index 8aad17c..7e785e0 100644 --- a/evmone/evmc/include/evmc/mocked_host.hpp +++ b/evmone/evmc/include/evmc/mocked_host.hpp @@ -324,6 +324,17 @@ class MockedHost : public Host return it->second.balance; } + /// Get the account's nonce (EVMC Host method). + uint64_t get_nonce(const address& addr) const noexcept override + { + record_account_access(addr); + const auto it = accounts.find(addr); + if (it == accounts.end()) + return 0; + + return static_cast(it->second.nonce); + } + /// Get the account's code size (EVMC host method). size_t get_code_size(const address& addr) const noexcept override { diff --git a/evmone/evmc/include/evmc/tooling.hpp b/evmone/evmc/include/evmc/tooling.hpp deleted file mode 100644 index 3177ec2..0000000 --- a/evmone/evmc/include/evmc/tooling.hpp +++ /dev/null @@ -1,19 +0,0 @@ -// EVMC: Ethereum Client-VM Connector API. -// Copyright 2020 The EVMC Authors. -// Licensed under the Apache License, Version 2.0. - -#include -#include -#include - -namespace evmc::tooling -{ -int run(VM& vm, - evmc_revision rev, - int64_t gas, - bytes_view code, - bytes_view input, - bool create, - bool bench, - std::ostream& out); -} // namespace evmc::tooling diff --git a/evmone/evmc/lib/CMakeLists.txt b/evmone/evmc/lib/CMakeLists.txt index d452124..bb4fbaf 100644 --- a/evmone/evmc/lib/CMakeLists.txt +++ b/evmone/evmc/lib/CMakeLists.txt @@ -14,4 +14,3 @@ target_include_directories(evmc_cpp INTERFACE $fn(instr, state); - const auto gas_left = - (state.status == EVMC_SUCCESS || state.status == EVMC_REVERT) ? state.gas_left : 0; - const auto gas_refund = (state.status == EVMC_SUCCESS) ? state.gas_refund : 0; - - assert(state.output_size != 0 || state.output_offset == 0); - return evmc::make_result(state.status, gas_left, gas_refund, - state.output_size != 0 ? &state.memory[state.output_offset] : nullptr, state.output_size); + return make_execution_result(state, state.gas_left); } evmc_result execute(evmc_vm* /*unused*/, const evmc_host_interface* host, evmc_host_context* ctx, diff --git a/evmone/lib/evmone/advanced_instructions.cpp b/evmone/lib/evmone/advanced_instructions.cpp index 5aa9cfa..e57858a 100644 --- a/evmone/lib/evmone/advanced_instructions.cpp +++ b/evmone/lib/evmone/advanced_instructions.cpp @@ -270,6 +270,10 @@ constexpr std::array instruction_implementations = []( table[OP_CREATE2] = op_create; table[OP_STATICCALL] = op_call; + table[OP_DUPN] = op_undefined; + table[OP_SWAPN] = op_undefined; + table[OP_EXCHANGE] = op_undefined; + return table; }(); } // namespace diff --git a/evmone/lib/evmone/baseline.hpp b/evmone/lib/evmone/baseline.hpp index 7f78e3e..8ed1e5d 100644 --- a/evmone/lib/evmone/baseline.hpp +++ b/evmone/lib/evmone/baseline.hpp @@ -56,11 +56,10 @@ namespace baseline class CodeAnalysis { private: - bytes_view m_raw_code; ///< Unmodified full code. - bytes_view m_executable_code; ///< Executable code section. + bytes_view m_code; ///< The executable code. /// Padded code for faster legacy code execution. - /// If not nullptr the executable_code must point to it. + /// If not nullptr m_code must point to it. std::unique_ptr m_padded_code; BitsetSpan m_jumpdest_bitset{nullptr}; @@ -68,23 +67,18 @@ class CodeAnalysis public: /// Constructor for legacy code. CodeAnalysis(std::unique_ptr padded_code, size_t code_size, BitsetSpan map) - : m_raw_code{padded_code.get(), code_size}, - m_executable_code{padded_code.get(), code_size}, + : m_code{padded_code.get(), code_size}, m_padded_code{std::move(padded_code)}, m_jumpdest_bitset{map} {} - /// The raw code as stored in accounts or passes as initcode. - /// TODO: Merge back raw_code() and executable_code() after EOF removal. - [[nodiscard]] bytes_view raw_code() const noexcept { return m_raw_code; } - - /// The pre-processed executable code. This is where interpreter should start execution. - [[nodiscard]] bytes_view executable_code() const noexcept { return m_executable_code; } + /// The executable code. This is where the interpreter should start execution. + [[nodiscard]] bytes_view code() const noexcept { return m_code; } /// Check if given position is valid jump destination. Use only for legacy code. [[nodiscard]] bool check_jumpdest(uint64_t position) const noexcept { - if (position >= m_raw_code.size()) + if (position >= m_code.size()) return false; return m_jumpdest_bitset.test(static_cast(position)); } diff --git a/evmone/lib/evmone/baseline_execution.cpp b/evmone/lib/evmone/baseline_execution.cpp index 907449f..39b4473 100644 --- a/evmone/lib/evmone/baseline_execution.cpp +++ b/evmone/lib/evmone/baseline_execution.cpp @@ -265,12 +265,12 @@ int64_t dispatch_cgoto( evmc_result execute(VM& vm, const evmc_host_interface& host, evmc_host_context* ctx, evmc_revision rev, const evmc_message& msg, const CodeAnalysis& analysis) noexcept { - const auto code = analysis.executable_code(); + const auto code = analysis.code(); const auto code_begin = code.data(); auto gas = msg.gas; auto& state = vm.get_execution_state(static_cast(msg.depth)); - state.reset(msg, rev, host, ctx, analysis.raw_code()); + state.reset(msg, rev, host, ctx, code); state.analysis.baseline = &analysis; // Assign code analysis for instruction implementations. @@ -292,12 +292,7 @@ evmc_result execute(VM& vm, const evmc_host_interface& host, evmc_host_context* gas = dispatch(cost_table, state, gas, code_begin); } - const auto gas_left = (state.status == EVMC_SUCCESS || state.status == EVMC_REVERT) ? gas : 0; - const auto gas_refund = (state.status == EVMC_SUCCESS) ? state.gas_refund : 0; - - assert(state.output_size != 0 || state.output_offset == 0); - const auto result = evmc::make_result(state.status, gas_left, gas_refund, - state.output_size != 0 ? &state.memory[state.output_offset] : nullptr, state.output_size); + const auto result = make_execution_result(state, gas); if (INTX_UNLIKELY(tracer != nullptr)) tracer->notify_execution_end(result); diff --git a/evmone/lib/evmone/constants.hpp b/evmone/lib/evmone/constants.hpp index f3200c0..6bbb9f9 100644 --- a/evmone/lib/evmone/constants.hpp +++ b/evmone/lib/evmone/constants.hpp @@ -6,10 +6,25 @@ namespace evmone { /// The limit of the size of created contract -/// defined by [EIP-170](https://eips.ethereum.org/EIPS/eip-170) +/// defined by [EIP-170](https://eips.ethereum.org/EIPS/eip-170). constexpr auto MAX_CODE_SIZE = 0x6000; /// The limit of the size of init codes for contract creation -/// defined by [EIP-3860](https://eips.ethereum.org/EIPS/eip-3860) +/// defined by [EIP-3860](https://eips.ethereum.org/EIPS/eip-3860). constexpr auto MAX_INITCODE_SIZE = 2 * MAX_CODE_SIZE; + +/// The increased limit of the size of created contract in Amsterdam +/// defined by [EIP-7954](https://eips.ethereum.org/EIPS/eip-7954). +constexpr auto MAX_CODE_SIZE_AMSTERDAM = 0x10000; + +/// The increased limit of the size of init codes in Amsterdam (EIP-7954). +constexpr auto MAX_INITCODE_SIZE_AMSTERDAM = 2 * MAX_CODE_SIZE_AMSTERDAM; + +/// The maximum allowed account's nonce value: 2⁶⁴-1. +/// Transactions and create instructions with nonce equal or above this value are invalid. +/// Defined by [EIP-2681](https://eips.ethereum.org/EIPS/eip-2681). +constexpr auto MAX_NONCE = 0xffff'ffff'ffff'ffff; + +/// The gas given back to a value-transferring CALL, the Yellow Paper's G_callstipend. +constexpr auto CALL_STIPEND = 2300; } // namespace evmone diff --git a/evmone/lib/evmone/create_address.cpp b/evmone/lib/evmone/create_address.cpp new file mode 100644 index 0000000..411ddf0 --- /dev/null +++ b/evmone/lib/evmone/create_address.cpp @@ -0,0 +1,62 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2023 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "create_address.hpp" +#include +#include +#include +#include + +namespace evmone +{ +address compute_create_address(const address& sender, uint64_t sender_nonce) noexcept +{ + static constexpr auto RLP_STR_BASE = 0x80; + static constexpr auto RLP_LIST_BASE = 0xc0; + static constexpr auto ADDRESS_SIZE = sizeof(sender); + static constexpr std::ptrdiff_t MAX_NONCE_SIZE = sizeof(sender_nonce); + + uint8_t buffer[ADDRESS_SIZE + MAX_NONCE_SIZE + 3]; // 3 for RLP prefix bytes. + auto p = &buffer[1]; // Skip RLP list prefix for now. + *p++ = RLP_STR_BASE + ADDRESS_SIZE; // Set RLP string prefix for address. + p = std::copy_n(sender.bytes, ADDRESS_SIZE, p); + + if (sender_nonce < RLP_STR_BASE) // Short integer encoding including 0 as empty string (0x80). + { + *p++ = sender_nonce != 0 ? static_cast(sender_nonce) : RLP_STR_BASE; + } + else // Prefixed integer encoding. + { + const auto num_nonzero_bytes = (std::bit_width(sender_nonce) + 7) / 8; + *p++ = static_cast(RLP_STR_BASE + num_nonzero_bytes); + intx::be::unsafe::store(p, sender_nonce); + p = std::shift_left(p, p + MAX_NONCE_SIZE, MAX_NONCE_SIZE - num_nonzero_bytes); + } + + const auto total_size = static_cast(p - buffer); + buffer[0] = static_cast(RLP_LIST_BASE + (total_size - 1)); // Set the RLP list prefix. + + const auto base_hash = ethash::keccak256(buffer, total_size); + address addr; + std::copy_n(&base_hash.bytes[sizeof(base_hash) - ADDRESS_SIZE], ADDRESS_SIZE, addr.bytes); + return addr; +} + +address compute_create2_address( + const address& sender, const bytes32& salt, bytes_view init_code) noexcept +{ + const auto init_code_hash = ethash::keccak256(init_code.data(), init_code.size()); + uint8_t buffer[1 + sizeof(sender) + sizeof(salt) + sizeof(init_code_hash)]; + static_assert(std::size(buffer) == 85); + auto it = std::begin(buffer); + *it++ = 0xff; + it = std::copy_n(sender.bytes, sizeof(sender), it); + it = std::copy_n(salt.bytes, sizeof(salt), it); + std::copy_n(init_code_hash.bytes, sizeof(init_code_hash), it); + const auto base_hash = ethash::keccak256(buffer, std::size(buffer)); + address addr; + std::copy_n(&base_hash.bytes[sizeof(base_hash) - sizeof(addr)], sizeof(addr), addr.bytes); + return addr; +} +} // namespace evmone diff --git a/evmone/lib/evmone/create_address.hpp b/evmone/lib/evmone/create_address.hpp new file mode 100644 index 0000000..9410cdd --- /dev/null +++ b/evmone/lib/evmone/create_address.hpp @@ -0,0 +1,38 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2023 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +namespace evmone +{ +using evmc::address; +using evmc::bytes32; +using evmc::bytes_view; + +/// Computes the address of the to-be-created contract with the CREATE scheme. +/// +/// Computes the new account address for the contract creation context of the CREATE instruction +/// or a create transaction, as keccak256(rlp([sender, sender_nonce]))[12:]. +/// This is defined by 𝐀𝐃𝐃𝐑 in Yellow Paper, 7. Contract Creation, (88-90), the case for ζ = ∅. +/// +/// @param sender The address of the message sender. YP: 𝑠. +/// @param sender_nonce The sender's nonce before the increase. YP: 𝑛. +/// @return The address computed with the CREATE scheme. +[[nodiscard]] EVMC_EXPORT address compute_create_address( + const address& sender, uint64_t sender_nonce) noexcept; + +/// Computes the address of the to-be-created contract with the CREATE2 scheme. +/// +/// Computes the new account address for the contract creation context of the CREATE2 instruction, +/// as keccak256(0xff ++ sender ++ salt ++ keccak256(init_code))[12:] per EIP-1014. +/// +/// @param sender The address of the message sender. +/// @param salt The salt. +/// @param init_code The init_code to hash (initcode or initcontainer). +/// @return The address computed with the CREATE2 scheme. +[[nodiscard]] EVMC_EXPORT address compute_create2_address( + const address& sender, const bytes32& salt, bytes_view init_code) noexcept; +} // namespace evmone diff --git a/evmone/lib/evmone/execution_state.hpp b/evmone/lib/evmone/execution_state.hpp index 2552ad9..9511612 100644 --- a/evmone/lib/evmone/execution_state.hpp +++ b/evmone/lib/evmone/execution_state.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -122,16 +123,6 @@ class Memory void clear() noexcept { m_size = 0; } }; -/// Initcode read from Initcode Transaction (EIP-7873). -struct TransactionInitcode -{ - /// Initcode bytes. - bytes_view code; - /// Result of initcode validation, if it was validated. - /// std::nullopt if initcode was not validated yet. - std::optional is_valid; -}; - /// Generic execution state for generic instructions implementations. // NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding) class ExecutionState @@ -203,4 +194,21 @@ class ExecutionState return m_tx; } }; + +/// Builds the execution result for a finished frame from its final @p state and @p gas_left. +/// +/// Applies the frame-exit rules shared by the baseline and advanced interpreters: an exceptional +/// halt consumes all gas (only a success or revert keeps it), the gas refund counts only on +/// success, and the output is the memory range recorded in the state. +inline evmc_result make_execution_result(ExecutionState& state, int64_t gas_left) noexcept +{ + // An exceptional halt consumes all gas; only a success or revert keeps gas_left. + if (state.status != EVMC_SUCCESS && state.status != EVMC_REVERT) + gas_left = 0; + const auto gas_refund = (state.status == EVMC_SUCCESS) ? state.gas_refund : 0; + + assert(state.output_size != 0 || state.output_offset == 0); + return evmc::make_result(state.status, gas_left, gas_refund, + state.output_size != 0 ? &state.memory[state.output_offset] : nullptr, state.output_size); +} } // namespace evmone diff --git a/evmone/lib/evmone/instructions.hpp b/evmone/lib/evmone/instructions.hpp index ceff693..ae2afe8 100644 --- a/evmone/lib/evmone/instructions.hpp +++ b/evmone/lib/evmone/instructions.hpp @@ -55,6 +55,46 @@ struct Result struct TermResult : Result {}; + +/// Swap two values. +constexpr void fast_swap(uint256& x, uint256& y) noexcept +{ + // The simple std::swap(stack.top(), stack[N]) is not used to work around + // clang missed optimization: https://github.com/llvm/llvm-project/issues/59116 + // TODO(clang): Check if #59116 bug fix has been released. + + auto t0 = x[0]; + auto t1 = x[1]; + auto t2 = x[2]; + auto t3 = x[3]; + x = y; + y[0] = t0; + y[1] = t1; + y[2] = t2; + y[3] = t3; +} + +/// Decode DUPN/SWAPN immediate. Returns the stack depth n [17–235], +/// or std::nullopt if the immediate is in the forbidden range [0x5b–0x7f]. +constexpr std::optional decode_dupn_swapn_imm(uint8_t imm) noexcept +{ + if (imm >= 0x5b && imm <= 0x7f) + return std::nullopt; + return static_cast(imm + 0x91); +} + +/// Decode EXCHANGE immediate. Returns the pair (n, m) with 1 <= n < m and n + m <= 30, +/// or std::nullopt if the immediate is in the forbidden range [0x52–0x7f]. +constexpr std::optional> decode_exchange_imm(uint8_t imm) noexcept +{ + if (imm >= 0x52 && imm <= 0x7f) + return std::nullopt; + const auto k = imm ^ 0x8f; + const auto q = k / 16; + const auto r = k % 16; + return (q < r) ? std::pair{q + 1, r + 1} : std::pair{r + 1, 29 - q}; +} + constexpr auto max_buffer_size = std::numeric_limits::max(); /// The size of the EVM 256-bit word. @@ -85,7 +125,7 @@ constexpr int64_t copy_cost(uint64_t size_in_bytes) noexcept int64_t gas_left, Memory& memory, uint64_t new_size) noexcept { // This implementation recomputes memory.size(). This value is already known to the caller - // and can be passed as a parameter, but this make no difference to the performance. + // and can be passed as a parameter, but this makes no difference to the performance. const auto new_words = num_words(new_size); const auto current_words = static_cast(memory.size() / word_size); @@ -528,6 +568,11 @@ inline void blobbasefee(StackTop stack, ExecutionState& state) noexcept stack.push(intx::be::load(state.get_tx_context().blob_base_fee)); } +inline void slotnum(StackTop stack, ExecutionState& state) noexcept +{ + stack.push(state.get_tx_context().block_slot_number); +} + inline Result extcodesize(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept { auto& x = stack.top(); @@ -724,7 +769,7 @@ inline code_iterator jump_impl(ExecutionState& state, const uint256& dst) noexce return nullptr; } - return &state.analysis.baseline->executable_code()[static_cast(dst[0])]; + return &state.analysis.baseline->code()[static_cast(dst[0])]; } /// JUMP instruction implementation using baseline::CodeAnalysis. @@ -743,7 +788,7 @@ inline code_iterator jumpi(StackTop stack, ExecutionState& state, code_iterator inline code_iterator pc(StackTop stack, ExecutionState& state, code_iterator pos) noexcept { - stack.push(static_cast(pos - state.analysis.baseline->executable_code().data())); + stack.push(static_cast(pos - state.analysis.baseline->code().data())); return pos + 1; } @@ -867,22 +912,67 @@ template inline void swap(StackTop stack) noexcept { static_assert(N >= 1 && N <= 16); + fast_swap(stack.top(), stack[N]); +} - // The simple std::swap(stack.top(), stack[N]) is not used to workaround - // clang missed optimization: https://github.com/llvm/llvm-project/issues/59116 - // TODO(clang): Check if #59116 bug fix has been released. +inline code_iterator dupn(StackTop stack, ExecutionState& state, code_iterator pos) noexcept +{ + const auto n = decode_dupn_swapn_imm(pos[1]); + if (!n) + { + state.status = EVMC_UNDEFINED_INSTRUCTION; + return nullptr; + } + + // Stack overflow is checked by check_requirements() (stack_height_change=+1). + const auto stack_size = stack.end() - state.stack_space.bottom(); + if (*n > stack_size) + { + state.status = EVMC_STACK_UNDERFLOW; + return nullptr; + } + + stack.push(stack[*n - 1]); + return pos + 2; +} + +inline code_iterator swapn(StackTop stack, ExecutionState& state, code_iterator pos) noexcept +{ + const auto n = decode_dupn_swapn_imm(pos[1]); + if (!n) + { + state.status = EVMC_UNDEFINED_INSTRUCTION; + return nullptr; + } + + if (const auto stack_size = stack.end() - state.stack_space.bottom(); *n >= stack_size) + { + state.status = EVMC_STACK_UNDERFLOW; + return nullptr; + } + + fast_swap(stack.top(), stack[*n]); + return pos + 2; +} + +inline code_iterator exchange(StackTop stack, ExecutionState& state, code_iterator pos) noexcept +{ + const auto decoded = decode_exchange_imm(pos[1]); + if (!decoded) + { + state.status = EVMC_UNDEFINED_INSTRUCTION; + return nullptr; + } + + const auto [n, m] = *decoded; + if (const auto stack_size = stack.end() - state.stack_space.bottom(); m >= stack_size) + { + state.status = EVMC_STACK_UNDERFLOW; + return nullptr; + } - auto& a = stack[N]; - auto& t = stack.top(); - auto t0 = t[0]; - auto t1 = t[1]; - auto t2 = t[2]; - auto t3 = t[3]; - t = a; - a[0] = t0; - a[1] = t1; - a[2] = t2; - a[3] = t3; + fast_swap(stack[n], stack[m]); + return pos + 2; } inline Result mcopy(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept diff --git a/evmone/lib/evmone/instructions_calls.cpp b/evmone/lib/evmone/instructions_calls.cpp index 6a243ba..13d6fbd 100644 --- a/evmone/lib/evmone/instructions_calls.cpp +++ b/evmone/lib/evmone/instructions_calls.cpp @@ -2,6 +2,8 @@ // Copyright 2019 The evmone Authors. // SPDX-License-Identifier: Apache-2.0 +#include "constants.hpp" +#include "create_address.hpp" #include "delegation.hpp" #include "instructions.hpp" #include @@ -161,8 +163,8 @@ Result call_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexce { if (has_value) { - msg.gas += 2300; // Add stipend. - gas_left += 2300; + msg.gas += CALL_STIPEND; + gas_left += CALL_STIPEND; if (intx::be::load(state.host.get_balance(state.msg->recipient)) < value) return {EVMC_SUCCESS, gas_left}; // "Light" failure. } @@ -204,7 +206,7 @@ Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noex const auto endowment = stack.pop(); const auto init_code_offset_u256 = stack.pop(); const auto init_code_size_u256 = stack.pop(); - const auto salt = (Op == OP_CREATE2) ? stack.pop() : uint256{}; + const auto salt = (Op == OP_CREATE2) ? intx::be::store(stack.pop()) : bytes32{}; stack.push(0); // Assume failure. state.return_data.clear(); @@ -215,7 +217,9 @@ Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noex const auto init_code_offset = static_cast(init_code_offset_u256); const auto init_code_size = static_cast(init_code_size_u256); - if (state.rev >= EVMC_SHANGHAI && init_code_size > 0xC000) + const size_t max_init_code_size = + state.rev >= EVMC_AMSTERDAM ? MAX_INITCODE_SIZE_AMSTERDAM : MAX_INITCODE_SIZE; + if (state.rev >= EVMC_SHANGHAI && init_code_size > max_init_code_size) return {EVMC_OUT_OF_GAS, gas_left}; const auto init_code_word_cost = 6 * (Op == OP_CREATE2) + 2 * (state.rev >= EVMC_SHANGHAI); @@ -230,20 +234,32 @@ Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noex intx::be::load(state.host.get_balance(state.msg->recipient)) < endowment) return {EVMC_SUCCESS, gas_left}; // "Light" failure. + const auto& sender = state.msg->recipient; + const auto sender_nonce = state.host.get_nonce(sender); // Pre-bump sender nonce. + + // Creation fails when the sender's nonce is at maximum (EIP-2681). + if (sender_nonce == MAX_NONCE) + return {EVMC_SUCCESS, gas_left}; // "Light" failure. + + const auto init_code = + bytes_view{init_code_size > 0 ? &state.memory[init_code_offset] : nullptr, init_code_size}; + evmc_message msg{.kind = to_call_kind(Op)}; + msg.recipient = (Op == OP_CREATE) ? compute_create_address(sender, sender_nonce) : + compute_create2_address(sender, salt, init_code); + + // Access to the new address is warmed and never reverted (EIP-2929). + if (state.rev >= EVMC_BERLIN) + state.host.access_account(msg.recipient); + msg.gas = gas_left; if (state.rev >= EVMC_TANGERINE_WHISTLE) - msg.gas = msg.gas - msg.gas / 64; + msg.gas -= msg.gas / 64; - if (init_code_size > 0) - { - // init_code_offset may be garbage if init_code_size == 0. - msg.input_data = &state.memory[init_code_offset]; - msg.input_size = init_code_size; - } - msg.sender = state.msg->recipient; + msg.input_data = init_code.data(); + msg.input_size = init_code.size(); + msg.sender = sender; msg.depth = state.msg->depth + 1; - msg.create2_salt = intx::be::store(salt); msg.value = intx::be::store(endowment); const auto result = state.host.call(msg); @@ -252,7 +268,7 @@ Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noex state.return_data.assign(result.output_data, result.output_size); if (result.status_code == EVMC_SUCCESS) - stack.top() = intx::be::load(result.create_address); + stack.top() = intx::be::load(msg.recipient); return {EVMC_SUCCESS, gas_left}; } diff --git a/evmone/lib/evmone/instructions_opcodes.hpp b/evmone/lib/evmone/instructions_opcodes.hpp index f4ae262..fdd2e9c 100644 --- a/evmone/lib/evmone/instructions_opcodes.hpp +++ b/evmone/lib/evmone/instructions_opcodes.hpp @@ -73,6 +73,7 @@ enum Opcode : uint8_t // NOLINT(*-use-enum-class) OP_BASEFEE = 0x48, OP_BLOBHASH = 0x49, OP_BLOBBASEFEE = 0x4a, + OP_SLOTNUM = 0x4b, OP_POP = 0x50, OP_MLOAD = 0x51, @@ -160,6 +161,10 @@ enum Opcode : uint8_t // NOLINT(*-use-enum-class) OP_LOG3 = 0xa3, OP_LOG4 = 0xa4, + OP_DUPN = 0xe6, + OP_SWAPN = 0xe7, + OP_EXCHANGE = 0xe8, + OP_CREATE = 0xf0, OP_CALL = 0xf1, OP_CALLCODE = 0xf2, diff --git a/evmone/lib/evmone/instructions_storage.cpp b/evmone/lib/evmone/instructions_storage.cpp index d9d40f2..58d91de 100644 --- a/evmone/lib/evmone/instructions_storage.cpp +++ b/evmone/lib/evmone/instructions_storage.cpp @@ -2,6 +2,7 @@ // Copyright 2019 The evmone Authors. // SPDX-License-Identifier: Apache-2.0 +#include "constants.hpp" #include "instructions.hpp" namespace evmone::instr::core @@ -30,7 +31,6 @@ constexpr auto storage_cost_spec = []() noexcept { tbl[rev] = {false, 200, 20000, 5000, 15000}; // Net cost schedule. - tbl[EVMC_CONSTANTINOPLE] = {true, 200, 20000, 5000, 15000}; tbl[EVMC_ISTANBUL] = {true, 800, 20000, 5000, 15000}; tbl[EVMC_BERLIN] = { true, instr::warm_storage_read_cost, 20000, 5000 - instr::cold_sload_cost, 15000}; @@ -41,6 +41,7 @@ constexpr auto storage_cost_spec = []() noexcept { tbl[EVMC_CANCUN] = tbl[EVMC_LONDON]; tbl[EVMC_PRAGUE] = tbl[EVMC_LONDON]; tbl[EVMC_OSAKA] = tbl[EVMC_LONDON]; + tbl[EVMC_AMSTERDAM] = tbl[EVMC_LONDON]; tbl[EVMC_EXPERIMENTAL] = tbl[EVMC_LONDON]; return tbl; }(); @@ -120,7 +121,7 @@ Result sstore(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept if (state.in_static_mode()) return {EVMC_STATIC_MODE_VIOLATION, gas_left}; - if (state.rev >= EVMC_ISTANBUL && gas_left <= 2300) + if (state.rev >= EVMC_ISTANBUL && gas_left <= CALL_STIPEND) return {EVMC_OUT_OF_GAS, gas_left}; const auto key = intx::be::store(stack.pop()); diff --git a/evmone/lib/evmone/instructions_traits.hpp b/evmone/lib/evmone/instructions_traits.hpp index 13e2c5a..d42429f 100644 --- a/evmone/lib/evmone/instructions_traits.hpp +++ b/evmone/lib/evmone/instructions_traits.hpp @@ -129,14 +129,12 @@ constexpr inline GasCostTable gas_costs = []() noexcept { table[EVMC_BYZANTIUM][OP_STATICCALL] = 700; table[EVMC_BYZANTIUM][OP_REVERT] = 0; - table[EVMC_CONSTANTINOPLE] = table[EVMC_BYZANTIUM]; - table[EVMC_CONSTANTINOPLE][OP_SHL] = 3; - table[EVMC_CONSTANTINOPLE][OP_SHR] = 3; - table[EVMC_CONSTANTINOPLE][OP_SAR] = 3; - table[EVMC_CONSTANTINOPLE][OP_EXTCODEHASH] = 400; - table[EVMC_CONSTANTINOPLE][OP_CREATE2] = 32000; - - table[EVMC_PETERSBURG] = table[EVMC_CONSTANTINOPLE]; + table[EVMC_PETERSBURG] = table[EVMC_BYZANTIUM]; + table[EVMC_PETERSBURG][OP_SHL] = 3; + table[EVMC_PETERSBURG][OP_SHR] = 3; + table[EVMC_PETERSBURG][OP_SAR] = 3; + table[EVMC_PETERSBURG][OP_EXTCODEHASH] = 400; + table[EVMC_PETERSBURG][OP_CREATE2] = 32000; table[EVMC_ISTANBUL] = table[EVMC_PETERSBURG]; table[EVMC_ISTANBUL][OP_BALANCE] = 700; @@ -176,7 +174,13 @@ constexpr inline GasCostTable gas_costs = []() noexcept { table[EVMC_OSAKA] = table[EVMC_PRAGUE]; table[EVMC_OSAKA][OP_CLZ] = 5; - table[EVMC_EXPERIMENTAL] = table[EVMC_OSAKA]; + table[EVMC_AMSTERDAM] = table[EVMC_OSAKA]; + table[EVMC_AMSTERDAM][OP_SLOTNUM] = 2; + table[EVMC_AMSTERDAM][OP_DUPN] = 3; + table[EVMC_AMSTERDAM][OP_SWAPN] = 3; + table[EVMC_AMSTERDAM][OP_EXCHANGE] = 3; + + table[EVMC_EXPERIMENTAL] = table[EVMC_AMSTERDAM]; return table; }(); @@ -252,9 +256,9 @@ constexpr inline std::array traits = []() noexcept { table[OP_XOR] = {"XOR", 0, false, 2, -1, EVMC_FRONTIER}; table[OP_NOT] = {"NOT", 0, false, 1, 0, EVMC_FRONTIER}; table[OP_BYTE] = {"BYTE", 0, false, 2, -1, EVMC_FRONTIER}; - table[OP_SHL] = {"SHL", 0, false, 2, -1, EVMC_CONSTANTINOPLE}; - table[OP_SHR] = {"SHR", 0, false, 2, -1, EVMC_CONSTANTINOPLE}; - table[OP_SAR] = {"SAR", 0, false, 2, -1, EVMC_CONSTANTINOPLE}; + table[OP_SHL] = {"SHL", 0, false, 2, -1, EVMC_PETERSBURG}; + table[OP_SHR] = {"SHR", 0, false, 2, -1, EVMC_PETERSBURG}; + table[OP_SAR] = {"SAR", 0, false, 2, -1, EVMC_PETERSBURG}; table[OP_CLZ] = {"CLZ", 0, false, 1, 0, EVMC_OSAKA}; table[OP_KECCAK256] = {"KECCAK256", 0, false, 2, -1, EVMC_FRONTIER}; @@ -274,7 +278,7 @@ constexpr inline std::array traits = []() noexcept { table[OP_EXTCODECOPY] = {"EXTCODECOPY", 0, false, 4, -4, EVMC_FRONTIER}; table[OP_RETURNDATASIZE] = {"RETURNDATASIZE", 0, false, 0, 1, EVMC_BYZANTIUM}; table[OP_RETURNDATACOPY] = {"RETURNDATACOPY", 0, false, 3, -3, EVMC_BYZANTIUM}; - table[OP_EXTCODEHASH] = {"EXTCODEHASH", 0, false, 1, 0, EVMC_CONSTANTINOPLE}; + table[OP_EXTCODEHASH] = {"EXTCODEHASH", 0, false, 1, 0, EVMC_PETERSBURG}; table[OP_BLOCKHASH] = {"BLOCKHASH", 0, false, 1, 0, EVMC_FRONTIER}; table[OP_COINBASE] = {"COINBASE", 0, false, 0, 1, EVMC_FRONTIER}; @@ -287,6 +291,7 @@ constexpr inline std::array traits = []() noexcept { table[OP_BASEFEE] = {"BASEFEE", 0, false, 0, 1, EVMC_LONDON}; table[OP_BLOBHASH] = {"BLOBHASH", 0, false, 1, 0, EVMC_CANCUN}; table[OP_BLOBBASEFEE] = {"BLOBBASEFEE", 0, false, 0, 1, EVMC_CANCUN}; + table[OP_SLOTNUM] = {"SLOTNUM", 0, false, 0, 1, EVMC_AMSTERDAM}; table[OP_POP] = {"POP", 0, false, 1, -1, EVMC_FRONTIER}; table[OP_MLOAD] = {"MLOAD", 0, false, 1, 0, EVMC_FRONTIER}; @@ -372,6 +377,12 @@ constexpr inline std::array traits = []() noexcept { table[OP_SWAP15] = {"SWAP15", 0, false, 16, 0, EVMC_FRONTIER}; table[OP_SWAP16] = {"SWAP16", 0, false, 17, 0, EVMC_FRONTIER}; + // Stack height_required is 0 because it depends on the immediate. + // The opcode handler is assumed to check stack height. + table[OP_DUPN] = {"DUPN", 1, false, 0, 1, EVMC_AMSTERDAM}; + table[OP_SWAPN] = {"SWAPN", 1, false, 0, 0, EVMC_AMSTERDAM}; + table[OP_EXCHANGE] = {"EXCHANGE", 1, false, 0, 0, EVMC_AMSTERDAM}; + table[OP_LOG0] = {"LOG0", 0, false, 2, -2, EVMC_FRONTIER}; table[OP_LOG1] = {"LOG1", 0, false, 3, -3, EVMC_FRONTIER}; table[OP_LOG2] = {"LOG2", 0, false, 4, -4, EVMC_FRONTIER}; @@ -385,7 +396,7 @@ constexpr inline std::array traits = []() noexcept { table[OP_CALLCODE] = {"CALLCODE", 0, false, 7, -6, EVMC_FRONTIER}; table[OP_RETURN] = {"RETURN", 0, true, 2, -2, EVMC_FRONTIER}; table[OP_DELEGATECALL] = {"DELEGATECALL", 0, false, 6, -5, EVMC_HOMESTEAD}; - table[OP_CREATE2] = {"CREATE2", 0, false, 4, -3, EVMC_CONSTANTINOPLE}; + table[OP_CREATE2] = {"CREATE2", 0, false, 4, -3, EVMC_PETERSBURG}; table[OP_STATICCALL] = {"STATICCALL", 0, false, 6, -5, EVMC_BYZANTIUM}; table[OP_REVERT] = {"REVERT", 0, true, 2, -2, EVMC_BYZANTIUM}; table[OP_INVALID] = {"INVALID", 0, true, 0, 0, EVMC_FRONTIER}; diff --git a/evmone/lib/evmone/instructions_xmacro.hpp b/evmone/lib/evmone/instructions_xmacro.hpp index 07b2696..056b0fe 100644 --- a/evmone/lib/evmone/instructions_xmacro.hpp +++ b/evmone/lib/evmone/instructions_xmacro.hpp @@ -111,7 +111,7 @@ ON_OPCODE_IDENTIFIER(OP_BASEFEE, basefee) \ ON_OPCODE_IDENTIFIER(OP_BLOBHASH, blobhash) \ ON_OPCODE_IDENTIFIER(OP_BLOBBASEFEE, blobbasefee) \ - ON_OPCODE_UNDEFINED(0x4b) \ + ON_OPCODE_IDENTIFIER(OP_SLOTNUM, slotnum) \ ON_OPCODE_UNDEFINED(0x4c) \ ON_OPCODE_UNDEFINED(0x4d) \ ON_OPCODE_UNDEFINED(0x4e) \ @@ -276,9 +276,9 @@ ON_OPCODE_UNDEFINED(0xe3) \ ON_OPCODE_UNDEFINED(0xe4) \ ON_OPCODE_UNDEFINED(0xe5) \ - ON_OPCODE_UNDEFINED(0xe6) \ - ON_OPCODE_UNDEFINED(0xe7) \ - ON_OPCODE_UNDEFINED(0xe8) \ + ON_OPCODE_IDENTIFIER(OP_DUPN, dupn) \ + ON_OPCODE_IDENTIFIER(OP_SWAPN, swapn) \ + ON_OPCODE_IDENTIFIER(OP_EXCHANGE, exchange) \ ON_OPCODE_UNDEFINED(0xe9) \ ON_OPCODE_UNDEFINED(0xea) \ ON_OPCODE_UNDEFINED(0xeb) \ diff --git a/evmone/lib/evmone/tracing.cpp b/evmone/lib/evmone/tracing.cpp index 2ea267a..87d931a 100644 --- a/evmone/lib/evmone/tracing.cpp +++ b/evmone/lib/evmone/tracing.cpp @@ -70,7 +70,7 @@ class InstructionCounter : public Tracer { std::stack m_codes; std::array m_counters{}; - std::string_view m_out_file_path; + std::string m_out_file_path; void on_execution_start( evmc_revision /*rev*/, const evmc_message& /*msg*/, bytes_view code) noexcept override @@ -94,7 +94,7 @@ class InstructionCounter : public Tracer ~InstructionCounter() noexcept override { - std::ofstream out{std::string{m_out_file_path}}; + std::ofstream out{m_out_file_path}; out << "{\n"; bool first = true; for (size_t i = 0; i < std::size(m_counters); ++i) diff --git a/evmone/lib/evmone/vm.cpp b/evmone/lib/evmone/vm.cpp index 2e2f6f5..83a3447 100644 --- a/evmone/lib/evmone/vm.cpp +++ b/evmone/lib/evmone/vm.cpp @@ -22,11 +22,6 @@ void destroy(evmc_vm* vm) noexcept delete static_cast(vm); } -constexpr evmc_capabilities_flagset get_capabilities(evmc_vm* /*vm*/) noexcept -{ - return EVMC_CAPABILITY_EVM1; -} - evmc_set_option_result set_option(evmc_vm* c_vm, char const* c_name, char const* c_value) noexcept { const auto name = (c_name != nullptr) ? std::string_view{c_name} : std::string_view{}; @@ -78,7 +73,6 @@ VM::VM() noexcept PROJECT_VERSION, evmone::destroy, evmone::baseline::execute, - evmone::get_capabilities, evmone::set_option, } { diff --git a/evmone/lib/evmone_precompiles/CMakeLists.txt b/evmone/lib/evmone_precompiles/CMakeLists.txt index 6c2a47f..2d40fea 100644 --- a/evmone/lib/evmone_precompiles/CMakeLists.txt +++ b/evmone/lib/evmone_precompiles/CMakeLists.txt @@ -39,4 +39,12 @@ target_sources( sha256.cpp kzg.hpp kzg.cpp + kzg_precomputed_lines.hpp + kzg_precomputed_lines.cpp +) + +# Enable optimizations in the cryptography code also in Debug builds, otherwise it is very slow. +target_compile_options( + evmone_precompiles PRIVATE + $<$,$>:-O1> ) diff --git a/evmone/lib/evmone_precompiles/blake2b.cpp b/evmone/lib/evmone_precompiles/blake2b.cpp index 6de8504..bd17554 100644 --- a/evmone/lib/evmone_precompiles/blake2b.cpp +++ b/evmone/lib/evmone_precompiles/blake2b.cpp @@ -21,7 +21,8 @@ inline uint64_t rotr(uint64_t x, unsigned r) noexcept /// The G primitive function mixes two input words, "x" and "y", into /// four words indexed by "a", "b", "c", and "d" in the working vector v[0..15]. [[gnu::always_inline, clang::no_sanitize("coverage"), clang::no_sanitize("undefined")]] -void g(uint64_t v[16], size_t a, size_t b, size_t c, size_t d, uint64_t x, uint64_t y) noexcept +inline void g( + uint64_t v[16], size_t a, size_t b, size_t c, size_t d, uint64_t x, uint64_t y) noexcept { v[a] = v[a] + v[b] + x; v[d] = rotr(v[d] ^ v[a], 32); diff --git a/evmone/lib/evmone_precompiles/bls.cpp b/evmone/lib/evmone_precompiles/bls.cpp index f90a939..d67c59c 100644 --- a/evmone/lib/evmone_precompiles/bls.cpp +++ b/evmone/lib/evmone_precompiles/bls.cpp @@ -194,6 +194,7 @@ void store(uint8_t _rx[128], const blst_fp2& _x) noexcept [[nodiscard]] bool g1_msm(uint8_t _rx[64], uint8_t _ry[64], const uint8_t* _xycs, size_t size) { constexpr auto SINGLE_ENTRY_SIZE = (64 * 2 + 32); + assert(size != 0); assert(size % SINGLE_ENTRY_SIZE == 0); const auto npoints = size / SINGLE_ENTRY_SIZE; @@ -217,10 +218,6 @@ void store(uint8_t _rx[128], const blst_fp2& _x) noexcept if (!blst_p1_affine_in_g1(&*p_affine)) return false; - // Point at infinity must be filtered out for BLST library. - if (blst_p1_affine_is_inf(&*p_affine)) - continue; - const auto& p = p1_affines.emplace_back(*p_affine); p1_affine_ptrs.emplace_back(&p); @@ -229,20 +226,13 @@ void store(uint8_t _rx[128], const blst_fp2& _x) noexcept const auto& s = scalars.emplace_back(scalar); scalars_ptrs.emplace_back(s.b); } + assert(p1_affine_ptrs.size() == npoints); - if (p1_affine_ptrs.empty()) - { - std::memset(_rx, 0, 64); - std::memset(_ry, 0, 64); - return true; - } - - const auto scratch_size = - blst_p1s_mult_pippenger_scratch_sizeof(p1_affine_ptrs.size()) / sizeof(limb_t); + const auto scratch_size = blst_p1s_mult_pippenger_scratch_sizeof(npoints) / sizeof(limb_t); const auto scratch_space = std::make_unique_for_overwrite(scratch_size); blst_p1 out; - blst_p1s_mult_pippenger(&out, p1_affine_ptrs.data(), p1_affine_ptrs.size(), scalars_ptrs.data(), - 256, scratch_space.get()); + blst_p1s_mult_pippenger( + &out, p1_affine_ptrs.data(), npoints, scalars_ptrs.data(), 256, scratch_space.get()); blst_p1_affine result; blst_p1_to_affine(&result, &out); @@ -255,6 +245,7 @@ void store(uint8_t _rx[128], const blst_fp2& _x) noexcept [[nodiscard]] bool g2_msm(uint8_t _rx[128], uint8_t _ry[128], const uint8_t* _xycs, size_t size) { constexpr auto SINGLE_ENTRY_SIZE = (128 * 2 + 32); + assert(size != 0); assert(size % SINGLE_ENTRY_SIZE == 0); const auto npoints = size / SINGLE_ENTRY_SIZE; @@ -278,10 +269,6 @@ void store(uint8_t _rx[128], const blst_fp2& _x) noexcept if (!blst_p2_affine_in_g2(&*p_affine)) return false; - // Point at infinity must be filtered out for BLST library. - if (blst_p2_affine_is_inf(&*p_affine)) - continue; - const auto& p = p2_affines.emplace_back(*p_affine); p2_affine_ptrs.emplace_back(&p); @@ -290,20 +277,13 @@ void store(uint8_t _rx[128], const blst_fp2& _x) noexcept const auto& s = scalars.emplace_back(scalar); scalars_ptrs.emplace_back(s.b); } + assert(p2_affine_ptrs.size() == npoints); - if (p2_affine_ptrs.empty()) - { - std::memset(_rx, 0, 128); - std::memset(_ry, 0, 128); - return true; - } - - const auto scratch_size = - blst_p2s_mult_pippenger_scratch_sizeof(p2_affine_ptrs.size()) / sizeof(limb_t); + const auto scratch_size = blst_p2s_mult_pippenger_scratch_sizeof(npoints) / sizeof(limb_t); const auto scratch_space = std::make_unique_for_overwrite(scratch_size); blst_p2 out; - blst_p2s_mult_pippenger(&out, p2_affine_ptrs.data(), p2_affine_ptrs.size(), scalars_ptrs.data(), - 256, scratch_space.get()); + blst_p2s_mult_pippenger( + &out, p2_affine_ptrs.data(), npoints, scalars_ptrs.data(), 256, scratch_space.get()); blst_p2_affine result; blst_p2_to_affine(&result, &out); diff --git a/evmone/lib/evmone_precompiles/bn254.cpp b/evmone/lib/evmone_precompiles/bn254.cpp index 8971d23..7c65617 100644 --- a/evmone/lib/evmone_precompiles/bn254.cpp +++ b/evmone/lib/evmone_precompiles/bn254.cpp @@ -28,8 +28,8 @@ AffinePoint mul(const AffinePoint& pt, const uint256& c) noexcept // See ecc::decompose() for more details. const auto [k1, k2] = ecc::decompose(c); - const auto q = AffinePoint{Curve::BETA * pt.x, !k2.sign ? pt.y : -pt.y}; - const auto p = AffinePoint{pt.x, !k1.sign ? pt.y : -pt.y}; + const auto q = AffinePoint{Curve::BETA * pt.x, k2.sign ? -pt.y : pt.y}; + const auto p = AffinePoint{pt.x, k1.sign ? -pt.y : pt.y}; const auto pr = msm(k1.value, p, k2.value, q); return ecc::to_affine(pr); } diff --git a/evmone/lib/evmone_precompiles/bn254.hpp b/evmone/lib/evmone_precompiles/bn254.hpp index 1294b10..4d9fa95 100644 --- a/evmone/lib/evmone_precompiles/bn254.hpp +++ b/evmone/lib/evmone_precompiles/bn254.hpp @@ -4,6 +4,7 @@ #pragma once #include "ecc.hpp" +#include "pairing/field_template.hpp" #include #include #include @@ -52,12 +53,32 @@ struct Curve /// @} }; +using Fq = Curve::Fp; + using AffinePoint = ecc::AffinePoint; -using Point = ecc::Point; -/// Note that real part of G2 value goes first and imaginary part is the second. i.e (a + b*i) -/// The pairing check precompile EVM ABI presumes that imaginary part goes first. -using ExtPoint = ecc::Point>; +/// Fq² extension field config: base field extended by the irreducible `u² + 1`. +/// Stays in this namespace so ADL finds multiply()/inverse() (defined in pairing/bn254/fields.hpp). +struct Fq2Config +{ + using BaseFieldT = Fq; + using ValueT = Fq; + static constexpr auto DEGREE = 2; +}; +/// Fq² element with coefficients in (real, imaginary) order. +using Fq2 = ecc::ExtFieldElem; + +/// The BN254 twisted curve E₂: y² = x³ + b/ξ over Fq². G2 lives here. +struct E2 +{ + using Fp = Fq2; + static constexpr auto A = 0; + /// b/ξ, i.e. Curve::B divided by the Fq⁶ non-residue ξ. + static constexpr Fp B{0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5_u256, + 0x9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2_u256}; +}; + +using ExtPoint = ecc::AffinePoint; /// Validates that point is from the bn254 curve group /// @@ -75,6 +96,6 @@ AffinePoint mul(const AffinePoint& pt, const uint256& c) noexcept; /// followed by a point from twisted curve G2 group over extension field Fq^2. /// @return `true` when ∏e(vG2[i], vG1[i]) == 1 for i in [0, n] else `false`. /// std::nullopt on error. -std::optional pairing_check(std::span> pairs) noexcept; +std::optional pairing_check(std::span> pairs) noexcept; } // namespace evmmax::bn254 diff --git a/evmone/lib/evmone_precompiles/ecc.hpp b/evmone/lib/evmone_precompiles/ecc.hpp index 7f4c853..bf95cc9 100644 --- a/evmone/lib/evmone_precompiles/ecc.hpp +++ b/evmone/lib/evmone_precompiles/ecc.hpp @@ -52,11 +52,21 @@ class FieldElement constexpr uint_type value() const noexcept { return Fp.from_mont(value_); } + /// The valid range for from_bytes(). + enum class Range : bool + { + full, ///< Valid in [0, ORDER). + half, ///< Valid in [0, ORDER/2]. + }; + + template static constexpr std::optional from_bytes( std::span b) noexcept { + constexpr auto LIMIT = R == Range::full ? ORDER : ORDER / 2 + 1; + const auto x = intx::be::load(b); - if (x >= ORDER) [[unlikely]] + if (x >= LIMIT) [[unlikely]] return std::nullopt; return FieldElement{x}; } @@ -119,16 +129,6 @@ class FieldElement static constexpr auto one() noexcept { return FieldElement{1}; } }; -/// The affine (two coordinates) point on an Elliptic Curve over a prime field. -template -struct Point -{ - ValueT x = {}; - ValueT y = {}; - - friend constexpr Point operator-(const Point& p) noexcept { return {p.x, -p.y}; } -}; - /// The affine (two coordinates) point on an Elliptic Curve over a prime field. template struct AffinePoint @@ -141,8 +141,11 @@ struct AffinePoint AffinePoint() = default; constexpr AffinePoint(const FE& x_, const FE& y_) noexcept : x{x_}, y{y_} {} - /// Create the point from literal values. - consteval AffinePoint(const Curve::uint_type& x_value, const Curve::uint_type& y_value) noexcept + /// Create the point from literal values. Only available when Curve defines uint_type. + template + requires requires { typename C::uint_type; } + consteval AffinePoint( + const typename C::uint_type& x_value, const typename C::uint_type& y_value) noexcept : x{x_value}, y{y_value} {} @@ -153,6 +156,8 @@ struct AffinePoint return p == AffinePoint{}; } + friend constexpr AffinePoint operator-(const AffinePoint& p) noexcept { return {p.x, -p.y}; } + static constexpr std::optional from_bytes( std::span b) noexcept { @@ -172,18 +177,19 @@ struct AffinePoint /// Elliptic curve point in Jacobian coordinates (X, Y, Z) /// representing the affine point (X/Z², Y/Z³). -/// TODO: Merge with JacPoint. template struct ProjPoint { using FE = Curve::Fp; FE x; - FE y{1}; // TODO: Make sure this is compile-time constant. + FE y = FE::one(); FE z; ProjPoint() = default; constexpr ProjPoint(const FE& x_, const FE& y_, const FE& z_) noexcept : x{x_}, y{y_}, z{z_} {} - constexpr explicit ProjPoint(const AffinePoint& p) noexcept : x{p.x}, y{p.y}, z{FE{1}} {} + constexpr explicit ProjPoint(const AffinePoint& p) noexcept + : x{p.x}, y{p.y}, z{FE::one()} + {} friend constexpr bool operator==(const ProjPoint& p, zero_t) noexcept { return p.z == 0; } @@ -201,41 +207,9 @@ struct ProjPoint friend constexpr ProjPoint operator-(const ProjPoint& p) noexcept { return {p.x, -p.y, p.z}; } }; -// Jacobian (three) coordinates point implementation. -template -struct JacPoint -{ - ValueT x = 1; - ValueT y = 1; - ValueT z = 0; - - // Compares two Jacobian coordinates points - friend constexpr bool operator==(const JacPoint& a, const JacPoint& b) noexcept - { - const auto bz2 = b.z * b.z; - const auto az2 = a.z * a.z; - - const auto bz3 = bz2 * b.z; - const auto az3 = az2 * a.z; - - return a.x * bz2 == b.x * az2 && a.y * bz3 == b.y * az3; - } - - friend constexpr JacPoint operator-(const JacPoint& p) noexcept { return {p.x, -p.y, p.z}; } - - // Creates Jacobian coordinates point from affine point - static constexpr JacPoint from(const ecc::Point& ap) noexcept - { - return {ap.x, ap.y, ValueT::one()}; - } -}; - -template -using InvFn = IntT (*)(const ModArith&, const IntT& x) noexcept; - /// Converts a projected point to an affine point. template -inline AffinePoint to_affine(const ProjPoint& p) noexcept +AffinePoint to_affine(const ProjPoint& p) noexcept { // This works correctly for the point at infinity (z == 0) because then z_inv == 0. const auto z_inv = 1 / p.z; @@ -397,7 +371,7 @@ ProjPoint add(const ProjPoint& p, const AffinePoint& q) noe } template -ProjPoint dbl(const ProjPoint& p) noexcept +constexpr ProjPoint dbl(const ProjPoint& p) noexcept { const auto& [x1, y1, z1] = p; diff --git a/evmone/lib/evmone_precompiles/keccak.c b/evmone/lib/evmone_precompiles/keccak.c index 0e4f926..add0f87 100644 --- a/evmone/lib/evmone_precompiles/keccak.c +++ b/evmone/lib/evmone_precompiles/keccak.c @@ -323,16 +323,11 @@ static inline ALWAYS_INLINE void keccak( const size_t hash_size = bits / 8; const size_t block_size = (1600 - bits * 2) / 8; - size_t i; - uint64_t* state_iter; - uint64_t last_word = 0; - uint8_t* last_word_iter = (uint8_t*)&last_word; - uint64_t state[25] = {0}; while (size >= block_size) { - for (i = 0; i < (block_size / word_size); ++i) + for (size_t i = 0; i < (block_size / word_size); ++i) { state[i] ^= load_le(data); data += word_size; @@ -343,7 +338,7 @@ static inline ALWAYS_INLINE void keccak( size -= block_size; } - state_iter = state; + uint64_t* state_iter = state; while (size >= word_size) { @@ -353,21 +348,17 @@ static inline ALWAYS_INLINE void keccak( size -= word_size; } - while (size > 0) - { - *last_word_iter = *data; - ++last_word_iter; - ++data; - --size; - } - *last_word_iter = 0x01; - *state_iter ^= to_le64(last_word); + // Absorb last 0–7 bytes of input + the padding byte. + uint64_t last_word = (uint64_t)0x01 << (size * 8); + for (size_t i = 0; i < size; ++i) + last_word |= (uint64_t)data[i] << (i * 8); + *state_iter ^= last_word; - state[(block_size / word_size) - 1] ^= 0x8000000000000000; + state[(block_size / word_size) - 1] ^= 0x8000000000000000; // Last block bit flip. keccakf1600_best(state); - for (i = 0; i < (hash_size / word_size); ++i) + for (size_t i = 0; i < (hash_size / word_size); ++i) out[i] = to_le64(state[i]); } diff --git a/evmone/lib/evmone_precompiles/kzg.cpp b/evmone/lib/evmone_precompiles/kzg.cpp index fd211c1..0840a33 100644 --- a/evmone/lib/evmone_precompiles/kzg.cpp +++ b/evmone/lib/evmone_precompiles/kzg.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "kzg.hpp" +#include "kzg_precomputed_lines.hpp" #include #include #include @@ -12,45 +13,12 @@ namespace evmone::crypto { namespace { -/// The field element 1 in Montgomery form. -constexpr blst_fp ONE = {0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, - 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493}; - -/// The negation of the subgroup G1 generator -[1]₁ (Jacobian coordinates in Montgomery form). -constexpr blst_p1 G1_GENERATOR_NEGATIVE = { +/// The negation of the subgroup G1 generator -[1]₁ (affine coordinates in Montgomery form). +constexpr blst_p1_affine G1_GENERATOR_NEGATIVE{ {0x5cb38790fd530c16, 0x7817fc679976fff5, 0x154f95c7143ba1c1, 0xf0ae6acdf3d0e747, 0xedce6ecc21dbf440, 0x120177419e0bfb75}, {0xff526c2af318883a, 0x92899ce4383b0270, 0x89d7738d9fa9d055, 0x12caf35ba344c12a, - 0x3cff1b76964b5317, 0x0e44d2ede9774430}, - ONE}; - -/// The negation of the subgroup G2 generator -[1]₂ (Jacobian coordinates in Montgomery form). -constexpr blst_p2 G2_GENERATOR_NEGATIVE{ - {{{0xf5f28fa202940a10, 0xb3f5fb2687b4961a, 0xa1a893b53e2ae580, 0x9894999d1a3caee9, - 0x6f67b7631863366b, 0x058191924350bcd7}, - {0xa5a9c0759e23f606, 0xaaa0c59dbccd60c3, 0x3bb17e18e2867806, 0x1b1ab6cc8541b367, - 0xc2b6ed0ef2158547, 0x11922a097360edf3}}}, - {{{0x6d8bf5079fb65e61, 0xc52f05df531d63a5, 0x7f4a4d344ca692c9, 0xa887959b8577c95f, - 0x4347fe40525c8734, 0x197d145bbaff0bb5}, - {0x0c3e036d209afa4e, 0x0601d8f4863f9e23, 0xe0832636bacc0a84, 0xeb2def362a476f84, - 0x64044f659f0ee1e9, 0x0ed54f48d5a1caa7}}}, - {{ONE, {}}}}; - -/// The point from the G2 series, index 1 of the Ethereum KZG trusted setup, -/// i.e. [s]₂ where s is the trusted setup's secret. -/// Affine coordinates in Montgomery form. -/// The original value in compressed form (y-parity bit and Fp2 x coordinate) -/// is the ["g2_monomial"][1] of the JSON object found at -/// https://github.com/ethereum/consensus-specs/blob/dev/presets/mainnet/trusted_setups/trusted_setup_4096.json#L8200 -constexpr blst_p2_affine KZG_SETUP_G2_1{ - {{{0x6120a2099b0379f9, 0xa2df815cb8210e4e, 0xcb57be5577bd3d4f, 0x62da0ea89a0c93f8, - 0x02e0ee16968e150d, 0x171f09aea833acd5}, - {0x11a3670749dfd455, 0x04991d7b3abffadc, 0x85446a8e14437f41, 0x27174e7b4e76e3f2, - 0x7bfa6dd397f60a20, 0x02fcc329ac07080f}}}, - {{{0xaa130838793b2317, 0xe236dd220f891637, 0x6502782925760980, 0xd05c25f60557ec89, - 0x6095767a44064474, 0x185693917080d405}, - {0x549f9e175b03dc0a, 0x32c0c95a77106cfe, 0x64a74eae5705d080, 0x53deeaf56659ed9e, - 0x09a1d368508afb93, 0x12cf3a4525b5e9bd}}}}; + 0x3cff1b76964b5317, 0x0e44d2ede9774430}}; /// Load and validate an element from the group order field. std::optional validate_scalar(std::span b) noexcept @@ -85,38 +53,14 @@ blst_p1_affine add_or_double(const blst_p1_affine& p, const blst_p1& q) noexcept return ra; } -blst_p1 mult(const blst_p1& p, const blst_scalar& v) noexcept -{ - blst_p1 r; - blst_p1_mult(&r, &p, v.b, BLS_MODULUS_BITS); - return r; -} - -/// Add two points from E2 and convert the result to affine form. -/// The conversion to affine is very costly so use only if the affine of the result is needed. -blst_p2_affine add_or_double(const blst_p2_affine& p, const blst_p2& q) noexcept -{ - blst_p2 r; - blst_p2_add_or_double_affine(&r, &q, &p); - blst_p2_affine ra; - blst_p2_to_affine(&ra, &r); - return ra; -} - -blst_p2 mult(const blst_p2& p, const blst_scalar& v) noexcept -{ - blst_p2 r; - blst_p2_mult(&r, &p, v.b, BLS_MODULUS_BITS); - return r; -} - -bool pairings_verify( - const blst_p1_affine& a1, const blst_p1_affine& b1, const blst_p2_affine& b2) noexcept +bool pairings_verify(const blst_p1_affine& a1, const blst_p1_affine& b1) noexcept { + // Uses precomputed Miller loop lines for the G2 generator [1]₂. blst_fp12 left; - blst_aggregated_in_g1(&left, &a1); + blst_miller_loop_lines(&left, g2_gen_lines(), &a1); + // Uses precomputed Miller loop lines for KZG_SETUP_G2_1 ([s]₂). blst_fp12 right; - blst_miller_loop(&right, &b2, &b1); + blst_miller_loop_lines(&right, kzg_setup_g2_1_lines(), &b1); return blst_fp12_finalverify(&left, &right); } } // namespace @@ -150,19 +94,23 @@ bool kzg_verify_proof(const std::byte versioned_hash[VERSIONED_HASH_SIZE], const if (!Pi) return false; - // Compute -Y as [y * -1]₁. - const auto neg_Y = mult(G1_GENERATOR_NEGATIVE, *yy); - - // Compute C - Y. It can happen that C == -Y so doubling may be needed. - const auto C_sub_Y = add_or_double(*C, neg_Y); + // The standard KZG verification equation + // e(C - [y]₁, [1]₂) =? e(π, [s - z]₂) + // is rearranged via bilinearity into + // e(C + [z]π - [y]₁, [1]₂) =? e(π, [s]₂) + // which eliminates the G2 multiplication and uses the 2-point MSM for G1. - // Compute -Z as [z * -1]₂. - const auto neg_Z = mult(G2_GENERATOR_NEGATIVE, *zz); + // Compute [z]π + [y](-[1]₁). + const blst_p1_affine* const points[]{&*Pi, &G1_GENERATOR_NEGATIVE}; + const byte* const scalars[]{zz->b, yy->b}; + // For 2 points this actually doesn't use the Pippenger, and we can skip the scratch allocation. + blst_p1 z_pi_minus_y_g1; + blst_p1s_mult_pippenger(&z_pi_minus_y_g1, points, 2, scalars, BLS_MODULUS_BITS, nullptr); - // Compute X - Z which is [s - z]₂. - const auto X_sub_Z = add_or_double(KZG_SETUP_G2_1, neg_Z); + // Compute C + ([z]π - [y]₁). The addends may be the same / opposite points. + const auto lsh_g1 = add_or_double(*C, z_pi_minus_y_g1); - // e(C - [y]₁, [1]₂) =? e(Pi, [s - z]₂) - return pairings_verify(C_sub_Y, *Pi, X_sub_Z); + // e(C + [z]π - [y]₁, [1]₂) =? e(π, [s]₂) + return pairings_verify(lsh_g1, *Pi); } } // namespace evmone::crypto diff --git a/evmone/lib/evmone_precompiles/kzg_precomputed_lines.cpp b/evmone/lib/evmone_precompiles/kzg_precomputed_lines.cpp new file mode 100644 index 0000000..ebfb2a3 --- /dev/null +++ b/evmone/lib/evmone_precompiles/kzg_precomputed_lines.cpp @@ -0,0 +1,1130 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2026 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "kzg_precomputed_lines.hpp" + +namespace evmone::crypto +{ +namespace +{ + +/// Precomputed Miller-loop lines for the BLS12-381 G2 generator [1]₂. +/// +/// To regenerate the line table: call blst_precompute_lines() on +/// blst_p2_affine_generator() and serialize the resulting blst_fp6[68] +/// as hex-limb literals. +// clang-format off +constexpr blst_fp6 G2_GEN_LINES[68]{ + {{ + {{{0xe547457b5803d28e, 0xa04c9204cec6827a, 0x30d1942baa126eda, 0x6f845b37e080e5dc, 0x37c92fab9ea539bb, 0x0643c42a1f6a0081}, + {0xfddf233864606368, 0xb667f347cf9d52f1, 0x6865350865cd31c6, 0x9e4fcc458c685791, 0xda1b03b43c10b649, 0x1378d5a36d32abd4}}}, + {{{0x1dd0773767275300, 0xa9c17f2a3bafcd7e, 0x9d3d896fecae6d6e, 0x7bc97690f4ed0da6, 0xf332c2894908633e, 0x06e24c8c8f6f83f2}, + {0x05a3bb58893e6a5d, 0xe121302e421ccbf6, 0x261553e775539726, 0x823c0287054407ea, 0x547e9daac976afd1, 0x148386b635328897}}}, + {{{0x98e615f0c0929894, 0xb2f9f43ebc6d38b4, 0xcfcd0ad95414c6b4, 0x77df6bd2dc1a92bf, 0x0fa752ebe1de4b45, 0x0107fb1cfd01b5ca}, + {0x5b81f925bec960ba, 0x31544e145628c3b9, 0x0d5b58d477c9d740, 0xf292b89d927b4675, 0xce2eb0a1487995da, 0x16578542c7bc37e5}}}, + }}, + {{ + {{{0x74bfdd645d7e9208, 0x3ba5ee1dde7817fd, 0xac74b94d19f87144, 0x87f74d68add9d596, 0x43ba27f7ce72f32b, 0x179a55d014bf8a9a}, + {0xca9accc9319c9007, 0x5a6897239dd2c979, 0x97ad796a00f2ffbf, 0x0f2fa1213d64f883, 0x3ee851573b2b0e59, 0x02151bfe04f37465}}}, + {{{0x7426d892bf7bcf5c, 0x7731af36dca032e6, 0xa9dfddaf9f607571, 0x75170343b9bd0d85, 0x7c9b869902a7f3d6, 0x14e3db2ca0901a90}, + {0x6c694bb35588c329, 0x9c0f6e66303d9b40, 0x4ea76f658ac953ae, 0xb09a163cdcd88ed6, 0x956f77637ee17485, 0x002152d96bbd34f1}}}, + {{{0xa5e24e340c7a03ff, 0x2a55e6c4506aafd0, 0xdf27253d42c22d9e, 0x096bc0ae1de6eca3, 0x8faa05522ac01da5, 0x12f135b565072ffd}, + {0xed049e877e8ef67a, 0xf0e8950b0af88d7e, 0x915653f3002f2c2d, 0x9836791f4d9480a2, 0xf79ee12f6b0aa3f3, 0x0688f93badbe50bd}}}, + }}, + {{ + {{{0x6f3d67eded6375e9, 0xb64a55d27d0522ab, 0x44ee847f06f0c0cc, 0x6b4691edcac19928, 0xe21156579b928eb9, 0x08c32fc615c7cfac}, + {0xe2885b7899a860fa, 0x1bc844e1ea8d722c, 0x70ec8c96d768c8ba, 0xad6225a5491531a0, 0xb55898dae5bb3b10, 0x15bb04252e63d409}}}, + {{{0x4d93c6b546b3858f, 0x0e446e7155994532, 0xb0001fc1039ea8e5, 0xf8dbc16565cb3450, 0xf866df99640edca7, 0x06966562861d99d9}, + {0x8820b0ef5415c898, 0xadaedc3fd0e32a07, 0x74764a3a6bf4acd9, 0x584f8c62cc291853, 0x8339e0772f704d99, 0x0b5430d1a1a59d9f}}}, + {{{0xe7176c3679935d19, 0x5a6abc7244b53073, 0x8d516fa9c4e4d165, 0xa17c0bddfcc7b9d0, 0x1d706354aaa482fb, 0x0aad0ddf6aaa6c44}, + {0xf29c597a0ab86844, 0x7277023a1750fe24, 0x013adc3ae3de6cd2, 0xec788cd5886e7e46, 0x584a52d850239628, 0x19812490153991af}}}, + }}, + {{ + {{{0x32f447ddab469744, 0xb0ecd90644e6bb37, 0x8f0a81f663d08d80, 0x393b2493aab1732e, 0xbd4c7df8bbe9c04a, 0x0559d7979ef2c53f}, + {0x9abe71bd3531865f, 0xca6d8cc7b1ed4c68, 0x76db754dd12c4250, 0x28bb39a78c8bd169, 0x3df4a5d1f4daa16b, 0x076da3b628f590be}}}, + {{{0xc49d21ffdff0642a, 0xfb83966288e61efe, 0xf1392c0c3d87eec6, 0x340233ca09268244, 0xb5c576c2c44981c8, 0x0f6294c9a60339a2}, + {0x65fcf1b4e95f0ae5, 0x63cc0ab71bf97d24, 0xe3e4a8f75bac444e, 0xcaf9bb7aa3ee1e7c, 0x385918c8b437e179, 0x1488c93ef0375ec4}}}, + {{{0x0c51df3801277a41, 0xac4f5be1f18ec633, 0xec9574cc97a61c7e, 0x5153bb0ed969d33e, 0x84cb5594ddb504ae, 0x0f14a618fc0caac9}, + {0x6195655ab4d0cbb7, 0xec63183ddc6e944f, 0xcc6ad2afed422a0f, 0x399889ce0d8b76ca, 0x9766c87f3a262960, 0x0f0b5b8bd3a96bd6}}}, + }}, + {{ + {{{0x70678dddfdc3b09e, 0x4581713cd2bf8256, 0xb8821ea2acb13090, 0x98c01ae399b2baae, 0xb593ae0a4cde7cc1, 0x18adbbd3232f7565}, + {0x06e11ce3af7da28e, 0x0ca19c56ec89b291, 0xd882f1730b12d318, 0x35376288c820cb40, 0x05e31f7b542ea445, 0x0bafbf9479b07c5e}}}, + {{{0x3bc587a9016c61a1, 0x71b9b965c4c26e5b, 0x89a7ffaf094c659a, 0x6b0b0d6298789c17, 0x6a9758ae389ef899, 0x17e06d65cacccc10}, + {0x6897fc7f1de2a2e9, 0x24ad43222ec51dd9, 0x29b1362eaa0a8f50, 0xf677d05dc3b9c73a, 0x09e35757379da914, 0x16b7f335067e8c45}}}, + {{{0xfaeb0133d6e8c469, 0x4152424643ed5012, 0xae0726e7916ff38e, 0x8582080256b27206, 0xcf6ae5f66602e010, 0x0aa3f4f94d6cd52a}, + {0x102bfca9de355fdc, 0x8a65facca0467f35, 0xa8dafc39f9b4c828, 0xbb9225ac00497d69, 0xf77dc16cebffeba9, 0x111984232684cd94}}}, + }}, + {{ + {{{0x9de83522edc3da7f, 0x93b71d38001e7b57, 0xc66cc99b1acd2473, 0x377fad1234c797b9, 0xcb28665db0fcc232, 0x10cf44f91645bdd2}, + {0x7207c28069ad3eff, 0x2ecd7227159ac360, 0xb8446b7c3553a34c, 0xac9a9650125dc8d6, 0x3747bca74528c5bc, 0x01910df79cadeb8b}}}, + {{{0x1ef51702a56b796a, 0xa995d0c033dcaba8, 0x82dd0aaed7cf2863, 0x08db57db9d71c1f0, 0x2805ca7f8ab721ec, 0x0ea69d0223a03385}, + {0x4bab3a76d8664a11, 0x67ff152910dce4c1, 0xb3f44cc97a4bacda, 0x57139daad0aed52f, 0x113b7e0680b08405, 0x11b70c039f4ee0e9}}}, + {{{0x81671725582412aa, 0x5f255976864b9448, 0xfb4fe54ac4d7d0ff, 0xaca1c4a888e011bf, 0xf2508bf022c3d669, 0x0e5554fc532e26a0}, + {0xa74f0a93a80a5d83, 0x647863c5f4f5737e, 0x6fb8ebb3f3688c77, 0x0f31860fe9ea719c, 0xa490c46a37a1f331, 0x0d7a7999572c7dd8}}}, + }}, + {{ + {{{0x299504eacc73012f, 0xcf6a6950758f98f8, 0xdd54c98472158dae, 0x552102c32548404f, 0x2c4be42eaa6ca6ad, 0x108089bf4b475a10}, + {0xcd67d7ce040290a8, 0xc54120d3e38417d6, 0x4a5efe25d15794f2, 0x8130a9439216861f, 0x0f29abaa66a06951, 0x15ecf449986d214d}}}, + {{{0x4e9127e0886cd8cd, 0xc6ad62e46cc10cd2, 0x660eb5425d672c10, 0x6f6edbbdc5cadf43, 0x234c77bddb11e614, 0x1477622b1adfc0e2}, + {0xaf3239744b069ba7, 0x510c8d7ec8a3372d, 0xce4a17417f014824, 0xe1e3810a2f051c2d, 0x7d13ccfffd135d46, 0x10648195a6fd940b}}}, + {{{0x1a171cf2f832f923, 0x8faa8451e7f44933, 0xe879f0cdcfb47602, 0xbd99e03ca2cdba73, 0x8385793516b584bc, 0x085777b856e2d31c}, + {0xebb75606d0e8873f, 0xed054bb0344c03e4, 0xe1f5c78856c9df48, 0x96754128cf5b7d9c, 0x5d8f2d7267112c37, 0x155e8492cfff0e98}}}, + }}, + {{ + {{{0x8cf89902e301aafc, 0xacaebd8cc3b6ad69, 0x2eae2286f19f64bf, 0xcfcb81acf26ca7da, 0x1da1a070323163c7, 0x1668b3615d8bd448}, + {0x1a956e96cf02cbaa, 0x26e92f8fe1571d16, 0x92d2993e4ed73cc4, 0x5916cba9c5faa651, 0x12ef4b9238b24b0d, 0x040eb5b6f632fadd}}}, + {{{0x10503ed23fbfd427, 0xc5a0d8bf47bd6c14, 0x559745f24967dabf, 0x6a8bb24bdc83f38f, 0x9e7cd6ae212d6cc3, 0x14c7c72c47b91e84}, + {0x2930de43349f7378, 0x3ccdb2ed020a36d9, 0x8876836ee1f94161, 0x32275a74b4834ff0, 0xbcb936fb00bb2c21, 0x0af917f64efcf1d6}}}, + {{{0x9c9ac9f46ad18bb0, 0x37a436e773d08640, 0x4e003a7fe0942885, 0xe6c74d1225e3805e, 0x2b6588598cf71a6f, 0x07b552a5a3a3d2e1}, + {0x3798e44337ef3569, 0xaab484856b5e4075, 0x42f2e410449d03e5, 0xc1c0ee085fd4c61b, 0x93b24184235e51a8, 0x0d70cf606519c957}}}, + }}, + {{ + {{{0xe8e42b320ac55c39, 0x9bf2c6ad11d1b291, 0xdffed5f58ff5edc1, 0x788525142d4c96ab, 0xf43a09c83c721544, 0x05e861825d823b4c}, + {0x5232ded5295cbe7f, 0x1979315d403d37c8, 0x6d6e54dfce1db5c5, 0xc0a18e4ac07aa8da, 0xbb5c314e38022d71, 0x1264563b2fff0be7}}}, + {{{0x892f20b9fbd0050b, 0x7c77d06173c5cb71, 0xa716bd37f1bd88d6, 0x9fb87c47f85e96f2, 0x0da80c8d321e68e1, 0x10f2259b2ac62e19}, + {0x7715d9360f394bb2, 0xd6db3de79cc02c7b, 0x9a1cded5b9c69faf, 0x5057447414ba21dc, 0xbe68d4fd425be759, 0x118b2ad7b3979e29}}}, + {{{0x13e2af9946b0c7c7, 0x3f1791c962656a60, 0x396b34658992e4c5, 0x5dc398084308fd2d, 0x03669791885d453c, 0x19f4e44ea0a1ac4c}, + {0x582e081e239843bb, 0x0d9e562147b22e5f, 0x451cb4cd405be175, 0x9512cb8cd03ab965, 0x20e7ff2f6d3c1c0a, 0x00a12957b7ce6a86}}}, + }}, + {{ + {{{0x0c22a95cc328ac5d, 0x1d7b0a1facc850e9, 0x2c880eca047d90fd, 0x854be9173ab7d417, 0x1cd5f938001beadf, 0x15894e29f9e55e03}, + {0xb967aaf77ceb68a7, 0xa870822ef7e3cec0, 0x4b7aab48112667ab, 0xd07c2179faa925f1, 0x646088ed6a667a3e, 0x197e2219f6a48c31}}}, + {{{0x9f5c05fbf5d35e44, 0xec00ff1b6b941bae, 0x055bb3e795f82ddf, 0xe907532c4cfa8dfe, 0x4d0b1c0a7365d842, 0x016b836eb1a23ebf}, + {0xfd986f4a39df98cb, 0x1cf96e9d21338a86, 0x587c859c4b11a40f, 0x2a32da1b52821f75, 0x743525c0f0b8a8d4, 0x105206a5042d37d4}}}, + {{{0xa8fd9a0265d8c508, 0xc4edd6fa77412050, 0x4c63d051abb761e3, 0x966f6277f67d79e1, 0xd7787c44228b92d3, 0x0c7857f886114a8b}, + {0x38cf43c6892661ec, 0xc47451a47dd1e7b2, 0x4d129762dc69f21d, 0x68c33836e651ea79, 0x578a2adb63cbbb31, 0x0acf7990e4c286b7}}}, + }}, + {{ + {{{0xe153f6c19d79dcc4, 0xea17bd01deb5afb2, 0x6c8a156e08de6d72, 0xe7f01710dd687922, 0x70acf1822ea2babe, 0x17a0e4034d16b9b2}, + {0xf1b2a38ce3bf6d3a, 0xb69f099cf1438d22, 0xa8491ebcadd408cc, 0x2fbebe31eb52ea24, 0x1fe2029d2901e5d7, 0x04afff13f63badf8}}}, + {{{0x412d934cbdc9e620, 0xbf71904d9c3baaaf, 0x38cec507d26611e1, 0x2f0cb4495f85acd1, 0xb3ffa3c9b6bebc72, 0x0146de13661c7c72}, + {0xcb828e1858055ee5, 0xd320700a17b8f395, 0x782cb698f2db7821, 0x9fcf887ec1a2188b, 0x43bac4f7677b0462, 0x09c2341bd78a5f23}}}, + {{{0xef5e5e1b6754b416, 0xecf08e54dc10414e, 0x7abdbf47a41bff84, 0x475881202e776488, 0x9f1b4aff31504618, 0x0ce93162579480d6}, + {0xa0b5cb27ca237c68, 0xcde882bfde9ecf92, 0xa3cb2ac8c627aab5, 0xff937642474f7d4d, 0xa84c7364e2ada6c2, 0x0f251dd0c78df8d7}}}, + }}, + {{ + {{{0x56cca43fbcb947ac, 0x0bac6d619201124b, 0x9caabca0fb440e66, 0x7dcbdcef7e2249ed, 0x8e3702e35c9be342, 0x11cdada49dcae95e}, + {0x7f4b0cb3839d3afc, 0x47590d57aff04242, 0x627915b80aeddc8a, 0x158661c02d6cdc44, 0xc5b941effd7f1b2f, 0x008ae31d985201d2}}}, + {{{0x97a77ec53041d2e9, 0x6c9213dc6d4f6073, 0xaca5b003d1bd6249, 0xd66d611c1faf4b28, 0x0a711d18435f1c01, 0x133e860b930199a5}, + {0x4d7e08c11c14ee99, 0xb3173ed1a0769326, 0xcb24b99b80a452a0, 0xe08b158eda9bc03d, 0x76eb3bf205b2aed9, 0x05ce06ae1ec4e72d}}}, + {{{0xbc3c92678b9b8d04, 0xa7e57d0abe54ef73, 0x56aacb9da9dbbdfa, 0x9230843ca62acdbb, 0xee06c1627ef3c404, 0x0b2835511c75d5a3}, + {0xbde04a1c9fc9f571, 0xb92c982f44629040, 0xfbcf849251de9b4b, 0xb55aa93f1a66ca49, 0xb6e8c33e7ba49767, 0x0ea6b480ea56b2bf}}}, + }}, + {{ + {{{0x59fe5d1668b06514, 0x44f31d2547fdf0e2, 0xdc51c576df832de1, 0xd19e3ac149c2f5bc, 0x6b2460bff6cc6cf0, 0x1288778a66109028}, + {0xc47141285c3840a7, 0xdd4d90aee95934a2, 0x07ecc291de26c087, 0x16bb0c2b3dbe205f, 0x06ae4686c9b81cfa, 0x035358ca5bbe8a49}}}, + {{{0x1ac6311de262c74f, 0x8cf21fad86dfbbaa, 0xfe54e5c77a555c22, 0x1daa193cf61f3825, 0x20bd20dbabd06278, 0x0fdda2e14dc12f9e}, + {0x9ae01b04b4f79608, 0x4c77c9e47449a0da, 0xa904dd00a4fdaa88, 0x6e56d4d01117a152, 0x41037684d9f37cdd, 0x1639d6ef0bff8d98}}}, + {{{0xf8a69ca9ceae34ba, 0x86dfc80ca8049727, 0x74bda49117a35237, 0xea72c883ca49936b, 0x45063c43d8f9f171, 0x0afd30fd2a6da504}, + {0x9b646bf24eeb857e, 0x33ca0066a44699ad, 0x9afb676388d753a2, 0xd1770a3fccaaf0ba, 0xaca4cbdc820cd08e, 0x10409a92d81cc73a}}}, + }}, + {{ + {{{0x3e538c4dc1d8b628, 0x6cb1657b878108bc, 0xcafa328e61eb2dfd, 0x6fcc4b452f7fbe6f, 0x28da465220b4d505, 0x0c431b4499efa89f}, + {0xcad376670b4efc7a, 0x175d40f5a6f874c6, 0x220a67cc7e854113, 0x63aecff57c61912c, 0x30bee0c20732cec6, 0x158ad4015d81f14e}}}, + {{{0x2916d602f8353f1d, 0x7f8219c9e8bc91b4, 0xe969ed03fa3d079b, 0xe7c17f27951d74be, 0xf81d0d1764dece4d, 0x12f7b19d492df76a}, + {0xb6a0a4ca77373e3e, 0x82ae4b3c45bf4468, 0xf542a65223b48705, 0x458938e7f69bc8d5, 0x79a638a5c9f97a8d, 0x046c05f1ca1cdf10}}}, + {{{0xfa0ea3ed905879e7, 0x71e988fe8262c735, 0x285a5e01fb51b33b, 0x767b6bb9599f665f, 0x91731c2a5d7dd4d9, 0x059d0cd2758e5db5}, + {0xb264870a9cf6f837, 0xc02f0eac87180b8d, 0x0215a68246ce2dd0, 0x232acfa5e49e9639, 0x31dcb5effde9c1a8, 0x02e239bbadb5c1c2}}}, + }}, + {{ + {{{0xd84901810ab8a03c, 0x3ce52ead7d0b1e0a, 0xe09b6f1931040a96, 0xf06b529215a7281d, 0xaf974fbfa57c99ec, 0x12cf98968cc7488a}, + {0x714f18157720688c, 0x0df3fcdf568580a1, 0xf1a596bfb82e51b5, 0x65b70e0240911e0a, 0xc573e546f154210b, 0x105465e28d8e6d76}}}, + {{{0xbca5073454aaea8e, 0xc7320b852d8f35c2, 0xe1fb088b2e4b66d2, 0x5ddb455b1062ff15, 0xf99a32bb2c5584a6, 0x07a7315f1cb30690}, + {0x74b706524d1c02f8, 0xd8b27b33db657954, 0xed5ab6c69c493f01, 0x1bbbdf07f058e1b1, 0x7a00373505f20eb5, 0x05b096f3569862ad}}}, + {{{0xe7ac3aea71034aaf, 0x5795659eeea1ef5d, 0xd30739102c159ff2, 0x991274d2140ac9c9, 0xb405d7f3bfcb98d5, 0x06d6b97b2ff11802}, + {0x76995d4b0712020c, 0x6be9ccbb1f80a121, 0x6d82e9f3bc15a777, 0x3ee93b8b4cc7c53c, 0x7420ced9b40092f3, 0x04368c6c0911c869}}}, + }}, + {{ + {{{0x2bae022289d5972f, 0x953ef8afcba2f1e0, 0x1fc13b17bd485e01, 0xb94901d34329b695, 0x2c8caad360d6805a, 0x09d24759af07212d}, + {0x7fa3b11ba3708252, 0x8a9af4ccc3a7b681, 0xaf9e6a35b336f2fa, 0xeff546711759ed7a, 0x53df532a6ceecb0e, 0x10a99fc992ae3979}}}, + {{{0x864972d8f90ef2bc, 0xce24f14cff73ad45, 0xeb65b20f66ba1d7c, 0xbc5e600f9eb64860, 0xcc8b89509ba18fc4, 0x0e549c965e5bf657}, + {0x31c94593d1e74cab, 0x9ef9720ef25c5420, 0x6f339df9e287274e, 0xb51b9f4af0033588, 0xa9e1a8ebb319202c, 0x17135621e779aa50}}}, + {{{0x82d0d5d14d1922b2, 0x587143e401a0669d, 0xb82df798986ab463, 0x18a3bfb52c9a1432, 0x45f9ef4196dd3da2, 0x0662ddb7a0d4a126}, + {0xba10d3e160612442, 0xcec4be9cb4a7b8e4, 0x41a36029da348d3c, 0xdfc57c091e96639d, 0xb76510304bd08ed2, 0x1731211f7626a87b}}}, + }}, + {{ + {{{0xc78fc6b28545a1f5, 0xf371370fe6a5581a, 0xcc7fc697a698f808, 0x3807d7e480ba75c0, 0xffcb4c698f80c40b, 0x04323c8556eee7cc}, + {0xd6d36b3b81533d06, 0x120bf6e76ad2b1fe, 0xb29ce513f1d777c3, 0x02cba0865ef79fd8, 0x3467f7bed979903c, 0x15f3e230a7755a8d}}}, + {{{0xf295be74813f4490, 0xaddb05a6af15987a, 0x01a464fbab4950fa, 0x873ef0d5847cdd4e, 0x6e9ec3454eb46d3b, 0x117bbdf1bca7a097}, + {0xe6921e26ad2e3cd5, 0x27ab842e711685fd, 0xf0f9f4a2a803e986, 0x3a330247e77b3b75, 0xfcfd11caccda7a9b, 0x193c7d4f51bf9725}}}, + {{{0x07a4d1f12c2d989b, 0xdcf1777647d65d76, 0x1ccc632453c67379, 0x17614ca7fc9414ad, 0x435404c14765555e, 0x108cab018f6bed17}, + {0x41c6cea38287166e, 0xd18125f44c27463a, 0x27de9a55f0d66d1e, 0x5314577ddddae9c4, 0xb21bed6a77af007d, 0x0be8b0f058a9c28b}}}, + }}, + {{ + {{{0xbe30f4e73c6f2dc6, 0x2a274576fbd6d67b, 0x7ab975b94f28c6f3, 0xb45cecf5f6b6f4aa, 0x7ae428dbddeb3468, 0x13d304be9359b02b}, + {0x2a18faf18154bd07, 0xe871320a74ff483f, 0x58d9d9bfd8a010a6, 0x27f2825e3bdc4379, 0xadd4e56badb3907c, 0x17a4414ed22edab7}}}, + {{{0xc846559939ccdb9b, 0x2d0affa28cdebbc5, 0xa81a712a1f2d667a, 0x15a60377a0097db5, 0x0c32686330836b45, 0x148bd87993b6ca8f}, + {0x3f6b0b65feb4dbd3, 0x71177e9eb29560a9, 0x70154bd83bedc1a8, 0x3d3eae8b9c7485f7, 0x5bddbbf8fb6de7d1, 0x0386f9be3ab0d31b}}}, + {{{0x4b5ce48f7b70243f, 0xddb66c3f2fef7abd, 0x8aed2d661de339d7, 0xe2bb3e97852a8941, 0x265734b9f2ff5c4b, 0x03bf2f014952661c}, + {0x582a73de6b8b813b, 0x4c5f48a76944d9b0, 0xc5cff7b29bef83ba, 0xf12e92090323f0b4, 0xe12469c1a2089b07, 0x186e4b2f431fbab7}}}, + }}, + {{ + {{{0x9e81fdebb45eb999, 0x8371e6ae2dcb5f33, 0x012eebd6bd816591, 0x7c6e4061d4e19a2d, 0x94e34676eea4977d, 0x16a7f3bc876d8641}, + {0xeecd0f7a0598a3fe, 0xb78df29bc194461f, 0x8d36b088a43c6a30, 0x3a3f6f86f7028fe9, 0x6469e6ac6628a949, 0x0c96ca6c0eac9a03}}}, + {{{0x43985137159e587b, 0x7c09aa97f2df1652, 0xb8cacfe348f5f485, 0x1422a6eb1e49641c, 0xb2dc84aa96335312, 0x14da532162af0d37}, + {0x76e03f9665d1d56b, 0x9dad8c3b634d69ae, 0xd9815e5b0fe19603, 0x6fe625cf3a4f20d6, 0xe9686d0d76089532, 0x08a96a99286fb084}}}, + {{{0x02966f2d815c6c54, 0xe6f77545fcbce404, 0x18638e6a712795cb, 0x6a6e72598a24789c, 0x173ca7892580ac08, 0x039af608ea5f139a}, + {0x50255dac622b6c37, 0x58eb75bd29c1a765, 0xa394aecf10a9d85c, 0xe10623ab2cb28925, 0xfc30c667323c6a4e, 0x19facd005c88dcf8}}}, + }}, + {{ + {{{0x9b100d703b4223dc, 0x0fe8a0443c979756, 0x0ee6884a2f554423, 0x1d56c2c0aef40b97, 0x924040459b154a61, 0x0789dcceac7aaba9}, + {0xce0c7ae42b967956, 0x110ffd2c1fc742dd, 0xe3ac2f1a9c81e062, 0xd078ab578bfb02ff, 0x04d5932b0d90f927, 0x03d9d7ea87938012}}}, + {{{0x5ca11fe3ad4cd531, 0x25ade4206fa73b81, 0xededa4182e1c4caf, 0xaada5ebf70d98168, 0x7e724b5fae760154, 0x07beb1cc4042e38d}, + {0x8b71ab182129a894, 0xb20193073c606ce4, 0xa94d974be7f1ce7d, 0x47fed9c5ba68cd2d, 0x6ccc3ef36de88403, 0x09d528c216d8e3e9}}}, + {{{0xcacb1a10327a15e6, 0x665194183cc08848, 0x3d55cbd5a4bb62ba, 0xc07414fe66de7aae, 0x4759dace9b0b947e, 0x18c2f2cee6ac8385}, + {0x1a0b6e7b3fa028ec, 0x4935fb371b4a1435, 0x3e15815f8b7616ba, 0xbeb708913ad910a7, 0x29cd1469a4677fe6, 0x1057d972a4e36c91}}}, + }}, + {{ + {{{0xa7c8f71c027a6931, 0x95fc9f1d861495c5, 0xed05e96145d7a870, 0xc8d5b0a4bfec2e17, 0x8c9238ec48c78dad, 0x0d27c4bc335c0604}, + {0xa1765f3e254f9a4e, 0x4f5ea309c65e682c, 0xd7f2c8dd1ddf996a, 0x938bda887c571609, 0xe3457d062a59fbfc, 0x046d52614b643b36}}}, + {{{0x91026d6c66a2abc8, 0x69c0668de53b32f3, 0x51a0a80660d0a6ba, 0x52ca5b0a89a4e36d, 0xaf9aed942dd151c6, 0x0cfb9d6480356705}, + {0x9f7f87aa1987dd9d, 0xf6f75baaccac60d7, 0x1eea8999e0363686, 0xdcc0c954b694cf72, 0x21793204c477b228, 0x0b5a754ff3c5928b}}}, + {{{0x000a2e6a3618fe42, 0x1f801f04dd9105d2, 0x97cc815a1c90ad7d, 0x6073c0496f5b44ba, 0x1636866f98f33d02, 0x0373fb8e31853dbb}, + {0xaa73dfbdb53d16bd, 0x842f11759385e1db, 0x202de2d28c9e34fb, 0x93f21acf055ddb54, 0xd83e6ba92b7a2e41, 0x07c6bdabbe46b3d6}}}, + }}, + {{ + {{{0xe55f61fd501c5861, 0xc8f2ee9dd2b4b219, 0xf242f9071c012560, 0x0ba29210cc873ac6, 0x12c45b4891d67827, 0x0649b158ccbdede9}, + {0xd1fe165cf3aea1ed, 0x1a0fbd3a389f4e69, 0xeb0daaa4319f6c91, 0xa6161f65914d4b61, 0x7cd2e924ed72dae3, 0x07339528336921b4}}}, + {{{0xfc94c958c554b28d, 0x04e749cc9dc898d2, 0xafd5384b56ab931d, 0x95899f7d05e05c7d, 0x7ed9647106787d3e, 0x05c2fa9c20c8f819}, + {0xbf1f05750dc0f4a7, 0x63affd2a7884e0ef, 0xe9dca61e4d09d41a, 0x2363dcfb2bc3e412, 0x1a57eb3ffe6d78eb, 0x18e049547e067f3f}}}, + {{{0xccebfcc6cdf0fa72, 0xa9d538bc9c9f23fa, 0xa724d08106aee68c, 0x6bbdd5cb339a4c5d, 0xc890802c8b82b965, 0x059891dba12e34bb}, + {0x28037391159c2410, 0x99a0260ba7f6f435, 0x1f45ecb973adf2e0, 0x9555f9fb5a93de14, 0xd63334a8d8fa30bd, 0x03ce076561053206}}}, + }}, + {{ + {{{0x6a98760e9aa51110, 0xa8d69fdbb783058c, 0xb07ed276152084de, 0x56ca2b48edd2972b, 0x63a1ff6f425debc6, 0x11b1603f1bfb5dd2}, + {0x2510e759d79025c2, 0xf14a583bf2cbf330, 0x3fb815253e944701, 0xf750810506a8ad0a, 0xceee48acaf6875de, 0x1841b5fe75d663d0}}}, + {{{0x02208813c3093476, 0x149933ff6f6098fa, 0x71472fb6e0bf3893, 0xdb104a9263aa58cd, 0x508d948208ba4ab1, 0x0fd40e47d3ac0eb3}, + {0xe03d5c8242187ff2, 0xeea978f1a46531f2, 0xb57a13f4645a3d79, 0x0e2a9cb9f85e83ce, 0x76ac424601b4f544, 0x110c173de2847917}}}, + {{{0x80bf82706c8cc797, 0x9c12943f30d9d2ef, 0x6936b523754c05de, 0x2dd61beb5a005acb, 0x7b0b051e80c4e47b, 0x122bb1c0da3fb9ed}, + {0x00ae3f08458877cc, 0x4daa2d169186a12c, 0x5f72690e565c435b, 0xfbe046446e5f3183, 0x2910d8182d38bd13, 0x090cc31c5183d8cf}}}, + }}, + {{ + {{{0xebbee76b1a1b9b2a, 0x4318d25986a97a3e, 0x9546c6b0c0226c26, 0xe63c6489727b5dd9, 0x1055511e8a4b8fa8, 0x0825371c652c4168}, + {0x255200043f4fab8f, 0x2e20b113cef9fa54, 0xcfae28f7a5cde583, 0x5e511be5a9148aa1, 0xb44fd9dfe030d75f, 0x0db4925190cc2b2c}}}, + {{{0x2f98092be1b16652, 0x20abfa566432271d, 0x2a7b4891ff22180e, 0x8894a35f0bc12ccc, 0x67f7bf713c954fe2, 0x199fca412eaa1e34}, + {0x59a59ace5522cc3f, 0xcf20e01ff0a48a46, 0x0acc3f5218ff80b1, 0x7f3d3a70ca8e44d2, 0xf24ba3a296db6be8, 0x049661c9ea873ed1}}}, + {{{0xfc5e8ae94498fd15, 0xe58cefcda6e2cae1, 0x494140419090d66c, 0xb3ae0cd92c3ddfc9, 0x8d199a9c91f3b42b, 0x02b3588400f88afe}, + {0x1fdcd0d86d18ae49, 0xba73dfb38cc5afdf, 0x0375d3e69e8ee3c4, 0x10697e32ec1c3c81, 0x96d2e3c990787557, 0x053dac5f8c1ebe41}}}, + }}, + {{ + {{{0xac0ac445fc7024cb, 0xc75afff481c2aea0, 0x32b31625bb8ad96d, 0x2531636fdd86354e, 0xab1c3e581c31cea4, 0x15548279567a60ef}, + {0x5a0df4c780edfa90, 0xa9d51d36e890b2b7, 0x2ff2ef666c6d14f5, 0x15610670bd200708, 0x41211e98bec7e333, 0x17081e64d3c7f257}}}, + {{{0xfe77cd46fb0f89ee, 0xbca8a01db917dffc, 0x47242255d734a9bc, 0xc8e7435b6830467e, 0x877808247acdd116, 0x0acb8e8c09967095}, + {0xc5c04956bfd4229e, 0x8eb32cc762b550b8, 0x9952e8761ee2f080, 0xe9c721d1697440dd, 0xbe27a3389b81ad4e, 0x0f65e2d5dda9c358}}}, + {{{0xc5acb423c74184a5, 0x4cff2a2e84715f8d, 0xce72f3e927c6dedd, 0xa310a38b6dd93639, 0x4bc940192ca80aa8, 0x0a442421c59c4bf4}, + {0x36ed99c50567c637, 0xeb3fd7d4ebfae424, 0xd9c13316dec46be5, 0x3918a2d27a5de1e9, 0xf2c3417762ca06a9, 0x15761a6effec6527}}}, + }}, + {{ + {{{0x56a89b92286fcd47, 0x13c7a8c0d5100783, 0xe9afdb9c191db580, 0xd3b4238393b06de0, 0x22daa8a671c2e8a3, 0x0a16e7ae5db3a513}, + {0xa0b5c166afbc5931, 0x298d2a96e01f5fea, 0x0cf6a27e9d73021b, 0xda3188078ccf010c, 0xa28dee912fc0b232, 0x07c2f7f680ffa478}}}, + {{{0x10d777f388130d77, 0xda19a519bf1b16ce, 0x98b22019449c58f1, 0xa0235c372fc29332, 0xdfee6dcf22852353, 0x08f4328803030ea6}, + {0xc45c5f9c88484696, 0x5ad085ab1d1e77ed, 0xa84fb2124c4b0f5b, 0xbf8ca1867650a859, 0x79e912bd4e9afa24, 0x16b41dd743e0dc74}}}, + {{{0xa369b6a0bd1b1840, 0xbe01fc6f97eb914d, 0x9e15e9707d1f1755, 0xe98060f2774c9271, 0x4d5cf15d86fd5769, 0x0c10b0280d9e75fe}, + {0xf39b90bd323c9a5d, 0x72cb8e16accafcdd, 0xcfd18d405d344ff8, 0x866ca80babf1c7e5, 0xde02e105ede8fdc5, 0x00ee7d809d5de726}}}, + }}, + {{ + {{{0xe1cebdbdad5229db, 0xe81c48b17c637836, 0x001e23de63309f59, 0x64a45d7ce6b33120, 0x365db0e5c5934b82, 0x073ced31c269e4d1}, + {0xf328f75a6d00b82a, 0xd011a27714ade08a, 0x3ba974e8eb0522f4, 0xbb1b7b115beddaff, 0xcf94a6adee0f359a, 0x02e5b346321743a0}}}, + {{{0xba693e35354a27b1, 0x87303f5e9836e3bc, 0x59a64cdb0be5c309, 0xd718f0eb1274f8ae, 0xd542d08c974595cf, 0x15ed841fd010d4f5}, + {0x742b06d77872a04a, 0xd7fb63d165886f7e, 0xf036cea5e95720da, 0xf957069615594996, 0x8c52eaa3812d9d74, 0x0b8b836b6b914b82}}}, + {{{0x1c69bac36133e625, 0x2c0e307a8b1988d9, 0x0bd0947dee49827a, 0x3d867c354d40c090, 0x67849af38131b83d, 0x170965a92616c353}, + {0x9f0c68a7c9449a1c, 0x06fab30c666fa00c, 0x4e09c2d802f894f5, 0x933dc154a69840e8, 0x5bd12f4b892fc026, 0x0228a60592c473a5}}}, + }}, + {{ + {{{0x9331cb0f70afa264, 0x87de02e469a5c1c9, 0x2ef0d1aa04837736, 0x6852a7edc81159ae, 0x55f7049257c27a41, 0x161e2722f74d81ac}, + {0xbf46dcf1d905d4a1, 0x4b043bfee0971c27, 0xb5f23135e5c8f466, 0xbaf95c1d64c68cf1, 0x0fb47c76b3dfe99e, 0x19b3aa2e3c76d82d}}}, + {{{0xd315e736d242f8ec, 0x132370d8fa5a78a8, 0xda7ba834a624cec6, 0x68b1f7619ea2e22a, 0xce6d6c6c64a7b5f5, 0x077abdc38cf6e048}, + {0x48dd2e412f330e24, 0xae4cf941ef30cb66, 0x3fa6cda8636d92f3, 0xe85e51840f902072, 0x6d6776bb30c5076a, 0x04ea58b184ef6955}}}, + {{{0xda1f35d8cebf5ff7, 0x9a8d0887ae608638, 0x78f5422228fd9d8a, 0x15ea56ececf41823, 0xca69bd07694fab33, 0x1262c0fcb941a519}, + {0x52f6ce12ac54330b, 0xbb46a84470b978d3, 0x0003d97902707ec9, 0x66c8a8426e60566c, 0x2f13f392c51ffb2b, 0x0c3742d3e540de06}}}, + }}, + {{ + {{{0x1850d0d023ef4269, 0x19b161e545349f88, 0x94abd6103684c2b6, 0x009564488fed7ba0, 0xcb3f73b949db0661, 0x0a5ef88a0705c563}, + {0x38b8f342659bea83, 0xc2fb69ea580fea56, 0xeddfa8761e75bd23, 0x18e7006054fb08f6, 0x2d498447d5179ae3, 0x09377631977f2f9f}}}, + {{{0x0b45cc6840faeee3, 0x054d568832c8cfc5, 0x6d2fb62f73a12001, 0x85f50f6213cdc8b4, 0x116a9952d81ad3b0, 0x049c29dc333a1764}, + {0x38c9375401b3abc6, 0x2ae633e3dd17f9be, 0xed868525f85fe993, 0x0bead87d9a92902c, 0xc0bb461b5b5d0852, 0x00dec186a94e80aa}}}, + {{{0x85e8867dac12634a, 0x2b84c1e10a497999, 0xd10a08fb73b49d84, 0x21b665038a9b14a2, 0xfbf30f654f81becf, 0x12bf09a268d35275}, + {0xc58028fbb028bfc9, 0x466953d4a729c040, 0x918c0fc88b995f44, 0x3434d4550f7511b0, 0xcea87bc125d9863c, 0x15e645f706884b02}}}, + }}, + {{ + {{{0x6dba88e6a65ef4ec, 0x7dd1dc30cc25d569, 0xf76fe435f66de6e8, 0x67e3fde0ef1a0199, 0x780693f03b571ea5, 0x07895f67398dc597}, + {0x9d6ebc2cb24cef93, 0xb66e7ef74d38037c, 0x77847ba3b254ccd3, 0x8ef8a9aa38666837, 0x258fe0459a596a10, 0x04b28caf8db12b4f}}}, + {{{0xbcac7bf664691078, 0xc857d6307bd71b5d, 0xd68b38c7659d66a6, 0xa299b5e9791af057, 0x3e9afbee59ac8fee, 0x130b985a72e00198}, + {0xcdfb1e8c8156f4d1, 0xa7f48355e98f207b, 0xa4b33317d9b1f05d, 0xa6041d162a14f60d, 0xbbfded6b54d0a539, 0x053d353e02c11e2f}}}, + {{{0x6ee249cafe40890d, 0xf7c70b65cba0a74e, 0x851257be5500cc29, 0x90e41329296f1472, 0xbb32b814ab2d63f3, 0x0c554032763ddc7d}, + {0x285a602ae1d7635e, 0xf7951b02d789b5bf, 0xa60a7f75f0049671, 0x1d18cc853c21b9bc, 0x3ab9d953e4cd6f1d, 0x11bf721b697bd3bb}}}, + }}, + {{ + {{{0x71febbdaa6ca6816, 0x8f873326554e4214, 0xc853bc4ebc5b8ad2, 0x41025c976b069ad5, 0x109e51df1e661213, 0x0093ed7bcb3a0f7f}, + {0xf4e67535b2948eee, 0x1131fdc448f0f51a, 0xff0caf1a6536f22e, 0xd88e38fd41941509, 0x978ffcdbfc759ef5, 0x1490028fb6a02282}}}, + {{{0xdcf31514ca9a6299, 0xb12c9176a3bb237b, 0x4341c035fde7b8d9, 0x9ac62565e558f23d, 0x05f7f2bec3e7a219, 0x08001353a8b88be1}, + {0x73ad2470671b705b, 0xa3d18e04fddc1de3, 0x2ebb7e47b03cc1fc, 0xc911bd6d41e694e2, 0xc066a6b742ebe580, 0x1791846a46f99ad7}}}, + {{{0x6b1559f6563f29ee, 0x937a5740b3cd42ed, 0xdb45b25f719e8437, 0xde23f8ca26841231, 0x7e1385c4f87482af, 0x0ad2ab425e892a05}, + {0x5d089bdfa648e7aa, 0xa8da8148c275f10d, 0xb2328633d937112e, 0x133808f35171e658, 0xdc07744bfdb4d7b9, 0x0b9d472c01bb2a84}}}, + }}, + {{ + {{{0x5ff3ef87c7a2649d, 0x2e9d9e93f5a0166b, 0xeb1352a0ef072ec3, 0x1a1fc17b0f17fbf2, 0x9be52df1f919381a, 0x0dd47d959be3682a}, + {0xc729c7965fa32f61, 0xcd0d793612cef86e, 0xbccb226946125b8e, 0xb5a03206c0f61a62, 0x880c06f3f589f065, 0x189f8b5363c2c447}}}, + {{{0xce33e817b7ed1c5a, 0x5af554f6a5ce4d9b, 0x60982175187c9c2a, 0xd6d19f9c80658508, 0xfbccb1ae3758aca8, 0x19fb251016fc2e3a}, + {0x4e02ac1178241238, 0x23a56f155e1b1490, 0xa329d1300cc7e14a, 0x7714572b7672e10a, 0xc2075813395db298, 0x05fab3d599e8db71}}}, + {{{0x4a4da910f905eeb5, 0xebe757b3905887b9, 0xc9ad53756b4da5bf, 0x8f1378426eeb47dc, 0x935858212b29f2e4, 0x08f4921d7d0c5873}, + {0x1607547b1a9f4161, 0x4e707b60fffe8b38, 0x4d4cb01feee299c6, 0x56bc10d2b8a790bf, 0xcb34287afe4b0138, 0x14c7ee1860f49d6a}}}, + }}, + {{ + {{{0xa66b89c6e35929bf, 0xf32c9b320df4b7e8, 0x250c152b0a96c734, 0xde8de2ab8f3213db, 0xde7d98f953136175, 0x14ec363acc8831cd}, + {0x287501b3f2d8ed96, 0x9b7847be7bb444ea, 0xe9bd1d317322e4dd, 0x6b3e6de636e67774, 0xccd0796c27ef4457, 0x024548c1be9cebf2}}}, + {{{0x6c04d5de994c912c, 0xcec6312cf700b931, 0xd8c9bbd785945b65, 0x3a6095a1c2375450, 0x4df8789ebec2caf7, 0x02ea30ae9fc0858c}, + {0xc75c372b7c74b6b4, 0xd7d72b656ec6b076, 0xd7e73e817fdc5602, 0xacfac9abba950366, 0xd66e1cce4df71ed1, 0x060720b83aece82a}}}, + {{{0x259ccdb58a37a7f8, 0x768c2254b6c8de9f, 0x4a999f100b23d5b0, 0x5753849ed9597930, 0x073973602922cad3, 0x11f6f84c9ffc1556}, + {0x25681cafe9a67af3, 0x01f32dca8654d55d, 0x962a5b513eaf9481, 0x478f5e119daddab7, 0xdaecc2c118f6100d, 0x179671a05ce91a31}}}, + }}, + {{ + {{{0x6c3ffc7313369a75, 0xb32196aa084ebf91, 0x19888bd7d5107653, 0x98e6f81205f7adfb, 0xb970085b6f3ad328, 0x1798566c199a1308}, + {0x9c0a43e3e8062254, 0x3f59c01632962d49, 0x0f710143caa5322b, 0x2fa91b07515b6449, 0x3545e83cd60f65f6, 0x0e4dbe21a246dc8c}}}, + {{{0x8705447916fc4e32, 0x1928db18e1ca59cb, 0x5ce51b5b0e12fd7d, 0x1b86c70e2ed61245, 0x5d091cf57972eb0c, 0x181a07a21729d3ea}, + {0xe810f6465a045eee, 0x5d24c02e2ed3eda6, 0xa51a9f6559ca54c1, 0x214691d4c7c4f39d, 0xe551c5bcea2052a7, 0x186b4bc37abcfeff}}}, + {{{0xa5c252cd575b22d1, 0x1e73290376acef00, 0x215b1ce5bf826234, 0x68258ffe7def6d94, 0xad2fe90e8e6437bf, 0x1746ee2d57e45ed6}, + {0xbfd607cd99620ee5, 0x3976d6eadf0fa770, 0xcfb973d1a4d010c4, 0x47b979579be581c0, 0x17db99d532a61b1e, 0x021af2fdad4ebcf1}}}, + }}, + {{ + {{{0xebd668fade13fcd2, 0x3e2c922d330352b0, 0x79cd063c5d10b02d, 0x862ac1cb862c5c83, 0xce58f977a3a4974e, 0x16035f69fe8f1bb4}, + {0x2222fab82efef234, 0xeb53583187d01830, 0x15248c60d4e3b9c4, 0x21ffc0e823a40ea9, 0x3ecd52783a07cf3c, 0x199571e2ff2bff69}}}, + {{{0x1116511a8ecc1476, 0xfa43b14a8703b829, 0xbfa8bb4f062b8d83, 0x7171baea3f24df2f, 0x9572f92b59ce17a1, 0x0c9b915dadebd560}, + {0xf7b6a71a70672f9a, 0x9adf6797517b8886, 0x36dfc84004db033d, 0xec756c3d48ac63a7, 0xb0814fd64a7313f7, 0x03025ca2565980fd}}}, + {{{0xa18eb889b2b25da1, 0x1619af7362e47f5c, 0x7bf3d3c073c0e4fa, 0xefc234756dbb3b10, 0x68f1f76152b10ed0, 0x0b279158fdffcb51}, + {0x157be52b457bdb46, 0x53f567cf928b4057, 0x2005b4941b7cf528, 0x3509db54f26c6e58, 0x05dfe2d5e0f888b1, 0x03abbaca4934fd54}}}, + }}, + {{ + {{{0x012a954b2ad9559d, 0x1f1ddaf9d3bc4e31, 0x1c5ff7da680f0c54, 0x28dcaf8faa1d430f, 0x79c0283e8ca04b7a, 0x06474c993df8a031}, + {0xbafe9485098e8841, 0xc115a3875d7dfd1c, 0x8d41dd32294d7e9d, 0xfe73ec5f63807358, 0x400cdf921f459ce3, 0x14a312db0385bc8c}}}, + {{{0x909e57e62b5b4b40, 0x4b43ba00d5ecde37, 0x36767820fafd6e95, 0x5f6ad6037be97a2b, 0xb8a13990c896259d, 0x108e7f5ce9579686}, + {0xf4e91c5443bd6f2a, 0x1c85bdb5f97b17fa, 0x729895a463f44da0, 0x579210a56fc9bfb8, 0x1ea15250b8e82477, 0x12ff3a74b9d5435e}}}, + {{{0x7ca0ffd56a42bc2a, 0xf295044af745b213, 0x6c5e1d1bfdcca444, 0x744fe2e669e310e2, 0x31bee835da51d873, 0x021c8d108ef8016a}, + {0x9a0d60bf2fd43f19, 0x44030f1ab45d1df7, 0x51a6bbcb2fd9283b, 0x50963ed44a739d64, 0xcdbdeeb5e048e2c6, 0x073baf8839ab66df}}}, + }}, + {{ + {{{0x4f46006fd7dcb1f5, 0x616b81c62f8bfba8, 0x8f00cf5c667b3af2, 0x6e9ea24893596bc1, 0xf6309491cc8b4a13, 0x15ed356fa0fb5e8c}, + {0x21db22e1f8e1ac7b, 0x14b18a90b0b75a47, 0xdd5c871316d4c3b5, 0x7d0fa4c7316cb577, 0x759dff89c484b665, 0x0e38bf0619158130}}}, + {{{0xff6979d9a6fdb7aa, 0xa0df35fa1917b8a0, 0x05836339e611b7b4, 0xac91b61069b7acc8, 0x7d3e4d0a21984d9f, 0x1449eff5559d2368}, + {0x639ed5ebe89f21ef, 0xa32c26b3a7a7345a, 0x87866937493841c1, 0xb7959e61641ac59d, 0xe5edf8df3dad817f, 0x15ecabb11bd7e61e}}}, + {{{0x83680d16f3389724, 0xe496c375a64d1a09, 0x183fd9794d1c7061, 0x2cff4fabf073aed2, 0xdce471cd77f32ee5, 0x0d8431fdb3b8bb86}, + {0xf7f60ea5f2f52610, 0x917f6fa5cafa335e, 0xdb1dea5fa8376781, 0x2af55ee87acb589b, 0x349060ae34540f2f, 0x0419e187131d6a93}}}, + }}, + {{ + {{{0x4bbc17de2186551b, 0x80137952b2b572e0, 0xeb5b3ea6eb58448b, 0x669111d5b3ea4bbb, 0xd7eaf0de5a8df3b9, 0x1354d01b68ae77b2}, + {0xce62e1585252b636, 0x26d8f8ccc6201c2d, 0x23d165c880a9cc3f, 0x7feab6e39499e22c, 0x5fa61da0db4a25a2, 0x171534163ccb5ef8}}}, + {{{0x81f86fecad0afb58, 0x21ad6ae17d05331c, 0x67e9a2d7f4dd72dc, 0xd1a1d21c1c6e26e1, 0x259fc2d79c303b28, 0x02ce45510bfc995f}, + {0x38b9b6f5e119f091, 0xc23afeace0c3ec74, 0x7ae9b236d205a72d, 0x07e38723653f28f8, 0x51dc529cef51efca, 0x11da881500afcb75}}}, + {{{0x5744220d39a97503, 0xb043d76c46d0a966, 0x8a15412ed95f7ed6, 0x8a0c9927f14ec40c, 0xcd0599d2f534b9e5, 0x08842b0415d24149}, + {0x895a94f58932805c, 0x8b2e933404298d88, 0x993598a51736b52b, 0xda44bf66a365c1ce, 0x57aaee72d09128b9, 0x0c5ef4b969bd72ed}}}, + }}, + {{ + {{{0x7774c29ee8fc5bfa, 0xbc5226a8d2a98b55, 0xe3b25ede7bff8dec, 0xdb783d9a6177fd4d, 0x34521de2139cf858, 0x13a47abd3066bdc1}, + {0x80c069a96edf1161, 0x6a577d8fc9944127, 0x1f26bb387a3092dd, 0x3e8f21438d71a1e0, 0xba41129983cd8bc7, 0x07cb7638dbce4419}}}, + {{{0x6c3e048a4fceb146, 0xe5f76fb3d9323656, 0x925ee53bb4e3548a, 0x8884fdc193e40044, 0xe65aa08aa2104ebd, 0x070f2e074254150e}, + {0xd9c387a32826b77f, 0x9e46935eaceacdc7, 0x51479f7c3b0c3bdf, 0x19268685d9058a00, 0x848094ee70415ab4, 0x193d41d28ebe9fbd}}}, + {{{0xb6a9f2095719eed2, 0x5c44fc1883a2b036, 0xbf151b6ca46474d3, 0x74f20298980a8ef8, 0xd2febd6f41210997, 0x0cae18c9f959e837}, + {0xada0a15a030d9e68, 0xb1528ba1e98af8bf, 0x6c1b7e5db1406e33, 0xf3b8456f647c5db2, 0xd3276250b8433bfb, 0x07e868fe91cc6f69}}}, + }}, + {{ + {{{0xd48ec385e3fe2948, 0x76bcb305567ad5bf, 0xfacd1ea566f8a26b, 0xdc16d00aca6f939a, 0xa196a00e4221c035, 0x0e8b9f666e20adb2}, + {0x76c440ea3ba3b432, 0x361b24f0c51051b3, 0x40d9f97ed745c389, 0x7d0f3124b5b3ba9b, 0xc43ef29216fa7e8f, 0x03ff2caacb609467}}}, + {{{0x4be2e79a53c12326, 0x69f1781929e195dc, 0xa11b90dfabf7a0dc, 0x10efe8a636f19141, 0x77468e44f52849c9, 0x16cc53f13e202187}, + {0x4704626dcf23643a, 0x62dc4c8ca063bf89, 0xc8bcf87d94c4083a, 0xe670e87f49fa5ba0, 0x8b8d90a8a74a6582, 0x19b432b8c320ac64}}}, + {{{0x9224345cf4dac845, 0xf7e7f15e9e2e8c54, 0x53f8982c1d98763c, 0xe53a75b2d4dc7ecc, 0x2ddef007a3c69fd6, 0x14fb7102ec1e7704}, + {0x5e15498b27542bfa, 0x4332bc353fcd6d0d, 0x09581b5be6dc4483, 0x7dc32d2755f98eeb, 0x3d7df21bcdd4ad34, 0x011cc25b00ca4678}}}, + }}, + {{ + {{{0xc98e3f9793fb7dc2, 0xda768cdf44db5785, 0x8275c7f587a1a3aa, 0x6b12f3fa8e0a3b39, 0xb74c83718f0f898a, 0x16e4414701b1da51}, + {0x53e72720b3155909, 0xf12a37e44e01d3c3, 0x37bbb99d783cb20e, 0x86c4bb7e0fd3a264, 0x5fa5d3b1bc3d174d, 0x108ccb9a39384534}}}, + {{{0xf8f4bcb65a4602b6, 0x4e410d9c36071712, 0xa6943d9c15654d60, 0x101e9db72e381a59, 0x1b006bcae84c607b, 0x06b9725046471599}, + {0xbca2269971dfb327, 0xe9597b09d6ea7d90, 0x2cd5460d51268bf2, 0x4725e70c051924b3, 0x163ab43fd8f7afd1, 0x1008bc997ced5322}}}, + {{{0x1a49932def600b88, 0xfda311cfa216e398, 0xda44fbfaa7ffce7f, 0xbbce4a40ede5e696, 0xb996b24f076fe9af, 0x04aae7814a73377c}, + {0xd0a939f98618ad28, 0x30e23cae5d9caf6e, 0x07b1a3ebcd3d81a3, 0x76f15cf1b049478e, 0xc9845df0ff244dba, 0x0932fcf77bf512a3}}}, + }}, + {{ + {{{0xab3c352cb7903fec, 0xf5c732941190b463, 0xf01446ee660f883d, 0x6ddd55119c08ec81, 0x547f3ccd7b2c5ad9, 0x02d43772d2cde2cf}, + {0xfd1aabdc651b1194, 0xf7067479474bf8b1, 0xb9ae1be7ac942224, 0x7b824073dd154cf0, 0x508efc3b8bd7b320, 0x18f28b7f85db4b51}}}, + {{{0x0067be51ffc20973, 0xb4362fa46081d731, 0x3cd73de04b51509b, 0xc12a552343b90c3a, 0xdea3c5e8bd1960aa, 0x07f1f3b54d9608f5}, + {0xd3617fbde5ba17f6, 0x6b1e01286f28f19f, 0xdd663f33eefe958f, 0x540a19e46b343137, 0x4ad7b01156e8add8, 0x0c5e0b9b5744a190}}}, + {{{0xf986d78320f43fe4, 0xc9e93381a135b4af, 0xf1dab02eeacb337d, 0x657eaf8c2edfcdbc, 0xae67425d8c53cddf, 0x0e35b504432f6d38}, + {0x1dbb9f1d48127e43, 0x443965d0f31805d9, 0xe28e6278c81fc3d5, 0x1f3040ad1b9a2c9d, 0x0df2227c9155c895, 0x0059dd75ffbf0a7e}}}, + }}, + {{ + {{{0xd04da9134cc5c08e, 0x13645069a81df1b5, 0x8a3f034b224f981f, 0xdd2d7c6be6e1495c, 0xc1501f263cb8fe7f, 0x104d92a21d3da84a}, + {0xcf5bb6f97fc9c4ef, 0x524000c916964603, 0x2e6a94cf54c71e54, 0xa17b04a68dd209d5, 0xc768dfd3a7fe9087, 0x16fa6fd3bedd5c89}}}, + {{{0x3aaebca4858aedff, 0xe9de6acc7060006b, 0xab009dcb435fcf62, 0xd87ff783320aa26c, 0x1108e98f1b37079f, 0x0b7701738bf6e611}, + {0x682bed55c50f0ae5, 0x842cfd54608b1f96, 0x9f3c83542f5ac0d1, 0x4bf6ac01377cc8cc, 0xaef9feb12b27e372, 0x0b4533b57da69dda}}}, + {{{0xe514cf3c73db5962, 0x9ddc5d8c845207b1, 0xcd8a1ae73b5da609, 0x778d8f417285a33a, 0x84259c69c045d0ca, 0x0c6ccd43c3ce44ca}, + {0x73f49a3d6a3764c5, 0x16ff80f7242705f5, 0xfbacf265a129a412, 0xc6b4981c55176ade, 0x77a7e51125933910, 0x152d461791c7d67c}}}, + }}, + {{ + {{{0xc7dbb8cdfbe835ce, 0xb549a9f7c83e99d8, 0x30e7f5b41b6af205, 0x2b490844d169228c, 0x0c4daaeaf46b76c6, 0x043bf7eb5d78e277}, + {0xbe81b1da35a9f395, 0x073f9a57fb17621c, 0xd6b11f7b8d4d6e51, 0xf69e12fbd0b06f4a, 0x10a61711b19a1f9f, 0x0cec16150832b8ff}}}, + {{{0x21710ce5015609e3, 0x464aed88c6fdaf1f, 0xa5068976a77041b1, 0x2667799b2bd85c51, 0x52b749774d313d32, 0x15fcf37552956115}, + {0x005252f37966977b, 0x241e4931968f2e1e, 0xf32f1c8952fc0343, 0x2596fd980af20b88, 0xb4ca23c95c7e46c4, 0x0c4f149775ac8548}}}, + {{{0xce0cef0821ca3dbb, 0x0e9394b9eabc6ffd, 0x1298dfecb20a6cbb, 0xc688e544f953710f, 0x1b1fc723172021e1, 0x06d79db36ec9348f}, + {0xf48dbde53a87e93c, 0xb19eb40e7b5bd5a5, 0x4274912c2188598e, 0x0b09c932fdba05be, 0x4ca8a33e998a0da1, 0x09d7973cfdea8dcf}}}, + }}, + {{ + {{{0xea55e6bc877330f4, 0xe2a93aa8e6590941, 0xef5662699df3cd11, 0x86e5e28f8212c006, 0xa5a04634af641c92, 0x19d9232526b8e61c}, + {0xfdf4c98046b98efd, 0x099278b279211bc8, 0x73d2a2a2e7d6bed3, 0xd85ed3fedd92aa41, 0xce0bcd94511f3115, 0x18a3e6af99cc1c78}}}, + {{{0xcb7c4c547f059eba, 0x9042a754ded50276, 0x57f762e86bd008f2, 0x755a2aae285c340a, 0x07923dea80cb941c, 0x05fa4d2ddc6a7a15}, + {0x638cc6bc9353b473, 0x9fc22e668b291dd6, 0x3a8f9e22370aa02c, 0x3fae99fab50844c5, 0x540214bb1d7bea0f, 0x184926206de7b9f8}}}, + {{{0x50a7d6c7a5a857f6, 0x043ffbb8db7ce832, 0xa8850516944f6eac, 0x7449c03deb84aaec, 0xc993a8c4634d4a18, 0x0fd94e76c1c2aea9}, + {0x51fcd553fe0c682a, 0xa97bca9ab1268c07, 0xab2107b048604630, 0x2756fb32738d805c, 0xa463992135258941, 0x15c574da37704a1b}}}, + }}, + {{ + {{{0x040f531d1984af16, 0x1550b38eb91c7df9, 0xc6c4331f26ffa3d5, 0x07959e0e07db97b9, 0x20b3be3a858540d6, 0x0999e51d8371ac2a}, + {0x0e6543eb3674e42c, 0x813c2f8b5d4bf90d, 0x96a9dfc5091a5f66, 0xdda45f5081877f7d, 0xd38c8e5e19840551, 0x13f1c9b6b2bd4cfb}}}, + {{{0x9d4eb16fe463e89f, 0x4cd2983e2e012cb6, 0x14a8b79ea51811b9, 0x1786ab5e1446d0e3, 0x67022bc09915db05, 0x04fa951e2111b306}, + {0x7221bcdadf7770d8, 0x08d4de052a1737c2, 0x743b61967ffc30fd, 0x53ce07d714d0ddd3, 0xc239682fcf31312f, 0x18cbada0129b2a49}}}, + {{{0x3cb46234275397ff, 0x54d53039cbadc107, 0x6e6d3cebd750c881, 0xe1aa429178857c2b, 0x04873ea29f721b6e, 0x077b1a0feb10a32f}, + {0xbd4001de0988e389, 0x1541729edcc05bb8, 0xa6749319874bd738, 0x884101da1019750a, 0xc6c30fc60f295c63, 0x00486e285a377b09}}}, + }}, + {{ + {{{0x4fde628b772a10e1, 0x8e4c48b8067e0ba1, 0x9a2bce09ce96de6d, 0x40af1a7029bb881d, 0x9c3297d6525ac35a, 0x06945ca737f7c385}, + {0xea7a4a456ebb4423, 0x8519717a7686cc75, 0xee00b88734231f96, 0xb12baa22df980de3, 0xb5148971705ddc83, 0x1865e88d0d827b83}}}, + {{{0xb5d1fcb7a469540c, 0x18ff90394009a544, 0x9b3506bf36195f9c, 0xfb7f118f7ba85052, 0x4832da3baeca1be7, 0x1898c89d00fadcc9}, + {0x323778d50e57a592, 0x3215ba8a57adad45, 0x68ea2a23de6052d2, 0x0e71f5248b322172, 0x3acd3c1f68a15d8a, 0x15a6215738d1db10}}}, + {{{0x8b574251ae42394e, 0x0171798fe982e42a, 0x9c0a3dff8485784f, 0x6fe0db2def270039, 0xd7e8c3752cb81664, 0x0397b114ec183a25}, + {0xe2ebd1414ee8170f, 0x45285226453020c9, 0x917338f24dd8b54d, 0x796764cd511a38a1, 0x66deee75fe631812, 0x0cf7257659686176}}}, + }}, + {{ + {{{0xa9e9f67a84d79502, 0x58e947bd25f48425, 0x4e9cd72a3c82f9c3, 0x11a743c538454199, 0xfdfd8c3f3306f2e0, 0x0634f8b01b881091}, + {0xeccfd1bbcefe0d8c, 0x72ea70a84131bca8, 0x1f799c3956228ff1, 0x3cb8a135448efd71, 0x24045b05b60d08a4, 0x16feaeb69b3aac8a}}}, + {{{0x34139447f4ca3f53, 0x66070c368feca318, 0x086620287937ae25, 0xc1cbaa231ce67494, 0x1e570a2167f69c09, 0x0d3c1c3e96849ac0}, + {0x2603b64f467aaf68, 0x0b290c1b7e1a420f, 0x82e88f50580f0c56, 0xaba1e298cdb94eec, 0x4f3a2a9d704fa110, 0x15415926fee69d0c}}}, + {{{0x77f356fc232fcea5, 0x81997b1e51e75a19, 0x0a212ac453f09c43, 0xcb835df108c8f71a, 0xef86181e6110bc29, 0x134c50ce2f690d5d}, + {0x1c532d507347cc7f, 0xfdaa16b39696762a, 0x24b90dacda7dc04e, 0x740c0ee121bfb763, 0xff6027e30a461aeb, 0x165e6e1d60abb1e0}}}, + }}, + {{ + {{{0x2fd677de56636cae, 0xe643f49ab74566b8, 0x000fc809049f5534, 0xe2fda467600a5179, 0x9abaa7094c22d849, 0x121a9f7761b65dae}, + {0x9337f90db639666c, 0x3cfbd35635a5c2f2, 0x666994649a0f5480, 0x767c06a60c51d119, 0x6ee1b1136e992ff3, 0x16d34c93a74c580a}}}, + {{{0x092e9162e1db21b2, 0xe0604a65b210fbac, 0xfacd55bf10e7a820, 0x9f846b763c35eab5, 0x1fa279c79ad62960, 0x187284d92e15e26d}, + {0x66bfcbff9cbc2a5b, 0xe280b61342d1fde5, 0x26fc5a71ff10700a, 0xf157a040d6c384b5, 0x72935605abff546f, 0x0f4ebbab97693f64}}}, + {{{0xc3035c11f7961989, 0x79494afffc9eecf8, 0xb3a8bb598ae8862b, 0xbad9bd613dd0bd19, 0xa094f4ebf2b2b294, 0x13662ae9bbe303ae}, + {0xa2fbcc884f9ee8cf, 0xbe4c765b7888b0a9, 0x95d09526ca406c54, 0x8320dcd7102a7ef4, 0x12cec265e035b7dd, 0x1049b68cf5db5743}}}, + }}, + {{ + {{{0x030d14d258ac2331, 0x182cce476684c496, 0x745ba790938a609f, 0xcd4ee2c30bb918b6, 0x1bdda1cc35d26024, 0x16b10cdbd6319f32}, + {0xdc01227c1bc9a55e, 0xa46cda2b78a11939, 0x205cfb5b3e86b0c3, 0xf0c9e1768481a062, 0xff06489ae4fe8bb4, 0x0016647c573e4cf7}}}, + {{{0xa1875c64592230a7, 0xac19e641f8cf6cc4, 0x4d191b7a06755386, 0xd4fd63a589a5b7d5, 0x5faf6598e00404bb, 0x0541b09d037578f7}, + {0x84c3ce1226fec8f0, 0x39d8abb918c231ab, 0x514773ecd2b4f944, 0xdb4dbf23d180b48f, 0xaf4390c2edaca81c, 0x022b33505a8ae9d6}}}, + {{{0xc044bab55969740d, 0x5b08c31b6be64a5f, 0x3d59adf29dd8a5d4, 0xce6335d600d3f4fe, 0xed446fe327284220, 0x13f14d0459be36a8}, + {0xe83ef8cd1dcd519f, 0xaef2363bf1e79a49, 0xd9376651429d6397, 0xa3f99beebb717cdf, 0x133d057d6db1fac6, 0x0ead2e910a3ecba2}}}, + }}, + {{ + {{{0x1ff87898ced141d8, 0x79d6494b9b2b24c0, 0x99e6f18526b08bf3, 0xa3b57b546235f3bd, 0x49f5e7c775b7b062, 0x01a324710ef8671c}, + {0x14be3e38fe66a8e7, 0x76f68c4ab13ac975, 0xecd33b9facafbfb9, 0x048205e890d50760, 0x2665e8db42c8fa34, 0x0b30db6c343e5598}}}, + {{{0xdeb14fe2033d0beb, 0x0106094180482833, 0xc20f90caaf08131e, 0x947aaaa897609ac5, 0x65c538f17385f9a6, 0x19f912eb1530f946}, + {0xef8052b02e1edfaa, 0x0541484045da4b43, 0xac5f37b1a7441e8f, 0xfa6578317b4d688b, 0x819f06636592a680, 0x0bb4f2c34ed92b64}}}, + {{{0x8aa1fe522545be5b, 0x79297b8305ebdec2, 0x18ad24be9d6ff29b, 0x4614353ec7fd7445, 0x6de5104ff8aebd2c, 0x15fd357c75488237}, + {0x480d0071e14a2b9a, 0x50fe7e364991d381, 0xd4c023a8c352c6c8, 0xc64b9df84161cb88, 0xc1ea74d00015de42, 0x16ba6a3b9062a3a4}}}, + }}, + {{ + {{{0xd8645ac4c082fa74, 0xe7f713b50bbe3d71, 0xa0d5c05c509b2ed5, 0x030952b3d8fc5876, 0x5e68a733551e3e81, 0x0ae31153db8af4d7}, + {0xc4a1170b9db52a99, 0xf9320ad18b1856de, 0x0748d795e7c8834a, 0x24c904422e5e04cd, 0xaca71671117df0f1, 0x09d5ec6cb778fd18}}}, + {{{0xcae349bdcc6e9198, 0x6757ad6779ac2be9, 0xe7ef75388491f12d, 0x87cd3470021f0ffb, 0xa2a3776dd768b01b, 0x00a5de0ed5de97e0}, + {0xc30463f1ea95f7bd, 0xa7e3e534b99940cd, 0x8a75d416938f4566, 0x0219c645ed8faed8, 0x034eaa3752beae77, 0x1925500fdb997264}}}, + {{{0x9a7dc7efac57d439, 0x0fbae240fa86808b, 0xf6239cc4fc5719b8, 0xc976cbb843d96dc1, 0x3c6a2f936a7f067e, 0x0a10a27fae3b733a}, + {0x6dc70a7b0590ea5c, 0x80532384cbda4110, 0x91b8f05e02e9bfe5, 0x73814e1ef728bba0, 0x6789f2dcfb38b927, 0x1418d64da5c39c3c}}}, + }}, + {{ + {{{0xa50d0e584e3d5491, 0xa3fd0f055ea4677e, 0x7f551ae60b261d71, 0x92a1a6eacc690d33, 0x712ea8fe3f45b63e, 0x0304d1e65eda67ab}, + {0x1834768b75408ea8, 0x07b594955426bfa2, 0x0fed66109e5f1a9b, 0x2a171c0078e9d9bf, 0x35cf634a126aacde, 0x0ceca829a77233bb}}}, + {{{0x330abe4eb7fa41ab, 0xa0b8c9ba2aabe860, 0x748d42ec350ab2d6, 0x6688d65ade29c4ea, 0x5c1f35d59c483b6c, 0x115e7b28d6abbe7e}, + {0xa07d3dad3f998aca, 0x1ed077ae1c1f22ff, 0x0b54cf4c4ddc7db9, 0xdc3d2ebf0f9fe0a9, 0x8c2b6741daa546c4, 0x0fe0809e2954234d}}}, + {{{0xac99f2e0267b6cb2, 0xfde4f1f140eb90b8, 0xa72b272f641c9681, 0xb4c462db1f3b7f17, 0x30a7c2d4b237d284, 0x0ab65d1f6beec163}, + {0xcf82aa6c788c62d2, 0x6b0657dea8ec635d, 0xae872f508e353b51, 0x097de45d4a07fd7f, 0x8af646a99a735e1a, 0x1308f8bc81a875f8}}}, + }}, + {{ + {{{0x78b86ddcd2526d43, 0x8ad84620e76d255c, 0x8ad56cbd57624b1e, 0xd4742d48d3e59d5f, 0xbc551cfbe08827c4, 0x0f6ae2d59a1a3884}, + {0x1db10995c02d8a8b, 0xa59c04d30c86dfc1, 0x11a276664299a14a, 0x276a90db25cab4f1, 0x51dcf5ecc7361efd, 0x082ece1c2673b540}}}, + {{{0xbf917c7815855bde, 0xc22c96e636bb9755, 0x7c36e46122b5f68c, 0xd82ba6008e6023ec, 0xfac461f607d1318c, 0x060193feeb23430b}, + {0x83bdaface4e2fdfc, 0xedc1f32a055ec7ec, 0xc22ceda51488ae9a, 0x6179d480dc6aabea, 0xc45b50d6eebdd9d2, 0x19f6605143d9f857}}}, + {{{0xc10cf26c584e5d20, 0xbedde1c9f75151c3, 0x4e806e8bf40a68e4, 0xe0f731926523dc67, 0x49a81ad405f4bdc3, 0x0525a7c9b119e20b}, + {0x419da160002c1487, 0x3420cd5bdd81800d, 0x3084c0bbe28a851f, 0xc7a7cb5cc9f8883d, 0xfe023ab1f1a4fe7b, 0x11ce2a40070eccaa}}}, + }}, + {{ + {{{0xc2ff09492e9a3653, 0x6426b4841498146d, 0x5aac8d0db338f0d5, 0xb6ba71459593b35a, 0x2e68831694274b55, 0x140a3e1296567d68}, + {0xde14ac1b85d52ef6, 0x1e1fd94318c2ad58, 0x5467f1caacd4a1f2, 0x3d83d9dddfa6a8d7, 0xcc6cf47ceb879787, 0x145cbec0c6fdfec7}}}, + {{{0x1e842b4969676c44, 0x40441e88d8998604, 0x0ba35a2dc03e77c7, 0x71b45b3ed69483a1, 0xd689341219ff8301, 0x157552c2f255464d}, + {0x6f5e8dd3b354798d, 0x4acdb3499c7a3229, 0xaf93110880e6b5bd, 0x52594ab088cea13e, 0x8350483be77b7954, 0x0173a9339ad7f0cd}}}, + {{{0x49ec6e7923184c92, 0xd35fdcecc80ddcad, 0xb94724796bdbafc8, 0x792c8161aa7a7bf9, 0xb08e6499370f6b01, 0x054d4a2c14c65ab6}, + {0xaf0a4cf56d5af736, 0x0ca8bdd654c1841a, 0x4a535af91adb8a14, 0xdd1fef47236efc45, 0x6cc8ad5e2bfdf4b0, 0x0f2f9bd782cedcb6}}}, + }}, + {{ + {{{0xf324a4c5574c95b3, 0xb566e10f0d778ac8, 0xa20f8ee8592f389b, 0xd6847b66d45141bb, 0x561e2bc19c471dc6, 0x10b4772781718590}, + {0x6550125501baaa04, 0x5890afe3ab97255a, 0x8904e2a5c7fe4efa, 0x28a17700d2e76995, 0xbb5b47d35760373c, 0x0efd3dd9489641c1}}}, + {{{0x6b77177f5841a035, 0x76be81278b4d40ac, 0xf9c2cf60bab8af6d, 0xf9c57b08b8e7483c, 0xade1bba14029d1df, 0x172bf56c5d26c392}, + {0xefef7218cdb1b82a, 0x5de5b6564bcba452, 0xf70a0b5f6eee7f5a, 0x2e07984b217aa5db, 0x50d7dce849f73606, 0x0c06a25d6f14fda1}}}, + {{{0xba9915b064300121, 0x0e0c5e35ef4d2574, 0xef433aca307d0aa9, 0x4c62be61110772a6, 0xef649be216c3835d, 0x0d05c5ada3e9b302}, + {0x78241a2efc9029bb, 0xec68f08fd66d8c4e, 0x9fa877f9874e9886, 0x6582d9b661001f0b, 0x22fe25aadd54fccc, 0x041f6597c313593c}}}, + }}, + {{ + {{{0x0c6fa4336071ee47, 0x59a3660e891219d2, 0x9b841725d76d7537, 0x9572a93e74551fcf, 0x103c3f159e6f1c27, 0x0021decccdc4b7ce}, + {0xe5dbcefc4a0f29db, 0x6270886c2f416701, 0xcfb40f3244c360af, 0x0e3b4bbf82fd31c4, 0x34ffef1229867947, 0x0cf931b9978178ee}}}, + {{{0x3ee1003881940b70, 0xee6e1c954d1053f0, 0xcb8ac3b4783d22ee, 0xcc519e3b515a7b2b, 0xe69b0d6341829488, 0x063d116015152a46}, + {0x8f35f3fe7c6af2aa, 0x1b21ac3eb7afd5e8, 0x1e387566b52256c2, 0x2824455e80e0e2a0, 0x1982119be9124e25, 0x0ae50c6da0a89efb}}}, + {{{0xd8b9043cc1230b7d, 0x603f06c654b5f2cc, 0x6ef8d66262eb074e, 0x85595b1242c9b2b2, 0x81d6e84f702b9391, 0x0c57ec922b546a28}, + {0xb8e67510e8cf1643, 0x0a2837662892ad44, 0x42ce94e2cde94fa2, 0x6eb388f8badeb135, 0x6a32c17738aabacd, 0x07de47344be9894e}}}, + }}, + {{ + {{{0xe17c9c312d54b564, 0x4c2ef71978879bb8, 0xe494148ba30e0b97, 0xfaca7bd5743a3cb0, 0x98a40172dcda76ff, 0x05620ae16a78af0c}, + {0x24eaf06a4aed6b41, 0x78e68e8cbd40659e, 0x119195ab353902d4, 0xa93001c9c3382a9b, 0xdea211dba2968cf8, 0x0f0d12ad72eef5e5}}}, + {{{0x3dfbd901476d80f5, 0x77438418c7cef105, 0x45a3fa70dc041011, 0x35cadc7ab158c30b, 0x7df97c1e282e8120, 0x04c060a48631868c}, + {0x9ff7af8f5a035cfc, 0xf484403064bd557a, 0x87eebc1680261261, 0xa89a7eaa0f3ca1bc, 0xe2ddce24f24fa831, 0x0f15b6ff29d6cec6}}}, + {{{0xc45d20d423e5ada2, 0x49a9b125503d5cf0, 0xf602ae8f399c0066, 0xd303b963740b37ad, 0x4e2421b20864b40b, 0x0787f2f5a8306952}, + {0x0a72f4a8f1f984e0, 0xc3e621215463a50c, 0xf9785aa8ea31f6c0, 0xb5329278fb820871, 0x7a89224e4bc910d6, 0x110871eae7a49b80}}}, + }}, + {{ + {{{0xfb5d03e18e1b559b, 0x49a36cd48d90ce85, 0xe6939b2d50eece92, 0x0d770c374b919aa0, 0xd2e30b3969895522, 0x179cf5068c8ba750}, + {0x39fcb25303018f31, 0x26ad3456415e0cc1, 0xc51e87a248a78e4c, 0xea72c5efe41ea15c, 0x4164ecc2f4556beb, 0x146e88d95d7c47e5}}}, + {{{0x5059261a016da1bf, 0x0667ca99e9cdc253, 0x960b625f659219f0, 0x117bff5160736aee, 0x4558ea7ce9276616, 0x0f5db6cf709a0fb9}, + {0x827ceddb11a7de8e, 0x70c3e1d69115281e, 0xe9df1ffdb2ccb225, 0xe574f01e81dc1f73, 0x29f091b21056872f, 0x08474a2d37d28e5c}}}, + {{{0x85bca3888708c7a8, 0x7b19235df7524926, 0xa18e9bfbba01891c, 0x464dba134338e23a, 0x1f3fe23a13dad1e4, 0x0ba6f79677619f1c}, + {0x00121d993174afb4, 0x3d5e052126c1fda4, 0x02626c42237e55d7, 0xc5004fcbff4fa65a, 0x737fc09545593290, 0x0e635418e06bf785}}}, + }}, + {{ + {{{0x83bfc76f06e67710, 0xb9b1efad8f8cf511, 0x5eb529d8152e7378, 0x43e643d3a1053f0f, 0xe8f141b4b35f34b5, 0x15b7322d07a9982c}, + {0x5b114376a7b10e4a, 0xf1b4ee197395a8ca, 0x4c4d85664004389b, 0xba1da23a4cefa957, 0x60817b93ff0af8b6, 0x071c24945047a70f}}}, + {{{0x47e23feabdf0db96, 0x157c786fce5683c7, 0xe335b878dfc09d74, 0xe3c0f147c9dc279f, 0xd855b92e0b67f6d8, 0x0e6e67c01769ebb3}, + {0x90fc9ae548f9b4ee, 0xd90eb07413a0bfd0, 0x59b93622ff46be99, 0xc8cb365186be9e2f, 0xab519f7d33d655af, 0x0c356c1a4c1753a4}}}, + {{{0x45d75896470a0b63, 0x79360ba359262f05, 0xff8d059fce10d202, 0x600ea7fcc5922aa7, 0xb1a60d00ff67edd5, 0x11b9ef21a849bacc}, + {0x5d1cc3835fb01e67, 0x9165541883d4c3fd, 0x4a9526f495c82212, 0x3dd1dac37bf8b827, 0x3abe881ad9084062, 0x06714694f70ec943}}}, + }}, + {{ + {{{0xcf2c3ad387c188e9, 0xf7f0b20aa2f04831, 0x3ee7154e3931df8e, 0x0f712f34e0819b8f, 0x2f9af564ec375db1, 0x17aad06ca5f893ae}, + {0x23fd29dc73f87bbe, 0xf49a3092afa07da0, 0x9622a406cf115994, 0xea9fb6e98959d222, 0x6865d6fee0d86850, 0x00239987555baa17}}}, + {{{0x2f5162245f3230ff, 0x738df4a549e240ec, 0xe413cbe50a0f1ce9, 0x4e7d3aa6912afb37, 0x0852850b73faf48d, 0x08e12f36a38f514c}, + {0xcdb8b21c07d5ea48, 0xcc5b6177546f4f09, 0x5db22a7676f56732, 0xf3a826199e2469af, 0xd4e4014411edda86, 0x193a7bb07d607b71}}}, + {{{0x73f2b6835d893604, 0x3ee0e8a36f23c374, 0x79df0ca4b2c4d5f1, 0x7773e1b5f7876c99, 0x1adcdb7bac4ca399, 0x19298c434892e192}, + {0xdc93f858e60faf91, 0x8785e14f2ed9beeb, 0x6be740b37275e3af, 0x69bb86f034482a37, 0x67f472b951084fb4, 0x19829da0d945324a}}}, + }}, + {{ + {{{0x931c8bafae430278, 0x4aefda2c1a8ba47b, 0xddbfcc30e849f148, 0x904f2afd9a397db8, 0x810d939234df6634, 0x14b8f523fe0b7acd}, + {0x02d7570e8d2666d4, 0x53353e9d3c182484, 0xe012c989934a917d, 0x2d19bf4216aad225, 0x82f5fbab112a2b94, 0x140bcca8deef2092}}}, + {{{0x930c6eebe1198335, 0x4bd2f584cdde5916, 0xad8d28fa826e7e50, 0xac4e66a225799a1f, 0x155203f750a4fcdf, 0x05426b72211207b9}, + {0xa83a6bfab7d2100c, 0x6d35706ac329fbe5, 0x5e715d0e7fa21c80, 0xf37ecd5f5560165e, 0xb2f7a66df168c4e0, 0x0fd7f2bbfe5931b4}}}, + {{{0xee49f98363a5df98, 0x1a3e9d563040f1c8, 0x7d574093568cd870, 0x408436e78ece55f5, 0x502dfd6cb10c2b03, 0x161a94337b771049}, + {0x08842eb71aa179b9, 0x6e853dc8d216bee4, 0x9bb353d956f44d84, 0x1dd1c54c1fb99e0d, 0x98194d289d712e5c, 0x076ea63e300df4c3}}}, + }}, + {{ + {{{0x7abcb71170dc8a40, 0xf2d321ca5c2492b7, 0xabad006413d9f2ca, 0x740c42c2b8ce25e0, 0x2d24f3be9f664531, 0x0aab9b8237be669d}, + {0xf7a7c747ff033df9, 0xee7dccff4ac2fafe, 0xc77829e614abc8b6, 0x78708443879430fb, 0x013279b4fb6651df, 0x118462f985a0871c}}}, + {{{0xf3d494c3776c110a, 0xccf54327891ea0db, 0xb72ca687b2189dc3, 0xbaafd00ee450162c, 0x6a5cef51b6801397, 0x0111df36ca9c1046}, + {0xf8be345cb48db182, 0x324db2d913403384, 0x5ac52bcc3195feaf, 0xf90777e90e217909, 0x9f2eca87f6f2d828, 0x142f49e17bbd0d69}}}, + {{{0x7b21697696696fd8, 0x46942980ad249867, 0xa8d2a4426eff2d98, 0xe986af6b91a664fd, 0x2c5a84b29b153f74, 0x07c84317d502345b}, + {0x82348612e7f1a4ab, 0xef58905a63617bfd, 0x55e9f443de1cb450, 0x35f4d40ffeddce50, 0x2ee81f5454cb33ed, 0x05b0c2abd6ed9ffc}}}, + }}, + {{ + {{{0xb8155717eb398ea7, 0xf5cb9f466e1596d5, 0xaea26a515aef6fb5, 0x7bedf0da5078d9ab, 0xb649a31eeb13b293, 0x0fcc4f7d258604b8}, + {0xd30d382074c1e2bc, 0x39ed6dec4f8bf01f, 0xf2f23cfe2aea42e6, 0xd9bcd48b739a05e3, 0x8478fe0fa65505f0, 0x00009753b9fbd789}}}, + {{{0x71ff4f7da4a6e22c, 0x5dc28a58702448af, 0xa043e6be8cc4c1bb, 0x4bda5a550b471070, 0x8266247a998b900d, 0x10b40f0312e33404}, + {0x5461a25e1d5c1680, 0x40d6bd2ab685004a, 0xb236bb3184f335a5, 0xb090f2fbe8f4af9b, 0x3b421e1e259dd88d, 0x0fabba183205b033}}}, + {{{0x0893ac1c625775d9, 0xe555618e085cd0eb, 0xb7f4c59e4c350fc5, 0xd21654c2ba93beb0, 0x32bf4ffcef1bd374, 0x02f76d183705407d}, + {0xe60a0e9ce492be26, 0x0f84fb943fbc0770, 0x778127309cb4db1f, 0x0896e4863ed9290e, 0x48a490414e6e4228, 0x100f93ebba6d6eed}}}, + }}, + {{ + {{{0xba996a5724251388, 0xbe50304500ce7eaa, 0x45955e509a1ab18a, 0x0cc5b4f160dd14e5, 0x81f12ee0ada8e532, 0x0537d1a0ba4e90b3}, + {0xa9a6b2fc297a2140, 0x1c153c44ea304d5c, 0xcc17c9cb62d6a8e3, 0x87e153357bac8875, 0x77b12a8147d145a0, 0x1458fcf558109604}}}, + {{{0x98f212bd318e28eb, 0xc21efc8c36e0cb36, 0x1fa860e88518bf64, 0xecc5375ca1a97259, 0x60a7b6afa2c21b3a, 0x0dd673f81474eb9e}, + {0x2a746b9bcf136cfc, 0xc36459a8c39c84d8, 0x9acae76fff2cbd52, 0xa315cca2689ffc29, 0xa986c8ed3511b3dc, 0x17b5ee023cff616e}}}, + {{{0xd66c8ee6c265bb89, 0x549a679c4ff1f292, 0x077f927300e04ed8, 0xc0532961165162ca, 0x50db4158edfe2796, 0x1496c2129cf6b8f4}, + {0xbeaf70da911528e9, 0xd179ebd98d1bf84d, 0x3705d39b59f303d9, 0x6bef189f854869e9, 0xfb91dfb62f91ffb9, 0x0c9bdcc7d7863f51}}}, + }}, + {{ + {{{0xc5513e17140dda36, 0xb1ae9ed7177e5b3a, 0x52123afcdc91f55f, 0x5f4dcf95285c4a74, 0x639642e43a30565a, 0x00e069c99bba8cee}, + {0x5cab623f2f298217, 0xb86a9fd3678a6f57, 0xc71ab80c7272221a, 0x1c30bc7d72e48063, 0x85f590ce2061e757, 0x01c229c2e04c99df}}}, + {{{0xee45114b835d0d54, 0xf3ed1173febf5e97, 0xaef0f04f749942b5, 0x7e9c9fcb321501df, 0x4423f86518660118, 0x0809dbc71a15dc8a}, + {0xd8a43ea8eec682ce, 0x27fef5201fbad620, 0xdef5470858eed2f7, 0x6f8dd6da0fa5e0ae, 0x14e2afe395d58b87, 0x0fe7f4a82dafd5ec}}}, + {{{0xcd758e953f0576a2, 0xbe95aa7ba14347a8, 0x9eebbbb81afbce65, 0x584baebeb947ba47, 0xf9d1f674f37f8a61, 0x0833b4b821a1f075}, + {0xe7ec8795fe5f388d, 0xd5477a900ca8e4bf, 0xf83166ae72407617, 0x9ea7028d1e42c139, 0x76b3807bcbc2af80, 0x19c2260adbac881c}}}, + }}, + {{ + {{{0xfd816930e9fcf4aa, 0x6fba4041f5bc912b, 0x7b9ad5f5c988b0e1, 0x245bae6238db243e, 0xc066a3290a568476, 0x12cd5c94752227e0}, + {0xd091bf1e245fd993, 0x27b6575dbeb76bcc, 0xab2a9b1df91a12f1, 0xf1c4b0790b5ab4c8, 0x672280b13fb5268f, 0x189ea13fcadef682}}}, + {{{0x2745864313a29000, 0x2ad2195b2ba7a73f, 0x4a5b83fc2a18b0b1, 0x8204fe8e77926901, 0xda549537358eec58, 0x0f1411487ce97d76}, + {0x39f7504bc13d3dea, 0x66fd6962500f28a3, 0xaeb6f695d0af3820, 0x2de270f6b80c59f1, 0x1b0b8fc85b6846c6, 0x05bc3fa735072054}}}, + {{{0x2513f0dc6a79af0b, 0xf8aa753392e507df, 0x2dd980fcd2c6f7de, 0xd29753ac9b012efe, 0x517a5601612b4b3e, 0x188a6e627ca70f4e}, + {0xcdfecffa053a1f7c, 0xbc807e9acad84bc5, 0x7533c1ee5e2a5d65, 0xbed97fe03b27c89b, 0x69c7bdee723af99c, 0x0953ea392f6ddf0b}}}, + }}, + {{ + {{{0x22acaac48cb88b36, 0xd4ac4c60d020bbe4, 0x2ab63e87bd64ea3c, 0x2d4f2546193e9fe0, 0xa41cbcc65335598f, 0x0f6eea0310228633}, + {0xb48398798f323f9b, 0x4186ec8507fce3b4, 0x74ffb9b5765a8dc8, 0xdb3e4f60b9fb860a, 0xe520aa8bfb771bd6, 0x028679367f389d90}}}, + {{{0x348d57905874912d, 0xfda2239d13ef9347, 0xa8064cfc9ca9ba7c, 0xdb42c82240988f0b, 0xfbde4b4e0fb95ac4, 0x13102424f967c22a}, + {0x9174367b6856398a, 0x4f08d71e3073f080, 0xbad9994279f6f438, 0xa57e4bae2cb292ce, 0xc8077f750bf719c4, 0x01c1025a5ad768e9}}}, + {{{0xc160ef92ebb42bda, 0x635d658ffb2fc186, 0x61fb9d4d6ac305b0, 0x4812a6cbc73e4519, 0x73ad6063821abf52, 0x01140c2d1d2aa3bd}, + {0xe6e29c6e55ebe36f, 0x9985b53210574e5e, 0x5b07a15324a14323, 0x8df677a6c3464ecb, 0xd15591fbd3838efd, 0x011da244ca43788f}}}, + }}, +}; + +/// Precomputed Miller-loop lines for KZG_SETUP_G2_1. +/// +/// To regenerate the line table: call blst_precompute_lines() on KZG_SETUP_G2_1 +/// and serialize the resulting blst_fp6[68] as hex-limb literals. +/// +/// TODO: This can be done in constexpr, but with current code this takes ~30s and ~4GB of memory. +constexpr blst_fp6 KZG_SETUP_G2_1_LINES[68]{ + {{ + {{{0x6efed23c7d0a5bdb, 0x5949d950468ec242, 0xfc60ff8d82b55e1a, 0xdfb2e092c2026d95, 0x490be84d7fc955ac, 0x03d9c9b547a629f9}, + {0xed44396012645bf7, 0xe0974cb7f7fe961e, 0x43f5cd4a85a421c2, 0x707e0cc0069aa464, 0x9e1c737646d7257d, 0x086023b53e6fa8ea}}}, + {{{0x54da772ff80db1e4, 0x79ff082366890635, 0x7e6c2b91358af687, 0xdeee448c2e5e0978, 0x744182000f6272cc, 0x196f50a21e8ec98d}, + {0x29674747a4b7466a, 0x82bb127a4574c512, 0xfb8a6f647c59a6cc, 0x35762bd913a3725b, 0xa195d14c34ea5693, 0x0f489543e1871ac0}}}, + {{{0x9a271070f2769b83, 0xa5c1ba456dbe2c6f, 0x62d41db1543b1cdd, 0x3c410067172ac653, 0x760f453e44c0dc12, 0x16ac1538a781c170}, + {0xef403c2eb6080d69, 0x46d592b63cccd9fc, 0x621dcabbb75aaadc, 0x43468a65d92ec87d, 0xc827ff1a5dca4a4f, 0x0b9d62a011ebecdf}}}, + }}, + {{ + {{{0x3303be5653236535, 0x866813e95098f245, 0xe1de099cb9ab2ef4, 0x5421433de7d68bf4, 0xea54f3551f3f5faa, 0x026927772db41438}, + {0x854b6cb08fc4af43, 0xe7ee1fdd70c830b9, 0xc74f6a6929876ff2, 0x6996b86bb4c8d02c, 0xa2746ed3f0a7ec04, 0x0914667502721b61}}}, + {{{0xcf9e307c898c4c7d, 0x9eec9dec397c44f2, 0x1a040a956c72d137, 0xe786d22489197ffb, 0xc8107fb6c7bacb91, 0x0ad7dfc9afb56069}, + {0xf774eb83a40c81fe, 0xe1957de9a19fe537, 0xa52f6f253ed5a50e, 0x878a952bafa3a5db, 0x85c4b5ab6f0c82ac, 0x04cf8e12c4104c18}}}, + {{{0x927fc505181a4879, 0xbfa2957ac229834d, 0xdcacb1bffab54ae5, 0x5fd15f59b7ca20bf, 0x0e022f3856c89f7d, 0x140ac265dfaa364b}, + {0x428d78752fa40f9c, 0x08bb35b358e69a99, 0x3a535fc9989195f3, 0xfcfeace9767f1860, 0x7129562d2cdcd876, 0x0a45824a96d47eaf}}}, + }}, + {{ + {{{0xca83e873bd40013c, 0xfada5be99bd11f89, 0x530e1f3006387cd6, 0x5bc5cde5ffd4f002, 0xd547a7f80954a022, 0x15de2957faccfc1f}, + {0xfff00d86cb51e8a1, 0x3c37b58e4116a551, 0xeafd82491eb7e93d, 0xab2e08e9f8bd6624, 0x2dacf1a2d3e8e3bd, 0x0e40278e0d1c6e4c}}}, + {{{0x1c496dffeff685a0, 0x12b466e7166597a7, 0x3b51d1e026d65ea8, 0x2fe74305afc2d2f8, 0xee66b7b295b1e4cb, 0x0005b649e630eaca}, + {0xe688c7ca67d0095d, 0x4555d20cc981792d, 0x7875180c64c678fb, 0xf25b4e0b319bb068, 0xdee3da38db0d699d, 0x1884d99afaa673a2}}}, + {{{0xc1071de8d30a40a5, 0x8e3b2efd4fe3ec18, 0x30c04204d3c85f5a, 0x5a3d1899c226ad34, 0xfbe69dda034b1f55, 0x0a6e6f4a09460d73}, + {0x81b90cf10e2c1360, 0x62712c14357aaf94, 0xedf4e0e98e412d01, 0x5511d59cfc0831a9, 0x0037c031d9ee2b10, 0x0a7cef4644ea8c20}}}, + }}, + {{ + {{{0x69df63e37cf1c0bb, 0x92a85c35600f4255, 0x933e32a181c20156, 0xb9e56a499aaaf156, 0x8e554b2338900bcc, 0x0b51ea3828a7a7cb}, + {0x20c2400390800ddf, 0xec5f2cbafa9c4992, 0x50995f390310bfd7, 0xc204f5114feb0edf, 0xab7cc49b5ee78034, 0x026caf13aa2baac0}}}, + {{{0xe25988af7e6c74f5, 0x04a3bafd5679a410, 0xee1066911d5dbc74, 0x22fd13df66644043, 0x69c309322784f111, 0x10497c7017d34cc5}, + {0xbaa0fe08f8e70279, 0x36caa7ac66eb89df, 0x7ed0e4cb8a83ae83, 0x30a312bc6744108b, 0x582fcc16cd2d5936, 0x058491d9918e2194}}}, + {{{0x53eb955d405515bf, 0x58f946772e785bcf, 0xd1d6cac44978c788, 0xa1696dde2a249dfe, 0x7a37e238669b1074, 0x049cc89de67ae33f}, + {0xd414cdf5fce0e232, 0xb98d24f6ca14a90b, 0x7d84cc769dad38e4, 0x60cd0d5514283087, 0x7dba7e5f5d53a19b, 0x0e54cafdaccb344e}}}, + }}, + {{ + {{{0xec0d39b7a1409fea, 0x794f7f92897b17fd, 0x3e3860303523eee2, 0x1cf39b46eea3c3eb, 0x744081a980cd84a2, 0x03c5c3a78d97a5e8}, + {0x6d7639bcc426590c, 0x5984926a1607bf5d, 0x79fbb6d3f8b94c07, 0x34c3e16cbbd404f6, 0xcfe4e8272fced6bf, 0x0c804f29c0210b55}}}, + {{{0x4f9e82f1ef0760ed, 0xf8e95d81b8c987ee, 0x62f2ae9d30acd0bd, 0xfffa4d619bc2ea1a, 0xa373da18fad0a64f, 0x0f5e3e9d52a4af93}, + {0x23edbcdf697f625e, 0x976f58b65e0936d7, 0x6c337b896decc13f, 0x2f0d2fba5c03df2d, 0x022ddb9cc4c7b2e6, 0x12e3265b9000cad1}}}, + {{{0x919e83347782ff89, 0x968621bd29613cc3, 0xa2fc6ad72524b8d3, 0x628c286f420a0dfb, 0x1fd083a87804dd14, 0x15a69b576d8127f2}, + {0x57a34f5cb8491520, 0xf2ae4400b65994dc, 0xfbfb201a455bfee9, 0x44dc60975b1d89b2, 0xd783b49a1754612d, 0x06b357f6626c3b26}}}, + }}, + {{ + {{{0x25d983c0767addc6, 0x74e512e592061591, 0x7af9f005f1ad4bc9, 0xb614ad1c697f3ecc, 0x44ab58d63e14dcb0, 0x0e8bef903c0cd000}, + {0xc75a19ac58074ee1, 0x4cbb63dbf598f080, 0x522f40affa76473b, 0x82469b46af58575b, 0x497a5f6a601118ec, 0x02486f287e3b7f1b}}}, + {{{0x9d16d4ec375dddb0, 0xa677491b5caa4b99, 0x5902db887ab29bb1, 0xaf8d6cd594aefb36, 0x6b1b19c5651414ad, 0x0efdfb911792831f}, + {0x2275ac81c22eaa14, 0x9c7b00e7b3c52717, 0x224df2944474372b, 0xeb6438d4f031730f, 0xf47d217bc54126ca, 0x0d9fa488124c862b}}}, + {{{0x7c68de577eeb8408, 0xb63b49f1c68e9307, 0x7aff895dff382c53, 0x9451ceb585d258ad, 0xcf4ea09b46603c39, 0x06b6f8b2f1cf0df1}, + {0xaef2127926fd04be, 0xe4d969c3abfa81b5, 0x3b7110bfce5098a0, 0x0f9831afaa14bebd, 0xc35f6ce92aa6a89e, 0x19ba196230bab54b}}}, + }}, + {{ + {{{0xdf52b5d64735d1dc, 0xcf00ffa2ef4596fd, 0xb7468bb1049885e0, 0x9f9b390b5d4f34f7, 0xf5966575a0f1469f, 0x18813127bdd1ad53}, + {0x15f5f449be2fe6c8, 0xd721cf7144487a82, 0x299c01fd7f00f8d6, 0x75e160b5a0f46b6b, 0x171989da589eb7d4, 0x1813b586c9a4360b}}}, + {{{0xa987bda14a59c67c, 0x86747b073ffacb20, 0x00b3654244c9df80, 0x64517701c5a8061f, 0x98c6a1e7318fbe53, 0x18c0f298616e2a3d}, + {0xc94d30fc7b3fdba0, 0x19cc58e363905cdc, 0x0fafc4629e0f0ce3, 0x28ec509d35d88b17, 0x4fcd316faa9c133f, 0x07b7acda35d62100}}}, + {{{0x0296adac70f82c68, 0xccb6c7703aad6d91, 0x1dbec67f7adefbbb, 0x2763e8a795602748, 0x1a5eab46b0b05773, 0x0fdb42b13a93a9b7}, + {0xc4890535b7b3438e, 0xaab40dccbb9a9d6b, 0x3de484a943f52aa3, 0x65026db9430001b5, 0x84e62e5a6e909d01, 0x044f6a77ff06cf81}}}, + }}, + {{ + {{{0x59f9e4246d200605, 0x09d9072d4f609403, 0x881fff4ad7c13853, 0xafa7369b82f821d7, 0x407967f66b4b8e69, 0x062d476d5ff17cc0}, + {0x90c0bdf37792e358, 0xe196eb262c149d5a, 0x088075ff2bbeab83, 0xd8a8e5a46fa4bd32, 0x61ed91befe4f891d, 0x12e77f085cf7162f}}}, + {{{0x26dc50d05dbc8019, 0xfc2fc809d7a1799a, 0xb6dc36eaf8095bd1, 0x35129900e82269e7, 0x408d05cc05bdeefd, 0x09a4b6d54eaac12d}, + {0xa61502a232775b1e, 0x7d33932da426e567, 0xd07cb7334726ac7b, 0x2836f23a7ddb4a89, 0x631185bc12bffb88, 0x1552e9cc24e68804}}}, + {{{0xefcb7798d56a531c, 0xaad234a68b93a46c, 0x238cea67fc4b2aac, 0x5c55491247ddb66e, 0xdea5dc7269089275, 0x00f3e8c72cde3461}, + {0xb8e45290477ea949, 0x61b5410bd8184460, 0x51a2378b3e17887e, 0x02c0e56828c38517, 0xf93c713c3b0b5df8, 0x05ddd36cd6aca18d}}}, + }}, + {{ + {{{0xdc481c32fb1bd5bb, 0x460f147ae3af2e7e, 0x8a426ed8f1949357, 0x0be6f50cf8aed2e4, 0xe22b1a14cdb4a12f, 0x034501e70110fd56}, + {0x9dd6fc3fcaed3116, 0xdafd646495036a1d, 0xc0e42ad15ff10e0c, 0x30024c944fa8672b, 0x66abd8f117d368be, 0x18ec523e948316d3}}}, + {{{0x2bd277a5eca7799c, 0xb7c2a0365feee1e1, 0x4e844af245dfcf77, 0xaddb7c7a369ae540, 0x7e7b4d36069356d2, 0x10faf25d03079b38}, + {0x90d33332e4e87b1f, 0x756eea72a3ee3da8, 0x66468cac7da9bca8, 0x73f2ce421553e01c, 0xe0c100e4a43f2972, 0x08e3066171a8e83d}}}, + {{{0x52f7488c4a00544b, 0x2a6a426baa9ece5b, 0x07085efbb8910012, 0x73fae44413f47a76, 0x48415d4621692a37, 0x02924cd73606e737}, + {0x8d3ed548ca5c41e1, 0xd2849d73e51c892c, 0x3ffdc86fae02a2bb, 0x64e2b6a71139f804, 0xde8d6caa4a418e86, 0x0c67ee59311a5460}}}, + }}, + {{ + {{{0xc4f9acf06143027b, 0x30d616de244e07ce, 0xa677c4f720dccce8, 0xad87b1264f0ebb95, 0x342a12105d442ba0, 0x148817596369ef45}, + {0x2c5c532c4f19bbf6, 0xa29b67078b15920a, 0x8e6021d1f2d8e7b2, 0x4a433511dbf53538, 0x4e98a51612441253, 0x03613d17f74e69bc}}}, + {{{0x6a363236182d2a61, 0x250893dfc292ddfc, 0xb71f8947f68cf36c, 0x6e436ec7b649f2bc, 0x49d406d7939a9abc, 0x0469921d5971aa2f}, + {0x534a0b17b4011726, 0x19cefe6da9264803, 0x370d41ddd28639ee, 0xebac579d1f648f19, 0x9e61c3107b82c2cd, 0x16fa5abed123e62b}}}, + {{{0xf5e57402da716000, 0x6fd5fda54470b68a, 0xbbba916eea96b969, 0x2c63dec0a61024e8, 0xdd02c3cac504bdfd, 0x07551d40ac38b9da}, + {0xea0aa04bdbda6fc1, 0x58a643c2ea272417, 0x504029a1cb6bc85a, 0x3c3e64ed56a90692, 0x79f9456e92d122e8, 0x08ddb00b9f65dee6}}}, + }}, + {{ + {{{0x6737370091283118, 0x4a01d01cd75affb8, 0x2f75c365a2787c2c, 0x777085c156df9698, 0x5cc8e361fec1cde5, 0x133b84c6a0a5ebc9}, + {0x721b6185c7bf8392, 0xdd31e24e0c616374, 0x2b4228eaad364638, 0x66a709a19be3bc89, 0x8a8c53139de085e2, 0x1898ed4c01c22603}}}, + {{{0x988792c54e49b32d, 0xb7170844543794dc, 0xd8ade00c3146dab5, 0x0c4ed2f7021267b5, 0xce42dce6d6ba2fcd, 0x16243b7734aa2bb9}, + {0x94d71ed66e70d552, 0x97bc004a0e774682, 0xed2aa7df25c31493, 0x2307206584849f62, 0x12b34eb38179b1b2, 0x11769308880e5e1e}}}, + {{{0xa40a3b8405f2b7a4, 0x0e19d13d5d7c6ab3, 0x343849dde1a28e20, 0xacc9ce748368806b, 0xf9588420a8fe207d, 0x0eb9d53b0ccdf6f8}, + {0xc86c681d141c21a6, 0x1760e8ff10c6401c, 0x67a68882e87f1b44, 0x94d0929856c44d3f, 0x2c26e45a7ffe943a, 0x01bc8651094103e3}}}, + }}, + {{ + {{{0xe00b800b7cfc1a7c, 0xdbbef26596faeab2, 0x284c2f1d76ec3189, 0x3f35b35b4399a894, 0xb9b98b5b11f8611a, 0x15b423569df4383c}, + {0x3727b0e776159e6e, 0x8bfd77b609aa1700, 0x44504f3f07846d67, 0xa32b558955aa2149, 0x638a6bdd056665d8, 0x0e8c02cb02ffcab6}}}, + {{{0x1800dd35ee030d5d, 0xd03edd3e8334a6d8, 0xbacceee892c95b52, 0xf0d999e25968a5c1, 0x4b341ba967305572, 0x011423a57b2cb383}, + {0x552bf2301707d848, 0x1d480a1b9162bd07, 0x3ae45237d2d0887c, 0xf0e4a00787508d32, 0xbfb84f3f8b54b260, 0x074d53b7e682b223}}}, + {{{0x351a8a034d671a6f, 0xd4d3f5b6dab28298, 0x6e79c7ae631f6e8c, 0x4d1f7ca1cf762a41, 0xb7f434e2d475b820, 0x07c341d48a690218}, + {0x86f4658a47e5b8a6, 0xfb0a5c54c2633c33, 0xcc42e3db537945ed, 0xa246a0fcb019b370, 0x357e5cefddf37e2d, 0x0da6434f51e340f8}}}, + }}, + {{ + {{{0x76358616120b11ed, 0x6310b5b4f489dc9f, 0xe932cac95966b8c3, 0x471a6a0e674f5e64, 0x1e0b973b3c98d603, 0x135abd2dbc544244}, + {0x46b4c479fdaac13e, 0x6d874bed8f999c3d, 0xafb2d5019c61df50, 0x4ec403b9593ca839, 0x8f2ff358a610d191, 0x0a9603f24a9b6de6}}}, + {{{0x626091adf2aa0fe7, 0x5ed2b24bbba393e3, 0xc8040501b2558224, 0x5eb7d201e5d2095a, 0x258667bdcd5a416f, 0x0412bb2791b9113d}, + {0xbcca6474c0345162, 0x7a82d5709d6daf22, 0x56022bd82a153e33, 0xe26508192af0503b, 0x7c13f25286a631bd, 0x083f41f4952ca39b}}}, + {{{0xf9d1fa9fbb5fc1d3, 0x97a436e44a9beaea, 0xfe4dc89267122f39, 0x7939813a970b51e2, 0x7095d5d0084ff17d, 0x16e1f31c52e52d46}, + {0x1779301fe5111534, 0x8c98719a81d679ce, 0xf3a54ab8809ba435, 0x00a074558794b356, 0x172e82080699f2a2, 0x088038c26e1cc5d5}}}, + }}, + {{ + {{{0xcba978cfb47c016f, 0xe4b1900cdcf1bde6, 0x6854b68e0b0fdf57, 0xff72d9c7d28b0b5b, 0x9f1c53c6f2ef990f, 0x1556f0c06c7ede9c}, + {0xde1493844c541c10, 0x6b393320d4165964, 0xc1086759ae435898, 0xb7ab31802a5f6170, 0x4e96f493cf735c76, 0x07d8b319e226a73d}}}, + {{{0xd256bd46f5d96900, 0xf1c26a3bbb5ac25b, 0xf76f67555dc91c05, 0x0623f4ebc6c6972f, 0x564d21d645d39bbb, 0x15f5a6698213587d}, + {0xcda8ba4f2f63cd0f, 0xba939235166447b7, 0xeb8c595c74f4e6c3, 0xca2e46d03718a6b2, 0x23bbffe5363bce35, 0x0294bdd8800a1a7e}}}, + {{{0x8e72897b7087298c, 0x491b1f0aaceb7710, 0x7d84061beee17a70, 0xca0037ac9dcf201e, 0x1fa744e8ccda5b9c, 0x0a1ccc170945096a}, + {0xf31e3c0ee3c0c105, 0x79a7b5d82127710e, 0xbdf07f1382253774, 0x556c979ce9fa62e5, 0x0ceff2490021c4cd, 0x0899d9dd407fb357}}}, + }}, + {{ + {{{0x4b6b9a551b5d314e, 0x0668970a6e415822, 0xbd0a7f0c762c9d07, 0xb7025dcf0e31483d, 0x24a12164e554795e, 0x13a21b27424252c8}, + {0x26e9fa0f723119ec, 0x8059f2db4073c4f1, 0xd3e6b6f6d12bf7e4, 0x1896b846999e4b93, 0x8204436d6d1404d8, 0x1987b53655a71de7}}}, + {{{0xe719471705ceefb5, 0xc7dd8006726b0aa5, 0x441b501ccf8f29f9, 0xe1c2cb57099bb0f5, 0xc3caa35a38eea664, 0x17c9f772628a33de}, + {0x207b5a1b17f320e6, 0xeb367c7d4b288a04, 0x1f0430ca30e939a0, 0x50d6bc5cd87b38e1, 0xddf7cf83bb56eafc, 0x0cedcf6c9374b8ba}}}, + {{{0xb2175ed4ff049f46, 0xfe3fd61c0af6fc11, 0xfb8ff68d9342b0b7, 0x63db6146c2932cd3, 0x452de8be1ecc2421, 0x113f57c78429dfa3}, + {0x0539635c9aeff0ea, 0x7b707ada823146c8, 0xd4c1769bf8bb26e0, 0xf88ba0411c3071bb, 0x6904fdc52277b319, 0x11346c28c9ad765e}}}, + }}, + {{ + {{{0x1a77f69febecada1, 0x1bc61fa2462dc3c0, 0x9babffd318cb4af5, 0x49e9b99587f37e2e, 0xc60270fb2c02d268, 0x0d2f85359dbfa308}, + {0xb22e92d818723f09, 0xd65257aefc8966b8, 0xc156c539ece2d4fe, 0x36c143ec30e125d8, 0x97de68fde5174e5d, 0x17e16c294c3677f4}}}, + {{{0x4d0a6b17a4ff1ddb, 0x0c04072c6fac0c55, 0xbc67726280823e27, 0xb45445af76671ec7, 0x7704e38b2c9bab99, 0x08add94fecdce990}, + {0x358553d3923dd9bf, 0xbcf3bf9fbe164e50, 0xf40e8cf4c6aeafb9, 0x92b6b9af7b77dc36, 0x125cfe8191c03221, 0x0c9b02abb6db0de4}}}, + {{{0x40da88e1ad661aa1, 0xa63d931939ab12a3, 0x43ed8a209b06dc99, 0x7f5ea58bcfd211a1, 0x78e178cf8b3b4e94, 0x075a916d66bfd0d2}, + {0x2ff7d2b5b8e50e93, 0x327a0109c39205c7, 0x98f1cd6276537622, 0x59f81f5ceed035a4, 0x917426a600bc8085, 0x0e46db2daa7d3f8b}}}, + }}, + {{ + {{{0x86e47d220eb917aa, 0xb934c24e94dedfff, 0x139ac917650bcf62, 0x8cf1dc5ced5070c0, 0x5f434c52b6cab20d, 0x0c18685322857318}, + {0x73f4a58e35bd583a, 0x275e70ec605dca74, 0x1bf4867dd4922936, 0x84fde6fcbcb75f28, 0xc67cbb8872967724, 0x0f39358abbb5ba6a}}}, + {{{0x2e59dd023f3759ae, 0x62086c6318d2572e, 0xf8dcc962ac833a38, 0x9fa3cf9e711a6dd7, 0xc626c944c198750c, 0x1946dee6273f5fb9}, + {0x41e3ba53181afdba, 0xa323eeab296501fa, 0x0dc654f6bdfbd065, 0x523f7e7095d5471c, 0xb14e7cb7a0f2ed2e, 0x15bb2d5fc0239489}}}, + {{{0x1ff50f11c965c504, 0x93ca43ba3f25c160, 0x79da65f2a4e90bec, 0x8e95c2c54d9aa684, 0x89f34b61e5ab5552, 0x0d131a3202352bd1}, + {0xf388d35664c0d04b, 0xcffdcf013179e526, 0xffacf8b3b5cc3eca, 0x17e5675e3382d02f, 0x07f63e1e9f6ecd1a, 0x1018bcdc2a304c31}}}, + }}, + {{ + {{{0x122e6b05b3a654e1, 0xa7cbe19e6ab88507, 0x595d7d30dfac7f96, 0x94534dedf47d3ba6, 0x738cdf64d8cba191, 0x1300e15c2593c6e4}, + {0xa6d0b053c30025de, 0xafc3bdab5df46e38, 0x586b540b25e2e5a8, 0x36243a6afd797c0d, 0xfe760709294f6802, 0x13fa190d39f81254}}}, + {{{0x90d7f71ddaa96322, 0x597f99050e83ada1, 0xaa2b3d9373a67e86, 0x4eaeaa42f1084833, 0x2e0fc6868d5e66fb, 0x06938d42225872e3}, + {0x16e0a80c61e46995, 0x28072b1d8c24c9a4, 0xa77be4ef96c2fa38, 0x91cbc49db5b24f0f, 0x43bd4169f2ac8ff3, 0x10ca7f156110cd70}}}, + {{{0x4e46bc58d1a2c66b, 0x3ffee2b847a7736c, 0x81f6c31eeb4b1db7, 0xe7963e2e8eeee42f, 0x33892a647d690ad9, 0x16df2ce0029c2e27}, + {0xf9c7cdc440dd9774, 0xb31455a0469182a0, 0x058268be5b511b57, 0x39fe55ac7b2b3462, 0x5bafd78e389a3f53, 0x0ee74f76babcfade}}}, + }}, + {{ + {{{0xf66849fae3288dc2, 0x7247f0fc25599875, 0xa853d7c150928594, 0xf70d792bd715063c, 0x78a43330858fec49, 0x0aa31bcb6ab22548}, + {0x5a2dcc178ef59de7, 0xb4f362b0a7c53d1c, 0x955f6c47353483e8, 0xbe863378699b7688, 0x2572883802b54f9b, 0x0e45348dd7a55928}}}, + {{{0xe3fc10a7c3e43ea0, 0x2870d759dbb5d80f, 0x1ae5b45fc2c87431, 0xbbd94e4352423998, 0x811dfb5f3d810655, 0x0da550dfe8db012a}, + {0x98deb3a3ef0fdc25, 0xad6a53ff31fd8d6e, 0x5ebe571bc337323d, 0xe5888e509eabf490, 0x25e78cb69f5c2d58, 0x02573389dc4db3fb}}}, + {{{0x028c7585d51c81d3, 0xe66b9e79c1f6d4e3, 0xdf03a1cc9fc9eaae, 0xf358fea972c14ed2, 0x386fd9bf04c2c15f, 0x060fcf6edf5a1a70}, + {0x62ec4152ef967710, 0x03fd36c91b459c76, 0x57b996e01f14134c, 0x8bf0b4174b87c5f3, 0x7c007df68e16bdf0, 0x16e8eb9dc477970b}}}, + }}, + {{ + {{{0x2eddceaba2e8e040, 0xd6eca7f04c9f4f19, 0x9c8703dac795d299, 0x49d69b4a5f557910, 0xea0018f4969fec8d, 0x0bedf822a71b7964}, + {0x782d76dce914e81c, 0xcd9ec27f641ccdd6, 0xb8f4fb57d2d236ff, 0xaf8ae4c29cb1ac79, 0x7988ef15e237d7fa, 0x139b353a4f071c35}}}, + {{{0x50b748eaaafae60e, 0x5c53d78e9123f723, 0x6415b0c9650dc52b, 0xd9bbed4b5b551cfb, 0x0b1110f969b3e135, 0x15339265da7702ce}, + {0x4b0dcaec02b4f2bd, 0x5ab84e063ba2d0fa, 0xd359b66d003bebcd, 0x3165df04661e8843, 0x28aa42e3363f6770, 0x01d9ef3c0c1d0fa0}}}, + {{{0x68d01503dbc3567a, 0x76d1048db45f6b8a, 0xec4bd0ed58c4113e, 0x55f3785b9acdec08, 0x3e36c03898a2716a, 0x0a182ea86d54f744}, + {0x3f51d6339119043c, 0x42fbf75c02579bdb, 0xc19ce3c066c9b679, 0x4fb4121504a9a8b7, 0xd27ce91a305fccc2, 0x157c1848e281fb98}}}, + }}, + {{ + {{{0xc1ca83170b315098, 0x55d5daea2bfee9ef, 0x9e5a59f2d6406714, 0xbf3ac30744535dbc, 0x75cc0dce95d3258e, 0x18cfeb0b0f38ff88}, + {0xfd501b4789fa5e7f, 0xc0720f840c94a26a, 0xeae9f75c98900472, 0xbef9bf6d0e3f63be, 0x0cd3af134d744389, 0x04c01c650efece98}}}, + {{{0x146967d3363fed39, 0x9686c5e6d768857c, 0x03b9662ab8ce1ee8, 0x84dd8e198db22933, 0xb8d9e36dfe3c8bf7, 0x1572a7778eb70709}, + {0xb8b075873d2c8b05, 0xe5d278671901cf4f, 0x6fa7daabfa7e5b0c, 0xbaddd40aca5bcabd, 0x9b84cd5ebde18292, 0x10a791c10112ce4e}}}, + {{{0x898dcbced7ae3c30, 0x3051014f96a40c28, 0x438cfc338fe06934, 0x0cb2641f8298aff8, 0x23f9eb5bee884783, 0x047924e25b2e2a67}, + {0x1abb85d6e78c805f, 0x1bb62b8f0599990b, 0xed2825552d168b07, 0x90430ddb7063d215, 0x8594894f3d6a4a05, 0x1814a4c7b2daf7a5}}}, + }}, + {{ + {{{0x67cca50545ff51e9, 0x49145618771dbcbf, 0x7f7183c62ea2b47d, 0x5bf70440a1f9fc30, 0xe2b204e40fb5dd27, 0x1816258be2f2717c}, + {0x175ce1d6ea08fd9d, 0x7d64e4499109f1bf, 0x814d968c48ce4175, 0xeaf0f079de2c9ca3, 0xfc8cee4e895026f0, 0x09c0c8438946d39c}}}, + {{{0x1804f9f0d603e7fb, 0x6eb6b33a0245d0ca, 0xfafc446a1a37a77b, 0x4fdc0a29c6756bc2, 0xe20f1e92653cc464, 0x13c150ffd0208c47}, + {0x6f934cc732bf977a, 0x1402c129d992b961, 0xb28f508700c9fb46, 0xbd8c0bdfd3f6460b, 0x79ab7ee3631dff52, 0x06af90da01059bd1}}}, + {{{0x21befe1f8a3a9422, 0xbb8eac8497f61455, 0x3ea63233826b56f4, 0xd031aa474f714c99, 0x2ff7255ddde7e82e, 0x1395630f59fbbddd}, + {0x4b27aed673d299d3, 0xe5cd8bc59a843ba1, 0xf40d212e054951cf, 0xb6f398ecc948a5a6, 0x816950fc99c5822b, 0x11436eda129a2302}}}, + }}, + {{ + {{{0xf0423f2c1965f52b, 0xd3a7a30e1469831e, 0x16403f4e923eac25, 0x3804cdbe5aa7ad80, 0xd9fd594afbf92a7f, 0x07e924d4c274d2d1}, + {0xeb81a47d24866303, 0x81df3357caf0ca04, 0x54a14de333f5477a, 0x3876ee5730413df0, 0x7f3b6a440311184e, 0x143036cd3990f08d}}}, + {{{0xd8951b48b2ae85c6, 0x539471b68ec55d78, 0x088b8bd4bbcbdab9, 0xa8bfd23aca456a3d, 0x482ec582a7edb2e4, 0x197b9d80220d4f73}, + {0xb625a3e5bb283b5f, 0x0859b8552e476495, 0x32f973116e36915e, 0x1b037fbb0b6ea3f7, 0xbe5ded5532b0b5b7, 0x18e223748e519b0a}}}, + {{{0xf9255fefd56479b4, 0xa906f808d8d9d760, 0xe2bea4a2b7692fea, 0xfe4997decbc183d0, 0x8579e9eac58320b4, 0x047c21d1786bb3af}, + {0xe55d83b5a24aba6d, 0xebf5bca14a711a9c, 0xcbcd571b62af0282, 0x5d36e6480328979d, 0x1ce4a9017d57aca8, 0x00b489a454ce5be7}}}, + }}, + {{ + {{{0x92645bc8eab748e8, 0xf043c6441411a40c, 0x8a7a3cd6aaf3a1dd, 0x2a5e8b97409a9de4, 0x0f9706b40a1bfdc7, 0x006c5e73a2008a29}, + {0x9314ac6233c7f778, 0xde6ec35d00867f1a, 0xb0c5d9d43ad0f81b, 0xa897de4271b0d960, 0x8014c89c3055744f, 0x0c26a678b1f1dec3}}}, + {{{0x9eed83ce4ca7e453, 0x1b9caca14cd5cd72, 0x7452a2ccb9d7e42c, 0x5524ed1c5eb8d990, 0x4b24136ca6e2c53c, 0x141c8c4a85af9629}, + {0x73748639da59b95d, 0x5766e6bc30f7bd53, 0x238f696a1d0a14f8, 0x25414e789b699c83, 0x74157caec38f8ae7, 0x01fc343fd654b3ff}}}, + {{{0xaec90b8e8a60c9ce, 0x50584997428ef03d, 0x6e6caf0ed7a71c58, 0xb3f56a0fce563741, 0xb30bd163689dd290, 0x03f435b2da8754fb}, + {0xc9f35fa6984e6561, 0x959e0239fc2692dc, 0x4cc05a999c0a3a34, 0x757406730cd77218, 0x79be31c1cea0420d, 0x0b4d7098408f4800}}}, + }}, + {{ + {{{0x76e958948bc432e0, 0xdd4e720a036f2d1a, 0x5b9f1d2bfdf0251c, 0x2e9f7c436e0e8ff8, 0x65706c45397cb51b, 0x0abae684952ec2e9}, + {0x7ccd3af03a04fc65, 0x6371e3416533b04b, 0xea753ceaef4a1c1b, 0xf3dbe5eabfc09c7e, 0x65c23f914d1ac3cc, 0x0d1648dae105a7aa}}}, + {{{0x69e659ef3e57a8a0, 0x208e037417f0ff91, 0x04333fcc92624780, 0xaa781fd663ce75dc, 0x4a0fb9afdbc6eadb, 0x0437d7d5402c23eb}, + {0x7ce174b30413c655, 0xae32f3846933b553, 0xaedd97926931f828, 0xa68b937dc945eed1, 0xbe3a4fa85696fbdc, 0x0a623087f4d9daeb}}}, + {{{0xecf095c43df1ea88, 0x0a881ec237e3bb53, 0x327af8fa016bfe13, 0x49e3f68ebc5e0923, 0x47499f5091b03ae0, 0x13d38a0d5f896c2b}, + {0x42d171674ae3d21a, 0xc88b10ead9164164, 0x7c58c1e91ef15d4c, 0x88b64b0be7f351f1, 0xe7ac18ec4d970798, 0x0b0f3d7c06fb39c1}}}, + }}, + {{ + {{{0xf241dffdda94fe67, 0x3b8b805440ee02f6, 0xb9c1c390119ae29c, 0xb7044ec44d43d946, 0xca810f87af1b52a7, 0x0d485f2d04655be4}, + {0xe488aa14e592666a, 0x03a9dc250aa47a29, 0xe8c547c9a601a7c7, 0xb8a24830199330a1, 0x03ee55ce0b6ccd4f, 0x1250e54aa64a6d29}}}, + {{{0x6e2c69054153d131, 0x596313398230af4c, 0x089f28f9a6801a6e, 0x256dd368d8b6fe85, 0xbcc3c2e2ea5d97c6, 0x104d8694b212478c}, + {0xd7b3cb76c9aa6507, 0x9fea5c51a441de05, 0xca478810184d7ff2, 0xd422455d44b8229e, 0x0bf8adb01e4261f5, 0x16a7f1a88d67e467}}}, + {{{0x096361839af9398b, 0xeca6348303817086, 0x71afbf032cddeac4, 0x77232d194449347e, 0x1e1a360633d58885, 0x1287c52034b0df94}, + {0xb4208e1045f25cfc, 0x63ee167f2c996a5e, 0x90f6308b8b5bd00d, 0xa927c5396e613aec, 0x2d9597e563e3869e, 0x0a40d982939e4fe9}}}, + }}, + {{ + {{{0x8ca17a224fffede7, 0x04b4a6c0476f611b, 0xb59d750ed126b45b, 0x3d846a90bc6cdca2, 0x3e54cadd8a321d95, 0x17cae9d67d5ef6fb}, + {0xf49aa22c8d5f37f2, 0xd0b62cbdebfb6490, 0x9705d796ea15a3ea, 0x64a4099a7d187481, 0x05cc7b1620a02af0, 0x1431449f159bf4fc}}}, + {{{0xed17a74f399de313, 0x241205f6e9f0faf4, 0xf49b139415998071, 0x1350b8d96d30943e, 0xb774a77158d1de44, 0x12576b2bd0bcce7c}, + {0x898a821daddf7c45, 0x4a545450b7497858, 0x69c177c052f9f3cd, 0x497b87a77389e528, 0x49be6bfcc8490653, 0x0713f82ad5b7caf4}}}, + {{{0x4d04d670ff5d5a10, 0x97b95d34599daf0f, 0x74a8660bece7d0ee, 0x0d6edb83dacccd20, 0x94e4358a07cbc20b, 0x114f4407bcbaa130}, + {0x1b8822b540a6de79, 0x0c1457dc751eb4bf, 0x1d8b4f85a9511ee0, 0xc878b6749292e69f, 0xde7a7cd42dcfa838, 0x0e040373b7d77bd2}}}, + }}, + {{ + {{{0x1a59d698f13825ae, 0x1617f9220aa53d9b, 0x3e03023db714ecee, 0xdedb95d9b1c7acfb, 0xac9133508dfef4a5, 0x03d5f659d03b0cbc}, + {0x7fb02e0f6cfcf2d5, 0x641082653f9e4d1c, 0xf0151d16d47fa496, 0xd9f28b4868a469c4, 0x212ceef1befac563, 0x07fb7307c93acf9e}}}, + {{{0x76236195dcec6f95, 0xf7505aca4500f556, 0x607f9faff6e3cecd, 0xeb3bab7b24e079c1, 0xf624a44e91d5f59b, 0x1800aebb3b84a0f5}, + {0x06c33ccac480a2d2, 0xb44bcaee1e140f08, 0xf94f4072db8ea7d8, 0x15650ecc0b22b876, 0x7aa341901b115f96, 0x1901066cee3698cc}}}, + {{{0x56387f79ec8e60d9, 0x02729e2af9dad9e4, 0x71b5ec43d6ac04c2, 0x04ce7e0ea5294ec2, 0x510bcfaa5be44753, 0x12e9fee70f5e3bce}, + {0x70dcfb584d9889fa, 0x820b2b21456dc2ab, 0xf9beae6b4ee2cbed, 0x9dc3e4cc9ca37a90, 0x45908abcf058b2ea, 0x013e354ab205a09c}}}, + }}, + {{ + {{{0xa74c2756b0e79f5d, 0xa061420ed5468916, 0x977d17717f0aff48, 0x0da309413f82b728, 0x86ba482c80e52942, 0x004bf8d1d73702ac}, + {0xcece4a8cadecfe90, 0xf81f941b9156d260, 0x8d6183baae8b75d2, 0x756facb4662f8ecc, 0x0ba49df68aa6f77a, 0x127ef007f88ac40b}}}, + {{{0x276b6519dbc12c08, 0xfb77f7b42bf6184b, 0xb8fcb602496801a0, 0x67993cb224fe9146, 0x6ea0c02cd02f09a1, 0x15fb7d501bb62d3d}, + {0xa264eb858f60b788, 0xeb5fe12b3d46681d, 0x41d975c959d8973e, 0xf88f79f37194d850, 0x608bc8f90a78e604, 0x0422c39ad9d57a6c}}}, + {{{0xb171f11b32ce7a8b, 0xdb5e432ed4f43680, 0x280c3cad3dd0ee31, 0x22c146631413c415, 0x99efbaf57616679f, 0x11cb19d5498f1d59}, + {0x3e6330b5b81df5c6, 0x0fa0a74d3e47252d, 0x28b70fa6c679db4f, 0x245d83f12a311928, 0xd8e518713146d2c3, 0x0f53b3c388096d6d}}}, + }}, + {{ + {{{0xa531da8183111ee3, 0x8cf4dfa0891e8533, 0x9905c5095a49eb71, 0x485dad2564ef7b31, 0x1bf8781cb91a7c54, 0x10a9aa0cdb243c56}, + {0x5fa3b960a8fc4193, 0xd046df883227613b, 0xcafe91b2fbcd606e, 0xa9d97a5035a1bc38, 0x69e85859af7e7703, 0x0fcfd20c7a2ea8f6}}}, + {{{0xa577042a9e9b5f5a, 0xc5b395660518f305, 0x50420971ee9db33d, 0xdf70a0015972a994, 0x4dc246a075d834f0, 0x0f1f65745dbbdafe}, + {0x7f979648b043f452, 0x0e91813a80d8da97, 0xde968bf90d736b89, 0x72623c8f6b057d19, 0xe2688413998ae1a0, 0x09b59391eccf982e}}}, + {{{0x471d8d9a7fc4e976, 0x17518a9dddceb156, 0x3a4d155b831b6a1d, 0xad4065706d2b14cd, 0x5ddb8c2c7b2df4ed, 0x19b69779644aecf3}, + {0xa187d9c92cf453bf, 0x88348a83c8b99159, 0x30a5694aeeb019ba, 0xa7ba459086194a25, 0x1c03ae5599e48c43, 0x0a8c90ad3c0f0379}}}, + }}, + {{ + {{{0x5ad2f7c4a0b31425, 0xf8c58daf49bb53f4, 0x6d14d82329caee6d, 0x68fe7d85336d37b7, 0xa3c0f28003ef36e7, 0x10e8494e4d07fd67}, + {0x97dcf329c63a85eb, 0x33d9d2740ff6bdd6, 0x034e2bd83f6b6a26, 0x13a8541ddafff233, 0x054fcfc33965e843, 0x1095be59ddeae5ac}}}, + {{{0x92e2722b3f3b6e49, 0xa67bff7915381492, 0xf7429f4f530f8b93, 0x3e39ae8382f5d311, 0x74e1ce8cb4e42bd6, 0x12d22191fce48928}, + {0xf0596d0e21c24438, 0xb2b36e1ced3d95f6, 0x5da7fd7ec22536ec, 0x002ba5fae506d536, 0x2e848ab8ce9c5c8c, 0x04fba4c7aeb4b8f6}}}, + {{{0x91a96f89ed078433, 0xe24f6bbf83f607f9, 0x9a0f298bdb1148a2, 0x3d4ea1b65729c03a, 0x73926e8d8b48202a, 0x0dbf5dae75f1f191}, + {0x081683830ecb7c09, 0x2acd639815f91887, 0x2222910ea86f6713, 0x453446e82b206cd4, 0xc77872989e4ed24d, 0x0904ae6b917269f2}}}, + }}, + {{ + {{{0x4f9948573092a880, 0x2eaacc9c14288161, 0xd2f753fe4cbedda2, 0xbfe2f36e10404dda, 0xc1d7bebdbe673f7e, 0x0971f6d7037aa64b}, + {0xc0110f7e73cb30ec, 0x158d50c29068b5fb, 0xe45948ddcb7a667d, 0x621f1a5114dfadd5, 0x11c66132c99fd083, 0x0b109c98c9528008}}}, + {{{0xd1a180024d0961d9, 0xfa969805b17e1db0, 0x6e28b763620022b7, 0x90cd7b74ad82f22c, 0xf51f2532a36a9d7c, 0x14f8abcadfb41e84}, + {0xdc541427459003f6, 0x62971c2020a2f21d, 0x7daa0b118a9b7ff4, 0x204379a850292548, 0xffd879fee9c438cd, 0x172ec8dc1a9fda1f}}}, + {{{0xfcd378951575efba, 0x7b2382d7083ef433, 0x6b7dba24f795bccb, 0x9d87a0b2d54bead5, 0xdfa07e852047a8db, 0x1771535a6c2f8407}, + {0x65520a88b5f75b2e, 0xea32f31724996eb1, 0xb0bdc620cb78ddbd, 0xad93aed9afec3ae1, 0x4c3f45df993709cf, 0x13c703edfd1b4d01}}}, + }}, + {{ + {{{0xf099edd5fb9b116f, 0x66b3a1986aece8ad, 0xb4848f08d67f3477, 0xb4a8c1b5adb3f630, 0x48d2a16b48462afe, 0x15e2b936c6ebbe3e}, + {0x1ffb23facccda5ed, 0xdfdf0e2ea36098e4, 0x8f93e1448ac92830, 0xba870dab1edecd55, 0x5cd82cca76419fa5, 0x11f32a9e0125f6cb}}}, + {{{0x384a009ce255ef2a, 0x6cde58f762b6d2ca, 0xae5c6c5d64d2f6ab, 0xa713bb6bcf85501c, 0xc78466918eabf83c, 0x1551843d6093ea8a}, + {0x60a59e0be92fa486, 0x71a4b5f615d49987, 0xd32ff5284d769e51, 0x96cdb332881ae259, 0xdfb2b2f5a3eb35a4, 0x1404388677735d4b}}}, + {{{0x2094e3cd8712808b, 0xab1a610539283282, 0x53ed72b6e642e285, 0x73a92f87fe40348c, 0xba9bdcabf2ebee8f, 0x0ce45bf6da3a720b}, + {0x2738a96976282e3c, 0xef89fad072e9c6bf, 0x78719d87b3a7c426, 0xd5140a6daf9945cc, 0x6b03e7a7ed83366c, 0x0f29d7b0a7a02872}}}, + }}, + {{ + {{{0x6eb9728ff79a1915, 0xab8e847c2cfd81d8, 0x146c520ce3aaf1ca, 0xec67dca0cfad6c25, 0x6457b7c2a2df3a0e, 0x0b51325d9cce14dc}, + {0x17290cdb23dede6d, 0x5f8c8afa08699a8b, 0x830b024cfea11ee6, 0x57d98136b654126a, 0x1466bf30e4d25c5d, 0x12e8447d6904c90c}}}, + {{{0xeadcfc64ca0fd9a5, 0x4b959e3b9d2ec095, 0x9381c72e9f7a5b9e, 0x46d9568707f3e392, 0xc43c8f2fce452673, 0x19cdfee655b067eb}, + {0x5d71d24f2d4bb730, 0x8e091f2ea90ec6c0, 0x9773e16fef95d899, 0xe6928268799cec16, 0xb9db2f8c416bbbad, 0x0f713f764fdfcd0e}}}, + {{{0xedd6f17f6772cbe4, 0xf89bd5a366b31dd0, 0x005a7732c7f13a43, 0xa6296065c44730d0, 0xd8e1f125d962c6e6, 0x094b530ecab0afb4}, + {0x281b5fac45192a77, 0xd502dc02a17b0914, 0xc754e77cd3e32659, 0x1072e198bf1691df, 0x369b440e760f5924, 0x0016edca65f2e573}}}, + }}, + {{ + {{{0x7d284522dc77f18f, 0x1332e5249fcb028d, 0x63596b240a87298f, 0x6cdca748897a4c22, 0x75e881b96992573a, 0x0023d35ec0be1ea0}, + {0xf4e1db4e53276b64, 0x3d8ecd5d6643afc2, 0x734bf0c52c4cab0b, 0xa2472741732e5d2c, 0x64518b7839b4e90c, 0x067c6a0fe0d84b14}}}, + {{{0x6969e5c3a1f348f6, 0x663849ba51820aa0, 0x9c5fdafdd909c123, 0x33b275f7b564b41e, 0x1ee3a7c449db0bb6, 0x15b73f4128f04377}, + {0x3ba7d7e9525f5519, 0xf47017813094418b, 0x0bcee5840c365420, 0x5d714af71046d76f, 0xed0a5bff1a5f9162, 0x0567971af33ea3e8}}}, + {{{0xbf00b22579958bfb, 0x78ab229093f78e25, 0xb765d6ed128ae6bb, 0x36fcb3490422923c, 0x5e26b64a56de140d, 0x04a485c091ab48d3}, + {0xf9b32d194491e178, 0x5a330a00fc9314ca, 0x5ba4aea3ddd5bab7, 0xe8d980d6e8a63104, 0x09b757f5138cd0a2, 0x0d50eb8d0a129324}}}, + }}, + {{ + {{{0xb493f630eef6fa7b, 0x7f8fbb5ca1d6bb63, 0x0dbec76d0dbb0be1, 0x0f7d1ed6bf09e158, 0x5cf9fa797e47da85, 0x00ca67f06438aa1e}, + {0x41d2d497f888788a, 0x189e894359ca6f58, 0x426c2e0c285e0676, 0xfcbb55acc2b5234e, 0x027a481f90878a86, 0x16a87c0c0c7be0ee}}}, + {{{0xfad1ae9f351d746d, 0x3088bce6cbac2013, 0x0da2d1f5093e409b, 0x77a1a1f4a4001154, 0x7e83a72e90e980c0, 0x16cb2550cb51bc43}, + {0x2a99802c8af680af, 0x1b0f156db6ee077b, 0xe2062be7c2f4304f, 0x7c01275ba6937fd6, 0x5edaf458954e9ead, 0x13c6c1a41796bd3a}}}, + {{{0xaec7d87f8c57acfd, 0x37f8d9c2a891634a, 0x97077f471514f8f5, 0x7241efe91e7462b5, 0xd85fa510dfd017fb, 0x16990a4d6b5436e7}, + {0xc70d61eca328c018, 0xb9fe631855c1b571, 0xf84f065583fb2349, 0xcf31b5f66e68234f, 0x8e4a5571cea8f8fc, 0x0426ae2890da1514}}}, + }}, + {{ + {{{0x7b217b89b6c09a62, 0xbb73fe2621162852, 0x580c72f848844f9e, 0xa8a32cd8da82fab6, 0x9456c6833c50ea68, 0x0a927c0569fb96d1}, + {0xf9c0e0699c0faa47, 0xb0f46aafe5665414, 0xd9acaacc3e051537, 0xd3cf12dbe647e85d, 0xdda8908993da4114, 0x14bf6574a5ad7f52}}}, + {{{0xa2c63967087d1916, 0xd3e7e7771c775a09, 0x3e639906efa4a8f4, 0x5412c804b68d3a89, 0xa0ba09ccb9d18940, 0x07ad285c15a32372}, + {0x17967f71a625d2e6, 0xe8c2fa51a632d434, 0x698d2ccd1687b870, 0x028a863d110a1a88, 0x3ba7cf82e76ec1bc, 0x0b9843121ad083a7}}}, + {{{0x5f05ecf6a60bd86b, 0xc737767c9eb04fc6, 0xc5773b77a7ff3325, 0x17fe18d366f388e7, 0x87311d3456c313b1, 0x145823d0e764b4f7}, + {0x655ca3827ed00ed8, 0xf83685f1fad08326, 0x48a04cae46bc2e1b, 0x45ae806e0683eac3, 0x89ddab3904ffde24, 0x19a608d1a02e2148}}}, + }}, + {{ + {{{0x37652411d418c12d, 0xbab78e364540502c, 0xf98cadfcdcf7802c, 0x778b8b3ab5aea511, 0xb9fbe87afcad2c94, 0x0adb83721d62e17d}, + {0xd5d1063702266bf4, 0x806d6546597520af, 0xc5cab81cc5105314, 0xac9ecf7ba42fd539, 0xd4164888b575a6f8, 0x058bf6a9d870bdd0}}}, + {{{0x0db3e9d0dc420f85, 0xaeaecb76018c2ea5, 0xf8f1e899152d9a08, 0x7fb587bceda84ac2, 0xc2624a794c565344, 0x0fb2bd8c48b0d551}, + {0x8fef7aa048b85372, 0x8a5fea895af3f2db, 0xbd05a9d37b425023, 0xfb2dea38bc462da0, 0x6c315a87edfa89b0, 0x05ce785dcdd3416c}}}, + {{{0x28b3c201f15cf273, 0x57ee54b6fa8e881d, 0xef785128f6f3279e, 0x6221c445573f534f, 0x2d3b703b7513558e, 0x0a89bfdcaa17cbd5}, + {0x6b71b945552adeca, 0x20323851a9f0ea03, 0x3430b6f43f626523, 0x924eb961c63a499a, 0x0fd66fe1cc31ede9, 0x0209f144f574a063}}}, + }}, + {{ + {{{0x63b3e965eaee8e8b, 0xf991da2ece70b844, 0x54040c686578958e, 0x4bb62297ff0b4403, 0x4483160b57384b43, 0x1947d90228e659e0}, + {0xa1848e502551965f, 0x1706dd866c36db0c, 0xa0c6bdcbd4ec27cd, 0x28f04decebb81077, 0xeca06cd91a505263, 0x0ed294f92c6d6b09}}}, + {{{0x7eda412d269b1d61, 0x4a8db417315eb7be, 0x7179208f40fd1605, 0xdb74c3c025d0f713, 0x780d96c6c0d343cd, 0x02dda678a3d99deb}, + {0x72a986c1b567dbdd, 0x89fac54b26295b2e, 0x68d6fbac1774bc09, 0x350860025947ced4, 0x388b0aaff3ef0a79, 0x12c749a7032fa3eb}}}, + {{{0x30a0ec171d59a55d, 0x8e7308a8f5933e92, 0x162080070f0179c4, 0x71e1c5058000c2a6, 0xb74247b12f98248e, 0x078e9f07be89777c}, + {0x99c67e97d197f846, 0x78b866625fde4872, 0x680821189916ba99, 0xba5ae42a4934a918, 0x7db7cfc325854a65, 0x09a77bacf66368e7}}}, + }}, + {{ + {{{0x8dbe8a6ecfa281ca, 0xa739d2e2fe567338, 0xcec1a04e0abbcff5, 0xbdd3e0294cefdcd7, 0x208bafb49a86596f, 0x03a69c163aac66d1}, + {0x887d2369a09fd299, 0xe6a1e2e3011d4db6, 0xb5c167a3d3bcbb70, 0xc2de29fca0f7f865, 0x6aad7af11d72f642, 0x158f1bdac40da54d}}}, + {{{0x5ae0e5d52303c0dd, 0x380cb8f07553f82f, 0xbbd8e045684ec30b, 0x21190dfc806e2416, 0x4b7febc7945b9896, 0x14393acf68823eab}, + {0xdaeed5ee7918dda4, 0x6bd187ac7cf05a06, 0xa51c503a023a7bfe, 0x61c9cda82b7c87c6, 0x464c73de23754fd7, 0x19d12d8be333398c}}}, + {{{0xde03ae67803df29e, 0x75733bd16e8949dd, 0x00296fbef4920b1c, 0x1aad7c8f372d011c, 0x0b0a4c4b0d53b1fc, 0x002f41eb43b5b5d6}, + {0xd95202f744ab6fef, 0xea2544bf80fb7edc, 0xa664406401824d99, 0x0ac3df929ebc0675, 0x765a853ad5926932, 0x17b5af976eb6f04b}}}, + }}, + {{ + {{{0xe5fb3a53add9edc8, 0xa907870f910c2681, 0x91a807cf79dfc8f2, 0xb65bfe880c16266c, 0x035e96b9224b0678, 0x03c773ff6f1e6905}, + {0xb964b3265b5e39d7, 0x70994f6a7b024e48, 0xdc9326de928b2905, 0xfa57c8d3a7002249, 0x9f737bd05f5d8ac0, 0x13d62804fbde81bb}}}, + {{{0xcc06ed5465240389, 0x5e7a8ca6947b6f0d, 0xb510a630e9373e28, 0x7ef9d1bb1c179e8e, 0x026635a4b8feda2d, 0x075ffa3bb26af9eb}, + {0x6236765fa1ff17c6, 0x0f3e5a859b64fe25, 0x1490eca72561a4e1, 0x4cd667573616833f, 0x72c5d4dee924be40, 0x1748d8ffc2efaa4b}}}, + {{{0x7220a856658c233e, 0xcc3ac8b512c448ad, 0xf3adb2a11d2f64e7, 0x03d7e682f064ea26, 0xce0b31573eef036a, 0x0a402700259f5c68}, + {0x5cf6578a6036059b, 0x04b76d0d4dc5d52e, 0xf3888d2c080d32f8, 0x703011fbf592a410, 0x44586432a23a3fb3, 0x0889c08fe233cc6c}}}, + }}, + {{ + {{{0x7d17fea3dacfb8a9, 0x1aa264ba7572e07c, 0x1aacf076bc3d69e0, 0x3160c630231a681f, 0x47ddba09a7c26da0, 0x0b8bad7b14ca787d}, + {0xc2493b47ea212813, 0x3948c3fabdb2c522, 0x9dece266c2fc1bb8, 0x1e336f378ff2b213, 0x93b05e413b22fe82, 0x02647bcee61a1228}}}, + {{{0xe5e9bd9002abff5b, 0x4e179746fd9a2a75, 0x1a09c895d89f3f18, 0xde7beeb502c3c632, 0x01fa3765b8e31716, 0x17e891fee51c8c80}, + {0x9e584c995613f034, 0xac43892062fe97bd, 0xe904ca216c199275, 0x16dadd1251c3c73e, 0xc8e16d0e4bdf01e8, 0x190d339e715ebaf8}}}, + {{{0x4a97e61f7dbedccf, 0x4e79a3b7393ed996, 0xe57dace3a3627e04, 0xf134196a3f7a3479, 0x15101ab2a5b0998d, 0x18b7eea09ad8f725}, + {0x40f887b559262b6f, 0xd06e9fa747451f4d, 0x22de29303cfa9e4e, 0xaa6dcd618e1680b4, 0xfc2a48ae9b75da0d, 0x0e3fe3a9ae786211}}}, + }}, + {{ + {{{0x8d442d6b4046f186, 0xf54fc41d689d481b, 0xd131e6f30a74c8b8, 0x77dc52984d95d602, 0xb06654f3fc6e1bce, 0x029ab00d2d1d421c}, + {0x2c991754ecf7cbc9, 0x3e8fe4ecec389d25, 0x818b3383ad8f5037, 0xaa68f539919e015f, 0xf1498973fe856400, 0x17e38a634c40f2cd}}}, + {{{0x6ed2e2b74e88a9c0, 0x5292359d9237092d, 0x579e087ba6e8c5a7, 0x8185b65770d271cb, 0xfb90a9d8c7279d89, 0x18e7d5db271b9217}, + {0x8d81f25dac535aa2, 0x51ef1ae3520588e0, 0xa8ac42d03d91be6b, 0x3cebf1efccb43ec6, 0xa559b8c2aaaddfba, 0x0137869785e10008}}}, + {{{0xc5bdab0c5fb4d060, 0xb95e91d65fbe5291, 0x65db173e06555739, 0x60f7fd49dc51473f, 0xe8583447b582143f, 0x0395e700f6c3bfb6}, + {0x5020b5bac1b5150b, 0x940dbb19272e55da, 0x7000a98391f54da2, 0xc52c40f420caa172, 0x787ce0cf90baee7b, 0x0e265d7848179883}}}, + }}, + {{ + {{{0x5d9788bf5abb80be, 0xec46f946b671ac3e, 0x806b922c1edaf71e, 0x2b44aef38d4fbb20, 0x22cf11ce700d2860, 0x100659b4e81a68db}, + {0xa73d4523a230a301, 0x5836854905423986, 0x7c307830d4709655, 0x6890b4abac007a0e, 0xb1c1ae24409fb969, 0x09b051d68fb55b22}}}, + {{{0x968f4c5d548ca858, 0xf543834b3d6e8731, 0x42cb7f8876975b67, 0x5c83c09137621fe3, 0x58f4c29fcbb9f075, 0x0e7df16418f8fdba}, + {0xffe95e8a962bab6f, 0x1eb5682e8d7554dd, 0x2c21cf05c1e90708, 0xf070bcad0832bdae, 0x34ff5f3df09fe81f, 0x0f750c29eefd7e98}}}, + {{{0x0aedd0feeb6ddebb, 0xf14b5048140b11a5, 0x01157e0594954533, 0x87fd65019b33dc73, 0x53b6a333e852fedb, 0x0299a3f7e609f629}, + {0x8dbd9402c7e9ec2e, 0x504a23ef23a0b496, 0xe07abc542cb9cb9f, 0x93714a46da7db9c6, 0x440522594be3ac04, 0x10196aa082aebf75}}}, + }}, + {{ + {{{0xca2f92763e93ac5f, 0x02f9e213d06daeff, 0x045d2a98e510d696, 0xa0758046f0618fd4, 0x77ab084ed0d9c59c, 0x0e956daa82330484}, + {0x7c359e32632da650, 0xa92363a3c39553a7, 0xd29e866642efd2b1, 0x1dbacbe46a949cf3, 0xec240fc04d42a4eb, 0x0d146c0d7fd2489d}}}, + {{{0x7b6ad91a5036fe62, 0x1692b936d6291a9c, 0x3e9369a8a7d4d470, 0xda8e06588ef590df, 0x9e4a5fa27c6d705e, 0x12fa0878777794fb}, + {0xf780d09bd5d490cc, 0x49e7b1b4acd055f5, 0xcaa6baea644ece5e, 0x9ce1c898be520792, 0x884eebe3aebc6156, 0x066e9844a4b65a0f}}}, + {{{0xd4f3c46c68f189ce, 0x26b64df20394bea8, 0x255d020f8048e922, 0x3511be804bbfadf7, 0x39d8dd7420d2caec, 0x17556b504e9aa72f}, + {0x3eb64e7c58b83da8, 0x885aa7f683f2c1ef, 0xfd1a1fb22d627805, 0x3678cd83a9766320, 0x8e79f1d0f87b546e, 0x076b06582025f55e}}}, + }}, + {{ + {{{0xad10eeee09ccb27e, 0x52b428a9013c5d44, 0x2377b2d6765b8a21, 0x0e54e0ed78d86136, 0x95a9855ebe13b62a, 0x0eb23aa3df722ee0}, + {0x55b3804692b8a62e, 0x15b493258d6ac35c, 0x5bd47db33fcde5e4, 0x4df15025b8f5cf58, 0xd9fd0a8f79e36fc4, 0x0abecdd26aaa0757}}}, + {{{0xb48c266de6ad6f4d, 0x31830bcc6698826a, 0x21c49a7fc48d224d, 0x488941c6b68b457b, 0x082994a488d3d177, 0x0f893263dc472c92}, + {0xf619424a5dc6c18c, 0xab36bd0356821549, 0x1caf724ab25a8eb7, 0x493c84d99ce0da8e, 0x5fd315da8ba48230, 0x0ae745c6698b04c8}}}, + {{{0x9dfe6df4aacf8ba2, 0x4a99ad045e631a41, 0x7724162e88ff93d4, 0xcb79b3a5bf182c28, 0x5c107a7d374d3689, 0x0ff666c9650f3d47}, + {0x0d0df8f9eb0bc5b0, 0x34c344b89b9a9907, 0xef535388a59cafeb, 0xf1d947ce8bd3c682, 0xab81843645ef95e1, 0x1744c4b413513207}}}, + }}, + {{ + {{{0xa174b98ed32fb81f, 0xe04f31625fde3c83, 0x0bf105ad304d52ae, 0x7c2835432e9f763d, 0xfe65876dcb9dea33, 0x0323e80a7fbd84cb}, + {0x4f88f64bf269032f, 0x60b4fdd2c5df67c2, 0xbb5046f7ce0f1030, 0x941d95495344de6d, 0xfd3b7e86500db407, 0x17e1fe28b9bbbe84}}}, + {{{0x8d621bb1fcafdc7a, 0x4cf3395343543b4c, 0x571492824bae2e90, 0xf89f8056cd8d965f, 0xddbc44a287696e44, 0x1557ce166fe61903}, + {0xd7b7eb6373ed6acd, 0x590b40be4e569fcd, 0x577250907651e51d, 0xa71e724438a41f4b, 0xcc93c6992a2166b2, 0x1658a100af4ccc50}}}, + {{{0x649e5667f47f0ad7, 0x6fdd04e2adb10600, 0xb0702db7ff301430, 0x509421f0dd21e0a7, 0x20c79ecabafb1640, 0x02adc372cfdafaa5}, + {0x72652497957333c7, 0xdb105d25bc0cc316, 0x715d562634376cb1, 0x256afde081c9e83b, 0x87bb694483e33a30, 0x085dad8700d67ed9}}}, + }}, + {{ + {{{0x9b6e8e22d4afd60c, 0xd185810344f69487, 0x65f6fd48148b2ce3, 0x763127e46ad48b0c, 0xda4e8c98897000b8, 0x033608250f3430b5}, + {0x80d9fd23531ac9ad, 0x00b6c1e9784eb17d, 0xfec7e5cec5bd54c3, 0x86693910491cd246, 0x54da3ffc293c3612, 0x03ea93af4bdf4322}}}, + {{{0xa4e84bde2e98882d, 0xd9a86cbaf6c50c3b, 0x4634b4d33a20173e, 0x5d3e09e6c33fc6f8, 0x4dbc313c5a717a09, 0x0799128645f9b7d3}, + {0xaf7990114a557b39, 0xd1c538bf2a9e1c7e, 0x4823d18a22463159, 0xda4a8b106e8fe03f, 0x50d53e4cf5395dcf, 0x187d765026963f12}}}, + {{{0xbb540ec6d052fe7c, 0xfa5c6dbc16609f80, 0xd0626444ac5efd09, 0x875e673a614dd8c3, 0xfa72913c3ad4b00a, 0x11e89699c6f617bd}, + {0xcb2a3afe471a18e4, 0x0624bf3e0e4bc730, 0x2edcd3dd24cfcaf8, 0xc117cf9156430004, 0x756a7d6386208997, 0x18598d90314979ef}}}, + }}, + {{ + {{{0x1e3b293a3e5bccb4, 0x8ecab55a729325f3, 0x3c74303727a67920, 0xd9ebdf0db9f90016, 0x992f50f35c625422, 0x03991ff04ff18dc1}, + {0x3363909e1c0e961b, 0xdf1c2dc01064e43d, 0x2c78728735851ad5, 0xb1a56d89bb225728, 0x455d1ab4d6f0860a, 0x03163519ae1dde44}}}, + {{{0x1f48e7f7a3a308b4, 0xe24ee7553701d4b3, 0x7313ec50860532e0, 0x503f62cce8ded0fe, 0x23086d65338496f7, 0x0fe5fcbca7aef3fa}, + {0x4634b5c1b2be791f, 0x8c9ac1fce9163150, 0xdb04e7005ebc233e, 0x7fd875509bb4239f, 0x29ee7ccf66b1a65e, 0x024650947fe1c68e}}}, + {{{0x1e62f55987bcd34f, 0x9e9cc4799fc47988, 0xfd4b387739a9516d, 0x02c7d43c60e89e0d, 0x749cdf0f2536aae4, 0x17d8bf15a70dd9ed}, + {0xc02125b5e79432a3, 0x8926c5d2b88a65d3, 0x70c965fc07881412, 0xa520af41ea629586, 0xd35a392bd04048e7, 0x130e4e3c82e77e14}}}, + }}, + {{ + {{{0xb3304720eff7c7e1, 0x96eb62a57df3ccdb, 0x4fa8d864bd3bc823, 0xceab9e0e64327a64, 0x900c2c252c85fa03, 0x151c668a50754813}, + {0x801b58d32087abcc, 0x8e41224bedef2c94, 0x4e2dd94ede824ae0, 0xfdecff4dce0811f6, 0x0ad1cadf05f9688e, 0x1246848751741254}}}, + {{{0x950db3531288f124, 0x1e0487c83e99e4c3, 0x961209ef60b7e184, 0x70ef0ddb61db22c3, 0x4cc1476322c4aad1, 0x07d76a106cf34d1f}, + {0x23457f164940d866, 0xbb70a9c353ce7e98, 0xbae4a05c2708536d, 0x12e971eb6312811a, 0x6ec7f67b45b3f435, 0x1668768c52807bb4}}}, + {{{0xd31df6d19fb3c5fd, 0xb2682b5071c07261, 0xa320ba8d242f98d9, 0x4f0838f32effb8d6, 0x9b13cdf2c0926676, 0x18ac8b2bb8655813}, + {0xb76a57f0aab7c50e, 0x4e9621f40f9a306e, 0x80e545530f9291ca, 0x7c57dc7e15227669, 0x6dcd7081728eb25d, 0x00224e88f73300cb}}}, + }}, + {{ + {{{0x160a2f4e1cacae70, 0x1c880df4388d85eb, 0xdbfc8ddc059a047d, 0x12c6c9b8bad5a11c, 0x272144e26b4295db, 0x0d68e67f50a3a3e0}, + {0x37da89d499d3406c, 0xa16ebc109377afc7, 0xc9198cb6b4bf5098, 0x4ac2102e1caaad29, 0xa4c2a4a3b74c144a, 0x0de4d853e1992905}}}, + {{{0xe8952f6e128d2b1b, 0x3391a75a7d25c3b7, 0xc9556e631b12b1aa, 0x5bfac21bd64935be, 0xedc6737d077bbe59, 0x0412e06e6d004043}, + {0x922666d48cce8dcc, 0x2ff2eb5cbc845adc, 0x3717693e2918fbb0, 0x3c93dfc0a075d4f2, 0x73899cebbb188cdf, 0x140cadc135dddedc}}}, + {{{0xed9867204f136387, 0x533a86154e31b44d, 0x648680bfe0e01f38, 0x027014d5586aff77, 0x23431a689d9b52e4, 0x08efed7fe0d0195a}, + {0xa3b1b4a456fc1897, 0x6c711789095bbd89, 0x2a5701993fdbf6f6, 0x5fd661d798c6476c, 0x5e3b632a9b8edc49, 0x052817a8f8dc0ec8}}}, + }}, + {{ + {{{0x14612140cc055f01, 0x9789d44e1cc515be, 0xc5ea6765fb4751f8, 0x6fa1f5a774d2aaee, 0xf799217dae5cf282, 0x05098c086f4a199c}, + {0xd910b14b2025365e, 0xdac52fac02ebfb9d, 0x17985a2edd721eed, 0x5ca39be5cb55de7d, 0x33c01956728e22ef, 0x1357db688cc931c9}}}, + {{{0x6e374537e057b62f, 0x98d0f3fa36e95180, 0x51a13455d5c3c74d, 0x9263a252b5f26e48, 0xa2b1ef6603e60175, 0x14ea5990c03f9b2f}, + {0x52f86b96384763b7, 0x5e099ded661451b2, 0xdce3d85e55d576e6, 0xd5c88f98b0daa27a, 0x0b04b9d5c3a66b3a, 0x01ec2f8cba39eeb5}}}, + {{{0x8a3b090c4fb62660, 0x8247d9405e0bfa96, 0x7c9406f43af3c6a1, 0x5d1b2967fb801f16, 0x287be8adf57f1529, 0x085fc81a482bb5ce}, + {0xd121ddf34e3d0d52, 0x793080f9945fd68a, 0xddeff70e304f9323, 0xf7317d7f1a886614, 0x4cc100c140398b5c, 0x0a9c44eace85c5e1}}}, + }}, + {{ + {{{0xc7af1f319319449f, 0x758cc456e3df9234, 0xaf184aad25340727, 0xe355c32c69c95ba0, 0x366fdd4a3b5afd43, 0x03003640e74d39d5}, + {0xe53d9ad739d0e13e, 0xb396d4c3ecdb14c1, 0x0e83c37c1685c67f, 0x422b6c30fd66734e, 0x2ec55aaa245664ba, 0x17da652fca1be3ee}}}, + {{{0x208b81d56afe2cec, 0xe20f2e2354d9c993, 0x10dd41dd9821c468, 0x11ba09c13896851d, 0x3740a95b64456262, 0x0d152c0c206d3371}, + {0x9c1e75d6012a294b, 0xac58015cda248476, 0x8ac1a47373a75125, 0xeb134da500e46e92, 0x9e2a63249c361031, 0x1850b976f5448136}}}, + {{{0x8caa0cf9e362975e, 0x6ebd1b142c43ad82, 0x0d111c3c0e57a6b6, 0xef87504e4558f90a, 0xd1401852270201a3, 0x070e7eaa019766be}, + {0x3def69ce00a644cc, 0x6148e3b898252016, 0xffa18376fe170dac, 0xbe4f5d367a3c353a, 0xc6c1bfb5e596b84d, 0x12bad8df129318ae}}}, + }}, + {{ + {{{0x8b119b3921585cbd, 0x783035e889d3bab6, 0xb093aaaf86dcda36, 0x7727cc83c007ccec, 0xa52cf3563eb659d5, 0x0a246d2a293b9336}, + {0xaab695a49e09385c, 0x0be17519ae764329, 0x4ee3d06418e3e109, 0x07af58480bf65f9b, 0xc7a626bb9e89a423, 0x091a5f60e90faccf}}}, + {{{0x3d2b47b377705e24, 0x14417f4cc5bb879c, 0xebf8bc1b54842d5f, 0x8611ed0aa202a28e, 0xcb5335d5558e644a, 0x0da1aae56b441cf6}, + {0xe9e2667c22c672b3, 0xe8005aa120917c51, 0x4597ffac0886c36d, 0x84062dce6a4e258c, 0x49f9ca6c4b4851bd, 0x104973b6200a2c37}}}, + {{{0xe8dafb78d89fc410, 0x3ad747d96836bef0, 0x811dc442b6ced33e, 0x0ec071731b22420a, 0x31a7decae98e728b, 0x037ad5fa0c3d7e94}, + {0x2af0bfdfd0d9129f, 0xdbd45537a3f03ae0, 0xb4d2b3d424df6d4d, 0x0ccd4d614aaf1abf, 0xe7a11edc131bdcf0, 0x19e157461c5c03e1}}}, + }}, + {{ + {{{0xa72ee47afe4be25d, 0x423f0caf2352b4e3, 0xfb2221620f3714e7, 0x0b1760ef7b54983e, 0x9a3d77533ad81bf1, 0x110a1dfdaf23b58b}, + {0x4312e31001df9e64, 0x172df34f8bcea649, 0xee789069d431a70b, 0x066952b874a32480, 0xfd08e9161b38bd8d, 0x0244d139f487cb21}}}, + {{{0x9c0434c068561484, 0xffc8038eb4bd16f6, 0xda2d57a00132ece4, 0x7940ad1006ec202c, 0x380e020ce2064709, 0x116cafce6a9153d0}, + {0xed801065de6a3ad2, 0x37dc9025a0d9ee52, 0x0f7e6a99ab4f05aa, 0xf29d6285cb1cb21f, 0x42dcf3014bc103c5, 0x068176ff68c6b385}}}, + {{{0x2ea8cb3b60dabe27, 0x105ed715b47f8fa5, 0x4b4043cb6ac0e240, 0xa38d9c61742b7ae5, 0x720709cd80735704, 0x02c1cbfb1cb6acf4}, + {0xfcf0bc6fa93dacfb, 0x8d166b7311be7b28, 0x21f4a255e8ca7c46, 0xb2347d3cf650efd1, 0xe810f3ac0d30bf9f, 0x11b9df6f9480e169}}}, + }}, + {{ + {{{0x2f314fbf38a16f58, 0x958e2a3c8d5456b5, 0xfa2fc2adff1552a8, 0xe5ca85ed96be3f16, 0x7914ecc436f91d92, 0x13efba0cdfdcc9bf}, + {0x2e8f03698455da81, 0xb31b4cb0b4d013ce, 0x0e3cecd9620a31d2, 0x135caf0802ce41b6, 0x1e5f8d86fffa9c33, 0x010783f4f03b4bda}}}, + {{{0xdd7dbfb1de1bb42c, 0x8cc89afde7c3fdaa, 0xcb92e0b848951b92, 0xf019dbfa0c4c3e5f, 0xc066bf3c43496831, 0x08fce119fe825c38}, + {0xab2919a9f6c5a723, 0xccad8578c3180ea5, 0xaa47ba0eaeddd41f, 0x6dec098651cfc4f7, 0xe9a444be0a2a2a7c, 0x1023a25db9b30bc6}}}, + {{{0x9bdb5380a3fda223, 0x13bc86b626aa9856, 0x7cb9f9928b937d29, 0xd9cd1b32ce652a00, 0x81361c36ae94616b, 0x01562171f8e91691}, + {0x8ba9856ca9ecc949, 0x92db615eee2b1468, 0x0e9ad7250dff7bac, 0x4fe9c6f71fe07de1, 0xaf66bc96af59b1ba, 0x0618eca99ceef429}}}, + }}, + {{ + {{{0x939993e0d6fe93b0, 0x389a456e0b2f60a4, 0xda0abf6915241e4c, 0xc5477e26b289a83e, 0x70e422c36665cbb7, 0x061da647889fa7e5}, + {0xf4b0c6c43ae3d006, 0x7c326dca4883500f, 0xb019e256a3ebed22, 0x1eae63b693a5d001, 0x53d1290961f058c2, 0x0f5abc7fe3a31736}}}, + {{{0x31f82f1fc279d14b, 0x05b511c2856ba361, 0x5b753cc35d1a0366, 0xa7918c38d3629ef3, 0x34dc31bcff99ca4d, 0x13ed4f00d1b2e2df}, + {0x95cb5462e71e7f89, 0x3c771787d7d4fe80, 0x83fe3748dfaaab62, 0xe3ecb99251e2f37a, 0x0dc9c3befde1042e, 0x124a5e56f640794f}}}, + {{{0xf041a2ff98b8e8a8, 0x5eaa94097b3bc5ba, 0xb4c145c29902bad8, 0x96d25b2cd2c30230, 0x7cf293ac2c4636ad, 0x19bf265d54aa70b1}, + {0xff70ccde63f86b96, 0x8ea730f5aec41e7e, 0x85557053b976963f, 0x244625e1c9f3a582, 0x5eb5a69ab6758d10, 0x05e625106dbc11d9}}}, + }}, + {{ + {{{0xaaf47a3d7b2f1e0e, 0x716ba744f87e56e8, 0x03115e57b49e0882, 0xc91122ab1cb6bee2, 0xdb0ea125585e7ab4, 0x1284ef63a409b8fc}, + {0x21ec2ac837692825, 0x89ba725173c39bcb, 0x7aff10d6068320b8, 0x73ee77e3cc37996f, 0x812c8f716690b421, 0x0ca8725cf03793af}}}, + {{{0xb430ab8748b79f12, 0xeee56feea41e1b9e, 0xce4c4273df3e4035, 0x09ad2cc63ec96b4b, 0xf838d069e9da48da, 0x0e5853c4bc24b697}, + {0xf873d15a4075adaa, 0x99d114153e16ddc1, 0x25df78639147ba32, 0x20f306083c5a7b77, 0x9e47bc65c22d3f3f, 0x0c6ea9169db9d341}}}, + {{{0x5d60f3822b7ca257, 0xc7ec16b658fe678f, 0x586cbea8bee781de, 0xa1cc39c30831dbab, 0xb7923dfcbca368a3, 0x04b14ea3f5c91f69}, + {0xdac166a67ec330bf, 0x7e3ec41bccc0e30f, 0x80ca3a8ea89d777e, 0x3e7b966b0bba5259, 0x0fa1873c340b509b, 0x0c850590ac76fc21}}}, + }}, + {{ + {{{0xf5f91b52a66bd252, 0x70fc6d723d0eaa01, 0xa7c0b3959cc49604, 0x8ebaf1da378031e8, 0x7915f506eddb5f7b, 0x094680281299e971}, + {0x94cc382782696ffe, 0x84ae3ea2c800dac3, 0x6a9baff3dc620879, 0x731acf66ff8287d2, 0x8be7f480cdc7e485, 0x0d09254fcceef1f1}}}, + {{{0xa46780c362e05924, 0x75d97842165e4e69, 0x1cd2df3c87f90e56, 0x91cc820d5f62590f, 0x14b43adc6efca2d6, 0x0dae1b166113e733}, + {0x6924dad563f0b04b, 0xa99503478579c9ef, 0x5f085ac17f828017, 0x30afef2e23115acc, 0x8f16039ba07d8af7, 0x176a1cc3ef0f474b}}}, + {{{0xe371fad12fcd953c, 0x74cbac497059eec2, 0x31691bc3bb8ed9a7, 0x110a621563604627, 0x20ba33ba447ea7dc, 0x121533698d493706}, + {0x3b20186ec8c2cf17, 0x2f4ea12d350c424b, 0x80b55437ceb8c2a1, 0x063d301c3b4f9d6d, 0x97e4916a722a69a8, 0x0f2e29a70f42babb}}}, + }}, + {{ + {{{0xb847d7ada0ae2ca8, 0xf77c39b124c80c8a, 0x06429a95875c43a8, 0xb6dd6bd7264e57fd, 0x3967a10cb11f2e1a, 0x0a71bfead522f527}, + {0x82f53b5dee27ad85, 0x35f134d41f4ee4c2, 0x809637816c46a4cc, 0xf1bb9c1bee2327da, 0x435da8f97492e84c, 0x11cc5c33e37a05c0}}}, + {{{0x8c4545c35183136f, 0x1667b2de0b64e4cc, 0x3a188784d9a1c7dd, 0xaf8115b849229ab8, 0xeb4bf01068d30636, 0x14ab74e243c3db79}, + {0x1d0ed6b25bafc40d, 0xfa3fae48ea3a401e, 0x9d56aeee5a3a8578, 0x6fd5390320a9384d, 0x6143b56f9c6deb12, 0x0ccc9b972398e660}}}, + {{{0x622ea5877a24b196, 0x8fabc004802a412e, 0xaa86707610f27850, 0xfec7c4a2501df066, 0xffbf616e0fe04f35, 0x13b092b0b90a8fd6}, + {0xac54b7c6e8826392, 0x6526a246c3bd07f5, 0x129cde8d5a33865e, 0xff626ac11b31590d, 0x7a3472bc2ea27d44, 0x09b86ea0534491e6}}}, + }}, + {{ + {{{0x8925219ff1b9a120, 0x15990adff5dc12ef, 0xaa944153c5b98e6b, 0x62d5c5d07db021c5, 0x3339ad869453f964, 0x110d4ff45e98034d}, + {0x6e08132e99aa2e9c, 0xcef813ec604bbde4, 0xd2aa347974241040, 0x44f6b9eeaf3a3fdf, 0xf67e222244ba3eb9, 0x1438d26cad6d2c1d}}}, + {{{0xb4f4312b60e4f939, 0xcd404a5f8977b58f, 0xe4cf85267f049db8, 0xdc4d14ff68cac369, 0x4b09a490f09f6abe, 0x0cf73ca85311cbe8}, + {0x2a38dbdb42e59773, 0x28d60d4f78dc948f, 0xa38c38a25382a541, 0xd81b02efef4f24fd, 0x8d008d1c4356d1ea, 0x04384bb3f445a737}}}, + {{{0x194243f53988a0d0, 0x979423e80fbe6072, 0x0ff3db9034e31f31, 0x8849f8397f473e90, 0x4963ca011dcc9f04, 0x0ab95b543e942dd5}, + {0x88113e7c2535c155, 0xb75c0748e7fe11b3, 0x9511ab32482300fc, 0x24fb2664ef6581f1, 0x6c3c2f6f8c492c0a, 0x16fc7339b6ed8bd2}}}, + }}, + {{ + {{{0xf95b677bfb07f105, 0xa3aefcd088809785, 0xe852f7502c27fa1a, 0x249f5c63daf647cd, 0x75a0acc66fa8308a, 0x06ee8d7cbe469d65}, + {0xe63ee539e61c38ea, 0xa04d4e398a38b97d, 0xe636b38f3d2b825d, 0x3589d7e4f1a70a3a, 0xd21f57eb9707005a, 0x06eb64bcf42e7503}}}, + {{{0x2ef6190ddb0abefe, 0xda261e5cb5d31e69, 0x135a103e10969b02, 0xf2df767b861dcee7, 0xf92b36e609ebee0c, 0x052b9de715d97984}, + {0x650ab8a6b0679a78, 0xfbf743bce0adf55f, 0x7328aef2c6a9e477, 0x292f2f22b8167c9d, 0x880c0550dab99c17, 0x1073d026c7cd7201}}}, + {{{0x4f4e481faa1b3c72, 0x9eee0868a28d5d0f, 0x0dedc019fc733484, 0x60cc3f687ac12c0e, 0x7f0df357722207d7, 0x1785d68596ba0bb6}, + {0x7b0ef16a7cdbee5f, 0xb5b075c87f8c5bdf, 0x5286395f07404f29, 0xac2710fbbb8a79ce, 0x381dbd226d561bb9, 0x17936d0f53d0ff56}}}, + }}, + {{ + {{{0x333fd268ddedb195, 0xecd7fcdf66f6fbae, 0x488f9ebf49d92d5f, 0xaf9759c3c2622d84, 0xf3ffbf73cb2af834, 0x0d85f233264c47c5}, + {0xe29ecfecd1e8ce4f, 0xf5cc4bb9e92e703b, 0xf3930394a61020e6, 0x177e5fe8ab739dbb, 0xbec30e42528e61a3, 0x16f249811c5ab8ec}}}, + {{{0xb692ea37fd710479, 0x24df3c1b4796660b, 0xaea4ac16ce9018cd, 0x93cfa5c91e67ccb3, 0xf2be0d4cdff42025, 0x08c2a7ee16a4b45a}, + {0xa08e6901ec90860e, 0xc588cc5b76986c88, 0x0b7fe22b69a57946, 0x92e04232d82e88ec, 0x646a1d91e2fd4694, 0x18ba09d1949b5018}}}, + {{{0x74784ff6d22d1a37, 0x2766fa0f3168c65a, 0x8f6c7535a70e2625, 0x46e36dbad22025e8, 0x23139392eb50df24, 0x11fdbb8a5dc7c4e6}, + {0xec557985b7737278, 0x31ce114600a79a47, 0x8868fbd2e4b84064, 0xe356a801ebeed6a8, 0x59564427f5868beb, 0x07b5f9967f6ff6cd}}}, + }}, + {{ + {{{0x2543cf877d837644, 0x2ef8ee355f123f37, 0xdfb71676c0fbbd5f, 0x554854dd85d3a4b1, 0xecb75da6b2ba5920, 0x0b67518cd56768f0}, + {0xa34c768408a0c34d, 0x63c136b7dc3a3df8, 0x5fb9ed3888c6089c, 0x5b6670960c192677, 0x46f77c17a3a5319e, 0x0c5817360e845cbf}}}, + {{{0xd68b6926f07bf20b, 0xf1a5ebabecb273d5, 0x019ea04f4fa1efbf, 0x56cb19cf3bd8a8ef, 0x107e5dbdfc30330a, 0x0abc86dbe1777b92}, + {0x43ec4a13f9fc31d5, 0xbafa12f3a334aa5f, 0xd15e9bda9c4b2da0, 0x9d63f7c0708b684a, 0xa801203e6e0da174, 0x05e040487334b009}}}, + {{{0xb8f959ecaaadea84, 0x0ce2bcca30e483ee, 0x4ff69c1d2a223813, 0x237d508f254f52d6, 0xa2d4b70ece1d7be2, 0x0a9abaa141b40b49}, + {0xd07974611f0aaa0f, 0x1aed558d5c11850b, 0xbc601acd73504ba4, 0xa3ec53cee3012af1, 0x6b0498bd2331693a, 0x10ec19ddc7825c1b}}}, + }}, + {{ + {{{0x28191960e4b0332d, 0x1054cf9507cd95cf, 0x3276b40c37569561, 0x8fd1ee7dd6a6b621, 0x48bca1ee3079ba43, 0x106768352a01d710}, + {0x4ec00607eb623ecd, 0x4ac3e65ed5b6d516, 0x9eae4125f9ab4533, 0xc587c178d40bd396, 0x9172803863dd0c7b, 0x0f012699ad018e32}}}, + {{{0x803050b9865a05e2, 0xfd1f458e68947b94, 0x6085e13fd73c9182, 0x1a6ea1591bc73fad, 0x1a591328130016be, 0x06ae315e43ddc9b3}, + {0xdaffe7ce1cc85290, 0x2f64c7d708f89c05, 0x2cefcecad453f54b, 0x9ed317e6030f332a, 0x4b756824ace8ca02, 0x111d39969cd7eed5}}}, + {{{0x7893a6731d706ee3, 0x6da325e3491f8dae, 0x541611faf9f2d241, 0xd2d5be7c61fe3117, 0xa3e626a63b019af3, 0x027b5865aed33e3e}, + {0x5b55d3a8f801b4db, 0x7f533c7be4dc4666, 0xa927399a012e93a3, 0x7ea790f9e16f75c1, 0xbbd7249456443c50, 0x00dcf92d0b6b6a0f}}}, + }}, + {{ + {{{0x8462a122302060ce, 0x7a3e68508d384b0e, 0x2cb8e58ff7bc2e7e, 0x47a6970d99086b57, 0xba71f7aa795633d1, 0x094f2a36e0f4518c}, + {0x5ad5000a7c62152c, 0x0a937decacc7bf06, 0x2541f7e27a1d6dfd, 0x81a0b18ff94f1c15, 0xe0d33fa411c05f51, 0x135d565a4528cdf1}}}, + {{{0x58909437df9ed9af, 0xb641356137b0a46d, 0x0d590f62bd07e3e7, 0x7e74e42751081ca8, 0xc652a7d00812b39f, 0x18489d0f447cf484}, + {0xe3f447fd841e60a3, 0xe00338945f3c102c, 0xf9753fa80df2d5b7, 0x7c93afcfe17cc43d, 0x23762dc482f1953f, 0x16496674665b5dae}}}, + {{{0x6d2d136d34211606, 0x602e26ee465a26ef, 0x512b7482ddd60acf, 0xf2b0811624026f9c, 0xf058a1b4cc1bb893, 0x185de91ffe02b788}, + {0x36ec8c618b49e6fb, 0xaad2fcf14824ca08, 0x8a8fd9748dc6df31, 0xd3d5a5cb51ed42a0, 0xf487aa34b3a16427, 0x176aae7715697eb6}}}, + }}, + {{ + {{{0x975d270070acd091, 0xb99900f022d9845f, 0xb53758acabf8edf8, 0x80ff6307d6195c0f, 0x770503737a1faa88, 0x0a4712efa842229d}, + {0x491b36dbdd1e7b50, 0x52ad828dffb4565b, 0x059b6bbcb14ff9c7, 0x6e62bb1e684027cc, 0x39a29b32ccbe545a, 0x0a2d5c6ea19a1ae1}}}, + {{{0xba9e52355229efda, 0x18a2e26111a7f90f, 0x7ad6335f04a782ae, 0xaec9b9f9cc7707df, 0x6a60ce07d997d8c6, 0x13eb871992dcf55b}, + {0x2921c8231e281fea, 0x3073175d0f0bcea6, 0x2094fe52cd94efa2, 0x3c15b2bf54685d17, 0xcc59850a78426d56, 0x0e619e9cadfa777e}}}, + {{{0xde14943a5714d5cc, 0xd733a3074e2654eb, 0xa50511b0126cbbfa, 0xd4261786bb093adb, 0x356964a7c0e5c020, 0x05278e3abe041d23}, + {0x5a3194100392b3ae, 0x792a11c53de291e0, 0x47d9d2cc0e1d449d, 0x1e720d1ef3dbbdd7, 0x4d01a051e053e24b, 0x0a4b7931bf03f442}}}, + }}, + {{ + {{{0x7fc27ad83454a81c, 0xdc3bdc34a9f6a642, 0x59082193b538f744, 0x3ea16a55266b9a8f, 0xe24bf4c3a027067d, 0x12b1c3f963141ba3}, + {0xcdfbd9433306a7be, 0x702efdbfd25a969c, 0x04d02d6d19b864eb, 0xf87cf18b0809113e, 0xa65e8835c14a93d5, 0x13284aefd4371231}}}, + {{{0x6f056f24a07168c7, 0x19258937adce68d0, 0x25da38dcf27a5b04, 0x1bfcbce73f0ea5d6, 0x1f9c5366867584a5, 0x17f8c2a4640eca05}, + {0xd3ecdd10673c33bb, 0xc941c4a4b44b42b7, 0xf6bf54c5ffa7e285, 0xaf4379f30875c7f4, 0x970640dd5f19e8f9, 0x12e25f6aac12d868}}}, + {{{0x2199b4b321614c81, 0xa6720657ffbb3660, 0xd38cd99c69e8d2eb, 0x7de87de3c90cfc82, 0xb324062d2372f990, 0x02e5f45eb639f38b}, + {0x972f67248fe0bffe, 0x240de902ef2cec1d, 0xe4b64fd866df0de1, 0xb7225ce3574248ba, 0xdd24e68993f076ed, 0x0a30dea4aaf28334}}}, + }}, +}; +// clang-format on + +} // namespace + +const blst_fp6* g2_gen_lines() noexcept +{ + return G2_GEN_LINES; +} + +const blst_fp6* kzg_setup_g2_1_lines() noexcept +{ + return KZG_SETUP_G2_1_LINES; +} +} // namespace evmone::crypto diff --git a/evmone/lib/evmone_precompiles/kzg_precomputed_lines.hpp b/evmone/lib/evmone_precompiles/kzg_precomputed_lines.hpp new file mode 100644 index 0000000..3680141 --- /dev/null +++ b/evmone/lib/evmone_precompiles/kzg_precomputed_lines.hpp @@ -0,0 +1,16 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2026 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +namespace evmone::crypto +{ +/// Returns precomputed Miller-loop lines for the BLS12-381 G2 generator [1]₂. +const blst_fp6* g2_gen_lines() noexcept; + +/// Returns precomputed Miller-loop lines for KZG_SETUP_G2_1 ([s]₂ from the +/// Ethereum mainnet trusted setup). +const blst_fp6* kzg_setup_g2_1_lines() noexcept; +} // namespace evmone::crypto diff --git a/evmone/lib/evmone_precompiles/modexp.cpp b/evmone/lib/evmone_precompiles/modexp.cpp index 0565143..d54b0a2 100644 --- a/evmone/lib/evmone_precompiles/modexp.cpp +++ b/evmone/lib/evmone_precompiles/modexp.cpp @@ -280,8 +280,8 @@ class Exponent [[nodiscard]] size_t bit_width() const noexcept { return bit_width_; } - /// Returns the bit value of the exponent at the given index, counting from the most significant - /// bit (e[0] is the top bit). + /// Returns the bit value of the exponent at the given index, counting from the least + /// significant bit (e[0] is the bottom bit, e[bit_width() - 1] is the top bit, always set). bool operator[](size_t index) const noexcept { // TODO: Replace this with a custom iterator type. @@ -292,6 +292,19 @@ class Exponent const auto bit = (byte >> bit_index) & 1; return bit != 0; } + + /// Returns bits [lo, hi] as an integer, the bit at hi being the most significant. + /// The range must span at most 8 bits, so it covers at most two adjacent bytes. + [[nodiscard]] size_t window(size_t lo, size_t hi) const noexcept + { + assert(lo <= hi && hi - lo < 8); + const auto exp_size = (bit_width_ + 7) / 8; + const auto byte_index = exp_size - 1 - lo / 8; + auto bytes = size_t{data_[byte_index]}; + if (byte_index != 0) // Prepend the next more significant byte if there is one. + bytes |= size_t{data_[byte_index - 1]} << 8; + return (bytes >> (lo % 8)) & ((size_t{1} << (hi + 1 - lo)) - 1); + } }; /// Performs the Almost Montgomery Multiplication (AMM). @@ -361,14 +374,39 @@ void mul_amm(std::span r, std::span x, /// Almost Montgomery Multiplication specialized for 4-word (256-bit) operands. /// Delegates to mul_amm_256 in mulmod.cpp. template <> -[[gnu::always_inline]] void mul_amm<4>(std::span r, std::span x, - std::span y, std::span mod, uint64_t mod_inv) noexcept +[[gnu::always_inline]] inline void mul_amm<4>(std::span r, + std::span x, std::span y, + std::span mod, uint64_t mod_inv) noexcept { mul_amm_256(r, x, y, mod, mod_inv); } +/// Maximum window width used by the windowed method in modexp_odd. +constexpr unsigned MAX_WINDOW_WIDTH = 5; +static_assert(MAX_WINDOW_WIDTH <= 8, "Exponent::window() covers at most two adjacent bytes"); + +/// Number of precomputed base odd powers for the max width windowed method. +constexpr size_t MAX_PRECOMPUTED = size_t{1} << (MAX_WINDOW_WIDTH - 1); + +/// Selects the sliding-window width from the exponent bit length. +constexpr unsigned window_width(size_t exp_bits) noexcept +{ + // Break-even points for a random exponent, where the table's extra multiply stops being + // repaid: 2^(w-1) / (1/(w+1) - 1/(w+2)) = 6, 24, 80, 240. Each narrower width is kept + // one bit longer, which measures better on the sparse small exponents seen in practice. + if (exp_bits <= 7) + return 1; + if (exp_bits <= 25) + return 2; + if (exp_bits <= 81) + return 3; + if (exp_bits <= 241) + return 4; + return MAX_WINDOW_WIDTH; +} + /// Computes result[] = base[]^exp % mod[] for odd mod[] (mod[0] % 2 != 0). -/// Scratch space required: 4n + 3*base.size() + 2 words, where n = mod.size(). +/// Scratch space required: (MAX_PRECOMPUTED + 3)*mod.size() + 3*base.size() + 2 words. void modexp_odd(std::span result, std::span base, Exponent exp, std::span mod, std::span scratch) noexcept { @@ -379,17 +417,24 @@ void modexp_odd(std::span result, std::span base, Expo const auto n = mod.size(); const auto mod_inv = -evmmax::modinv(mod[0]); + const auto exp_bits = exp.bit_width(); - // Layout: u[n+base.size()] | base_mont[n] | t/rem_scratch[max(n, 2*(n+base.size())+2)] - // t and rem_scratch share the same region (exclusive lifetimes). - assert(scratch.size() >= 4 * n + 3 * base.size() + 2); + const auto w = window_width(exp_bits); + const auto table_size = size_t{1} << (w - 1); - // Compute base_mont = (base * R) % mod, where R = 2^(n*64). - // The numerator u = base << (n*64): base in the upper words, lower n words are zero. + // Layout: u[n + base.size()] | table[MAX_PRECOMPUTED*n] + // | rem_scratch[2*n + 2*base.size() + 2]. + // u and rem_scratch are dead after the to-Montgomery conversion; u's first n words are + // then reused as the exponentiation double-buffer. + assert(scratch.size() >= (MAX_PRECOMPUTED + 3) * n + 3 * base.size() + 2); const auto u = scratch.subspan(0, n + base.size()); - const auto base_mont = scratch.subspan(n + base.size(), n); - const auto rem_scratch = scratch.subspan(2 * n + base.size(), 2 * n + 2 * base.size() + 2); + const auto table = scratch.subspan(n + base.size(), MAX_PRECOMPUTED * n); + const auto base_mont = table.first(n); + const auto rem_scratch = + scratch.subspan(n + base.size() + MAX_PRECOMPUTED * n, 2 * n + 2 * base.size() + 2); + // Compute base_mont = table[0] = (base * R) % mod, where R = 2^(n*64). + // The numerator u = base << (n*64): base in the upper words, lower n words are zero. std::ranges::fill(u.first(n), uint64_t{0}); // Lower n words of u must be zero. std::ranges::copy(base, u.subspan(n).begin()); rem(base_mont, u, mod, rem_scratch); @@ -398,28 +443,62 @@ void modexp_odd(std::span result, std::span base, Expo const auto exp_loop = [&]() { auto r_cur = std::span{result}; auto r_tmp = std::span{u.first(n)}; - const auto bm = std::span{base_mont}; const auto m = std::span{mod}; - std::ranges::copy(bm, r_cur.begin()); - for (auto i = exp.bit_width() - 1; i != 0; --i) + // base_mont^v, for odd v. + const auto precomputed = [table, n](size_t v) noexcept { + return std::span{table.subspan((v / 2) * n, n)}; + }; + + // Fill the precomputed table (precomputed(1) is already set). + if (table_size > 1) + { + mul_amm(r_tmp, precomputed(1), precomputed(1), m, mod_inv); // r_tmp = base_mont^2. + for (size_t v = 3; v < 2 * table_size; v += 2) + mul_amm(precomputed(v), precomputed(v - 2), r_tmp, m, mod_inv); + } + + // The widest window of at most w bits ending at bit `hi`, which must be set. Trailing + // zero bits are trimmed off, so the value is odd and only odd table entries are + // needed. Returns the value and the index of its lowest bit. + const auto window = [exp, w](size_t hi) noexcept { + const auto lo = hi + 1 >= w ? hi + 1 - w : size_t{0}; + const auto v = exp.window(lo, hi); + const auto tz = static_cast(std::countr_zero(v)); // v != 0: exp[hi] is set. + return std::pair{v >> tz, lo + tz}; + }; + + // The top bit is always set, so the first window ends there and is loaded directly. + auto [v_top, pos] = window(exp_bits - 1); + std::ranges::copy(precomputed(v_top), r_cur.begin()); + + while (pos != 0) { - mul_amm(r_tmp, r_cur, r_cur, m, mod_inv); // Square. - if (exp[i - 1]) - mul_amm(r_cur, r_tmp, bm, m, mod_inv); // Multiply. - else + --pos; + mul_amm(r_tmp, r_cur, r_cur, m, mod_inv); // Square for this bit. + std::swap(r_cur, r_tmp); + if (!exp[pos]) + continue; + + const auto [v, lo] = window(pos); + for (auto b = lo; b != pos; ++b) // One more square for each remaining window bit. + { + mul_amm(r_tmp, r_cur, r_cur, m, mod_inv); std::swap(r_cur, r_tmp); + } + mul_amm(r_tmp, r_cur, precomputed(v), m, mod_inv); + std::swap(r_cur, r_tmp); + pos = lo; } - // Convert from Montgomery form: multiply by 1. + // Convert from Montgomery form: multiply by 1. Reuses precomputed(1) storage. std::ranges::fill(base_mont, uint64_t{0}); base_mont[0] = 1; mul_amm(r_tmp, r_cur, std::span{base_mont}, m, mod_inv); - std::swap(r_cur, r_tmp); // If the result ended up in scratch, copy to result. - if (r_cur.data() != result.data()) - std::ranges::copy(r_cur, result.begin()); + if (r_tmp.data() != result.data()) + std::ranges::copy(r_tmp, result.begin()); }; if (n == 4) @@ -530,10 +609,11 @@ void modexp(std::span base_bytes, std::span exp_by // Bump allocator for all working memory (values + scratch). // Stack buffer covers inputs up to the EIP-7823 limit (1024 bytes). - // Capacity: values[b+2m] + op scratch[4m+3b+2] + CRT[m+2] = 4b+7m+4 words. + // Capacity: values[b+2m] + op scratch[(MAX_PRECOMPUTED+3)m+3b+2] + CRT[m+2] + // = 4b + (MAX_PRECOMPUTED+6)m + 4 words. // The worst case is an even modulus with 1 trailing zero bit (odd_size=m, pow2_size=1). static constexpr size_t MAX_SIZE = 1024 / sizeof(uint64_t); // EIP-7823 - static constexpr size_t STACK_CAPACITY = 4 * MAX_SIZE + 7 * MAX_SIZE + 4; + static constexpr size_t STACK_CAPACITY = 4 * MAX_SIZE + (6 + MAX_PRECOMPUTED) * MAX_SIZE + 4; alignas(uint64_t) std::byte stack_buf[STACK_CAPACITY * sizeof(uint64_t)]; std::pmr::monotonic_buffer_resource pool{stack_buf, sizeof(stack_buf)}; std::pmr::polymorphic_allocator alloc{&pool}; @@ -577,7 +657,8 @@ void modexp(std::span base_bytes, std::span exp_by const auto need_crt = !pow2_is_trivial && !odd_is_trivial; // Allocate operation scratch (dead after each call, reused sequentially). - const size_t odd_scratch = !odd_is_trivial ? 4 * odd_size + 3 * base.size() + 2 : 0; + const size_t odd_scratch = + !odd_is_trivial ? (MAX_PRECOMPUTED + 3) * odd_size + 3 * base.size() + 2 : 0; const size_t pow2_scratch = !pow2_is_trivial ? pow2_size : 0; const size_t inv_scratch = need_crt ? 2 * pow2_size : 0; const size_t op_scratch_size = std::max({odd_scratch, pow2_scratch, inv_scratch}); diff --git a/evmone/lib/evmone_precompiles/mulmod.cpp b/evmone/lib/evmone_precompiles/mulmod.cpp index c2af255..5f729b8 100644 --- a/evmone/lib/evmone_precompiles/mulmod.cpp +++ b/evmone/lib/evmone_precompiles/mulmod.cpp @@ -10,40 +10,45 @@ void mul_amm_256(std::span r, std::span x, std::span y, std::span mod, uint64_t mod_inv) noexcept { static constexpr size_t N = 4; - const auto r_lo = r.subspan<0, 3>(); - const auto r_hi = r.subspan<1>(); + + // Local accumulator t[] avoids aliasing penalties when r overlaps x or y. + std::array t; // NOLINT(*-pro-type-member-init) + const auto t_lo = std::span{t}.subspan<0, N - 1>(); + const auto t_hi = std::span{t}.subspan<1>(); const auto mod_hi = mod.subspan<1>(); - // First iteration: r is uninitialized, so use mul instead of addmul. - bool r_carry = false; + // First iteration: t is uninitialized, so use mul instead of addmul. + bool t_carry = false; { - const auto c1 = mul(r, x, y[0]); + const auto c1 = mul(t, x, y[0]); - const auto m = r[0] * mod_inv; - const auto c2 = (umul(mod[0], m) + r[0])[1]; + const auto m = t[0] * mod_inv; + const auto c2 = (umul(mod[0], m) + t[0])[1]; - const auto c3 = addmul(r_lo, r_hi, mod_hi, m, c2); - std::tie(r[N - 1], r_carry) = addc(c1, c3); + const auto c3 = addmul(t_lo, t_hi, mod_hi, m, c2); + std::tie(t[N - 1], t_carry) = addc(c1, c3); } // Remaining 3 iterations. #pragma GCC unroll N - 1 for (size_t i = 1; i != N; ++i) { - const auto c1 = addmul(r, r, x, y[i]); - const auto [sum1, d1] = addc(c1, uint64_t{r_carry}); + const auto c1 = addmul(t, t, x, y[i]); + const auto [sum1, d1] = addc(c1, uint64_t{t_carry}); - const auto m = r[0] * mod_inv; - const auto c2 = (umul(mod[0], m) + r[0])[1]; + const auto m = t[0] * mod_inv; + const auto c2 = (umul(mod[0], m) + t[0])[1]; - const auto c3 = addmul(r_lo, r_hi, mod_hi, m, c2); + const auto c3 = addmul(t_lo, t_hi, mod_hi, m, c2); const auto [sum2, d2] = addc(sum1, c3); - r[N - 1] = sum2; + t[N - 1] = sum2; assert(!(d1 && d2)); - r_carry = d1 || d2; + t_carry = d1 || d2; } - if (r_carry) - sub(r, mod); + if (t_carry) + sub(t, mod); + + std::ranges::copy(t, r.begin()); } } // namespace evmone::crypto diff --git a/evmone/lib/evmone_precompiles/pairing/bn254/fields.hpp b/evmone/lib/evmone_precompiles/pairing/bn254/fields.hpp index 3d15b7e..9449a62 100644 --- a/evmone/lib/evmone_precompiles/pairing/bn254/fields.hpp +++ b/evmone/lib/evmone_precompiles/pairing/bn254/fields.hpp @@ -10,19 +10,9 @@ namespace evmmax::bn254 { using namespace intx; -using Fq = Curve::Fp; // Extension fields implemented based on https://hackmd.io/@jpw/bn254#Field-extension-towers - -/// Specifies Fq^2 extension field for bn254 curve. Base field extended with irreducible `u^2 + 1` -/// polynomial over the base field. `u` is the Fq^2 element. -struct Fq2Config -{ - using BaseFieldT = Fq; - using ValueT = Fq; - static constexpr auto DEGREE = 2; -}; -using Fq2 = ecc::ExtFieldElem; +// Fq, Fq2Config, Fq2, and E2 live in bn254.hpp to be reachable from the precompile boundary. /// Specifies Fq^6 extension field for bn254 curve. Fq^2 field extended with irreducible /// `v^3 - (9 + u)` polynomial over the Fq^2 field. `v` is the Fq^6 field element. @@ -32,10 +22,6 @@ struct Fq6Config using ValueT = Fq2; static constexpr uint8_t DEGREE = 3; static constexpr auto ksi = Fq2({Fq(9_u256), Fq(1_u256)}); - static constexpr auto _3_ksi_inv = Fq2({ - Fq(0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5_u256), - Fq(0x9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2_u256), - }); }; using Fq6 = ecc::ExtFieldElem; @@ -52,23 +38,28 @@ struct Fq12Config using Fq12 = ecc::ExtFieldElem; /// Multiplies two Fq^2 field elements -constexpr Fq2 multiply(const Fq2& a, const Fq2& b) +constexpr Fq2 multiply(const Fq2& a, const Fq2& b) noexcept { - return Fq2({ - a.coeffs[0] * b.coeffs[0] - a.coeffs[1] * b.coeffs[1], - a.coeffs[1] * b.coeffs[0] + a.coeffs[0] * b.coeffs[1], - }); + const auto& [a0, a1] = a.coeffs; + const auto& [b0, b1] = b.coeffs; + return Fq2({a0 * b0 - a1 * b1, a1 * b0 + a0 * b1}); +} + +/// Squares an Fq^2 field element. +constexpr Fq2 sqr(const Fq2& a) noexcept +{ + const auto& [a0, a1] = a.coeffs; + + // (a0 + a1*u)^2 = (a0+a1)*(a0-a1) + 2a0a1*u. + const auto a0a1 = a0 * a1; + return Fq2({(a0 + a1) * (a0 - a1), a0a1 + a0a1}); } /// Multiplies two Fq^6 field elements -constexpr Fq6 multiply(const Fq6& a, const Fq6& b) +constexpr Fq6 multiply(const Fq6& a, const Fq6& b) noexcept { - const auto& a0 = a.coeffs[0]; - const auto& a1 = a.coeffs[1]; - const auto& a2 = a.coeffs[2]; - const auto& b0 = b.coeffs[0]; - const auto& b1 = b.coeffs[1]; - const auto& b2 = b.coeffs[2]; + const auto& [a0, a1, a2] = a.coeffs; + const auto& [b0, b1, b2] = b.coeffs; const Fq2& ksi = Fq6Config::ksi; @@ -84,12 +75,10 @@ constexpr Fq6 multiply(const Fq6& a, const Fq6& b) } /// Multiplies two Fq^12 field elements -constexpr Fq12 multiply(const Fq12& a, const Fq12& b) +constexpr Fq12 multiply(const Fq12& a, const Fq12& b) noexcept { - const auto& a0 = a.coeffs[0]; - const auto& a1 = a.coeffs[1]; - const auto& b0 = b.coeffs[0]; - const auto& b1 = b.coeffs[1]; + const auto& [a0, a1] = a.coeffs; + const auto& [b0, b1] = b.coeffs; const auto t0 = a0 * b0; const auto t1 = a1 * b1; @@ -103,10 +92,10 @@ constexpr Fq12 multiply(const Fq12& a, const Fq12& b) } /// Inverses the Fq^2 field element -inline Fq2 inverse(const Fq2& f) +inline Fq2 inverse(const Fq2& f) noexcept { - const auto& a0 = f.coeffs[0]; - const auto& a1 = f.coeffs[1]; + const auto& [a0, a1] = f.coeffs; + auto t0 = a0 * a0; auto t1 = a1 * a1; @@ -120,11 +109,9 @@ inline Fq2 inverse(const Fq2& f) } /// Inverses the Fq^6 field element -inline Fq6 inverse(const Fq6& f) +inline Fq6 inverse(const Fq6& f) noexcept { - const auto& a0 = f.coeffs[0]; - const auto& a1 = f.coeffs[1]; - const auto& a2 = f.coeffs[2]; + const auto& [a0, a1, a2] = f.coeffs; const Fq2& ksi = Fq6Config::ksi; @@ -147,10 +134,9 @@ inline Fq6 inverse(const Fq6& f) } /// Inverses the Fq^12 field element -inline Fq12 inverse(const Fq12& f) +inline Fq12 inverse(const Fq12& f) noexcept { - const auto& a0 = f.coeffs[0]; - const auto& a1 = f.coeffs[1]; + const auto& [a0, a1] = f.coeffs; auto t0 = a0 * a0; auto t1 = a1 * a1; diff --git a/evmone/lib/evmone_precompiles/pairing/bn254/pairing.cpp b/evmone/lib/evmone_precompiles/pairing/bn254/pairing.cpp index 054d3aa..9a3b3e5 100644 --- a/evmone/lib/evmone_precompiles/pairing/bn254/pairing.cpp +++ b/evmone/lib/evmone_precompiles/pairing/bn254/pairing.cpp @@ -5,16 +5,15 @@ #include "../../bn254.hpp" #include "fields.hpp" #include "utils.hpp" -#include namespace evmmax::bn254 { namespace { /// Multiplies `fr` (Fq12) values by sparse `v` (Fq12) value of the form -/// [[t[0] * y, 0, 0],[t[1] * x, t[0], 0]] where `v` coefficients are from Fq2 +/// [[t[0] * y, 0, 0],[t[1] * x, t[2], 0]] where `v` coefficients are from Fq2 constexpr void multiply_by_lin_func_value( - Fq12& fr, std::array t, const Fq& x, const Fq& y) noexcept + Fq12& fr, const std::array& t, const Fq& x, const Fq& y) noexcept { const Fq12 f = fr; const auto& ksi = Fq6Config::ksi; @@ -45,10 +44,10 @@ inline constexpr auto ATE_LOOP_COUNT_NAF = 0x1120804220120081204008212022011_u12 inline constexpr int LOG_ATE_LOOP_COUNT = 63; /// Miller loop according to https://eprint.iacr.org/2010/354.pdf Algorithm 1. -Fq12 miller_loop(const ecc::Point& Q, const ecc::Point& P) noexcept +Fq12 miller_loop(const ecc::AffinePoint& Q, const ecc::AffinePoint& P) noexcept { - auto T = ecc::JacPoint::from(Q); - auto nQ = -Q; + auto T = ecc::ProjPoint{Q}; + const auto nQ = -Q; auto f = Fq12::one(); std::array t; auto naf = ATE_LOOP_COUNT_NAF; @@ -128,7 +127,7 @@ Fq12 final_exp(const Fq12& v) noexcept } } // namespace -std::optional pairing_check(std::span> pairs) noexcept +std::optional pairing_check(std::span> pairs) noexcept { if (pairs.empty()) return true; @@ -137,33 +136,19 @@ std::optional pairing_check(std::span> pa for (const auto& [p, q] : pairs) { - if (!is_field_element(p.x) || !is_field_element(p.y) || !is_field_element(q.x.first) || - !is_field_element(q.x.second) || !is_field_element(q.y.first) || - !is_field_element(q.y.second)) - { + if (!validate(p)) return std::nullopt; - } - - // Converts points' coefficients in Montgomery form. - const auto P_aff = ecc::Point{Fq(p.x), Fq(p.y)}; - const auto Q_aff = ecc::Point{ - Fq2({Fq(q.x.first), Fq(q.x.second)}), Fq2({Fq(q.y.first), Fq(q.y.second)})}; - const bool g1_is_inf = is_infinity(P_aff); - const bool g2_is_inf = g2_is_infinity(Q_aff); - - // Verify that P in on curve. For this group it also means that P is in G1. - if (!g1_is_inf && !is_on_curve(P_aff)) - return std::nullopt; + const bool g2_is_inf = q == 0; - // Verify that Q in on curve and in proper subgroup. This subgroup is much smaller than - // group containing all the points from twisted curve over Fq2 field. - if (!g2_is_inf && (!is_on_twisted_curve(Q_aff) || !g2_subgroup_check(Q_aff))) + // Verify that Q is on the curve and in the proper subgroup. This subgroup is much smaller + // than the group containing all the points from the twisted curve over Fq2 field. + if (!g2_is_inf && (!is_on_twisted_curve(q) || !g2_subgroup_check(q))) return std::nullopt; - // If any of the points is infinity it means that miller_loop returns 1. so we can skip it. - if (!g1_is_inf && !g2_is_inf) - f = f * miller_loop(Q_aff, P_aff); + // If either point is infinity, miller_loop returns 1, so skip it. + if (p != 0 && !g2_is_inf) + f = f * miller_loop(q, p); } // final exp is calculated on accumulated value diff --git a/evmone/lib/evmone_precompiles/pairing/bn254/utils.hpp b/evmone/lib/evmone_precompiles/pairing/bn254/utils.hpp index 4d48aff..5e04690 100644 --- a/evmone/lib/evmone_precompiles/pairing/bn254/utils.hpp +++ b/evmone/lib/evmone_precompiles/pairing/bn254/utils.hpp @@ -7,102 +7,49 @@ namespace evmmax::bn254 { -consteval Fq2 make_fq2(const uint256& a, const uint256& b) noexcept -{ - return Fq2({Fq(a), Fq(b)}); -} - /// Defines coefficients needed for fast Frobenius endomorphism computation. /// For more ref see https://eprint.iacr.org/2010/354.pdf 3.2 Frobenius Operator. -/// TODO: Make it constexpr. -static inline const std::array, 3> FROBENIUS_COEFFS = { - { - { - make_fq2( - 8376118865763821496583973867626364092589906065868298776909617916018768340080_u256, - 16469823323077808223889137241176536799009286646108169935659301613961712198316_u256), - make_fq2( - 21575463638280843010398324269430826099269044274347216827212613867836435027261_u256, - 10307601595873709700152284273816112264069230130616436755625194854815875713954_u256), - make_fq2( - 2821565182194536844548159561693502659359617185244120367078079554186484126554_u256, - 3505843767911556378687030309984248845540243509899259641013678093033130930403_u256), - make_fq2( - 2581911344467009335267311115468803099551665605076196740867805258568234346338_u256, - 19937756971775647987995932169929341994314640652964949448313374472400716661030_u256), - make_fq2( - 685108087231508774477564247770172212460312782337200605669322048753928464687_u256, - 8447204650696766136447902020341177575205426561248465145919723016860428151883_u256), - }, - { - make_fq2( - 21888242871839275220042445260109153167277707414472061641714758635765020556617_u256, - 0_u256), - make_fq2( - 21888242871839275220042445260109153167277707414472061641714758635765020556616_u256, - 0_u256), - make_fq2( - 21888242871839275222246405745257275088696311157297823662689037894645226208582_u256, - 0_u256), - make_fq2(2203960485148121921418603742825762020974279258880205651966_u256, 0_u256), - make_fq2(2203960485148121921418603742825762020974279258880205651967_u256, 0_u256), - }, - { - make_fq2( - 11697423496358154304825782922584725312912383441159505038794027105778954184319_u256, - 303847389135065887422783454877609941456349188919719272345083954437860409601_u256), - make_fq2( - 3772000881919853776433695186713858239009073593817195771773381919316419345261_u256, - 2236595495967245188281701248203181795121068902605861227855261137820944008926_u256), - make_fq2( - 19066677689644738377698246183563772429336693972053703295610958340458742082029_u256, - 18382399103927718843559375435273026243156067647398564021675359801612095278180_u256), - make_fq2( - 5324479202449903542726783395506214481928257762400643279780343368557297135718_u256, - 16208900380737693084919495127334387981393726419856888799917914180988844123039_u256), - make_fq2( - 8941241848238582420466759817324047081148088512956452953208002715982955420483_u256, - 10338197737521362862238855242243140895517409139741313354160881284257516364953_u256), - }, - }, -}; - -/// Verifies that value is in the proper prime field. -constexpr bool is_field_element(const uint256& v) -{ - return v < Curve::FIELD_PRIME; -} - -/// Verifies that affine point is on the curve (not twisted) -constexpr bool is_on_curve(const ecc::Point& p) noexcept -{ - // TODO(C++23): make static - constexpr auto B = Fq(3); - - const auto x3 = p.x * p.x * p.x; - const auto y2 = p.y * p.y; - return y2 == x3 + B; -} +inline constexpr std::array, 3> FROBENIUS_COEFFS = {{ + {{ + {0x1284b71c2865a7dfe8b99fdd76e68b605c521e08292f2176d60b35dadcc9e470_u256, + 0x246996f3b4fae7e6a6327cfe12150b8e747992778eeec7e5ca5cf05f80f362ac_u256}, + {0x2fb347984f7911f74c0bec3cf559b143b78cc310c2c3330c99e39557176f553d_u256, + 0x16c9e55061ebae204ba4cc8bd75a079432ae2a1d0b7c9dce1665d51c640fcba2_u256}, + {0x63cf305489af5dcdc5ec698b6e2f9b9dbaae0eda9c95998dc54014671a0135a_u256, + 0x7c03cbcac41049a0704b5a7ec796f2b21807dc98fa25bd282d37f632623b0e3_u256}, + {0x5b54f5e64eea80180f3c0b75a181e84d33365f7be94ec72848a1f55921ea762_u256, + 0x2c145edbe7fd8aee9f3a80b03b0b1c923685d2ea1bdec763c13b4711cd2b8126_u256}, + {0x183c1e74f798649e93a3661a4353ff4425c459b55aa1bd32ea2c810eab7692f_u256, + 0x12acf2ca76fd0675a27fb246c7729f7db080cb99678e2ac024c6b8ee6e0c2c4b_u256}, + }}, + {{ + {0x30644e72e131a0295e6dd9e7e0acccb0c28f069fbb966e3de4bd44e5607cfd49_u256, 0_u256}, + {0x30644e72e131a0295e6dd9e7e0acccb0c28f069fbb966e3de4bd44e5607cfd48_u256, 0_u256}, + {0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd46_u256, 0_u256}, + {0x000000000000000059e26bcea0d48bacd4f263f1acdb5c4f5763473177fffffe_u256, 0_u256}, + {0x000000000000000059e26bcea0d48bacd4f263f1acdb5c4f5763473177ffffff_u256, 0_u256}, + }}, + {{ + {0x19dc81cfcc82e4bbefe9608cd0acaa90894cb38dbe55d24ae86f7d391ed4a67f_u256, + 0xabf8b60be77d7306cbeee33576139d7f03a5e397d439ec7694aa2bf4c0c101_u256}, + {0x856e078b755ef0abaff1c77959f25ac805ffd3d5d6942d37b746ee87bdcfb6d_u256, + 0x4f1de41b3d1766fa9f30e6dec26094f0fdf31bf98ff2631380cab2baaa586de_u256}, + {0x2a275b6d9896aa4cdbf17f1dca9e5ea3bbd689a3bea870f45fcc8ad066dce9ed_u256, + 0x28a411b634f09b8fb14b900e9507e9327600ecc7d8cf6ebab94d0cb3b2594c64_u256}, + {0xbc58c6611c08dab19bee0f7b5b2444ee633094575b06bcb0e1a92bc3ccbf066_u256, + 0x23d5e999e1910a12feb0f6ef0cd21d04a44a9e08737f96e55fe3ed9d730c239f_u256}, + {0x13c49044952c0905711699fa3b4d3f692ed68098967c84a5ebde847076261b43_u256, + 0x16db366a59b1dd0b9fb1b2282a48633d3e2ddaea200280211f25041384282499_u256}, + }}, +}}; /// Verifies that affine point over Fq^2 field is on the twisted curve. -constexpr bool is_on_twisted_curve(const evmmax::ecc::Point& p) +constexpr bool is_on_twisted_curve(const ecc::AffinePoint& p) noexcept { const auto x3 = p.x * p.x * p.x; const auto y2 = p.y * p.y; - return y2 == x3 + Fq6Config::_3_ksi_inv; -} - -/// Verifies that affine point over the base field is infinity. -constexpr bool is_infinity(const evmmax::ecc::Point& p) -{ - return p.x == 0 && p.y == 0; -} - -/// Verifies that affine point over the Fq^2 extended field is infinity. -constexpr bool g2_is_infinity(const evmmax::ecc::Point& p) -{ - return p.x == Fq2::zero() && p.y == Fq2::zero(); + return y2 == x3 + E2::B; } // Frobenius endomorphism related functions are implemented based on @@ -115,7 +62,7 @@ constexpr bool g2_is_infinity(const evmmax::ecc::Point& p) /// This specialisation computes Frobenius and Frobenius^3 /// TODO: add reference that it's exactly the same as untwist->frobenius->twist template -constexpr ecc::JacPoint endomorphism(const ecc::JacPoint& p) noexcept +constexpr ecc::ProjPoint endomorphism(const ecc::ProjPoint& p) noexcept requires(P == 1 || P == 3) { return { @@ -129,7 +76,7 @@ constexpr ecc::JacPoint endomorphism(const ecc::JacPoint& p) noexcept /// over Fq^2 extended field. /// This specialisation computes Frobenius^2 template -constexpr ecc::JacPoint endomorphism(const ecc::JacPoint& p) noexcept +constexpr ecc::ProjPoint endomorphism(const ecc::ProjPoint& p) noexcept requires(P == 2) { return { @@ -143,7 +90,7 @@ constexpr ecc::JacPoint endomorphism(const ecc::JacPoint& p) noexcept /// over Fq^2 extended field. /// This specialisation computes Frobenius and Frobenius^3 template -constexpr ecc::Point endomorphism(const ecc::Point& p) noexcept +constexpr ecc::AffinePoint endomorphism(const ecc::AffinePoint& p) noexcept requires(P == 1 || P == 3) { return { @@ -156,7 +103,7 @@ constexpr ecc::Point endomorphism(const ecc::Point& p) noexcept /// over Fq^2 extended field. /// This specialisation computes Frobenius^2 template -constexpr ecc::Point endomorphism(const ecc::Point& p) noexcept +constexpr ecc::AffinePoint endomorphism(const ecc::AffinePoint& p) noexcept requires(P == 2) { return { @@ -205,11 +152,10 @@ constexpr Fq12 endomorphism(const Fq12& f) noexcept }); } - /// Computes `P0 + P1` in Jacobian coordinates. /// P0 and P1 must not be the point at infinity, and must not be equal or negations of each other. -constexpr ecc::JacPoint add( - const ecc::JacPoint& P0, const ecc::JacPoint& P1) noexcept +constexpr ecc::ProjPoint add( + const ecc::ProjPoint& P0, const ecc::ProjPoint& P1) noexcept { const auto& x0 = P0.x; const auto& y0 = P0.y; @@ -239,42 +185,15 @@ constexpr ecc::JacPoint add( const auto V = U1 * H_squared; const auto X3 = R_squared - H_cubed - (V + V); - const auto Y3 = R * (U1 * H_squared - X3) - S1 * H_cubed; + const auto Y3 = R * (V - X3) - S1 * H_cubed; const auto Z3 = H * z0 * z1; return {X3, Y3, Z3}; } -/// Computes `Q + Q` in Jacobian coordinates. -constexpr ecc::JacPoint dbl(const ecc::JacPoint& Q) noexcept -{ - const auto& x = Q.x; - const auto& y = Q.y; - const auto& z = Q.z; - - const auto y_squared = y * y; - const auto x_squared = x * x; - const auto z_squared = z * z; - const auto y_4 = y_squared * y_squared; - const auto _4y_4 = y_4 + y_4 + y_4 + y_4; - - const auto R = y_squared + y_squared; - const auto A = (x + R); - const auto S = A * A - x_squared - _4y_4; // 2xR = (x+R)^2 - x^2 - R^2 - const auto M = x_squared + x_squared + x_squared; - - const auto N = y + z; - - const auto Xp = M * M - (S + S); - const auto Yp = M * (S - Xp) - (_4y_4 + _4y_4); - const auto Zp = N * N - y_squared - z_squared; // 2yz = (y+z)^2 - y^2 - z^2 - - return {Xp, Yp, Zp}; -} - /// Computes `N` doubles of the point `a` in Jacobian coordinates. template -constexpr ecc::JacPoint n_dbl(const ecc::JacPoint& a) noexcept +constexpr ecc::ProjPoint n_dbl(const ecc::ProjPoint& a) noexcept { auto r = dbl(a); for (int i = 0; i < N - 1; ++i) @@ -285,7 +204,7 @@ constexpr ecc::JacPoint n_dbl(const ecc::JacPoint& a) noexcept /// Addchain generated algorithm which multiplies point `a` in Jacobian coordinated /// by X (curve seed). -constexpr ecc::JacPoint mul_by_X(const ecc::JacPoint& a) noexcept +constexpr ecc::ProjPoint mul_by_X(const ecc::ProjPoint& a) noexcept { auto t0 = dbl(a); auto t2 = dbl(t0); @@ -323,9 +242,9 @@ constexpr ecc::JacPoint mul_by_X(const ecc::JacPoint& a) noexcept /// Checks that point `p_aff` is in proper subgroup of points from twisted curve over Fq2 field. /// For more details see https://eprint.iacr.org/2022/348.pdf Example 1 from 3.1.2 Examples -constexpr bool g2_subgroup_check(const ecc::Point& p_aff) +constexpr bool g2_subgroup_check(const ecc::AffinePoint& p_aff) noexcept { - const auto p = ecc::JacPoint::from(p_aff); + const auto p = ecc::ProjPoint{p_aff}; const auto px = mul_by_X(p); const auto px1 = add(px, p); @@ -344,8 +263,8 @@ constexpr bool g2_subgroup_check(const ecc::Point& p_aff) /// the curve (not twisted curve) evaluated at point P /// Returns live evaluation coefficients (-t, tw, tvw) /// For more details see https://notes.ethereum.org/@ipsilon/Hkn2a2qk0 -constexpr ecc::JacPoint lin_func_and_dbl( - const ecc::JacPoint& Q, std::array& t) noexcept +constexpr ecc::ProjPoint lin_func_and_dbl( + const ecc::ProjPoint& Q, std::array& t) noexcept { const auto& x = Q.x; const auto& y = Q.y; @@ -372,14 +291,14 @@ constexpr ecc::JacPoint lin_func_and_dbl( t[1] = M * z_squared; t[2] = R - M * x; - return ecc::JacPoint{Xp, Yp, Zp}; + return ecc::ProjPoint{Xp, Yp, Zp}; } /// Computes points P0 and P1 addition for twisted curve + line defined by untwisted P1 and P2 /// points on the curve (not twisted curve) evaluated at point P. Formula is simplified for P1.z /// == 1. For more details see https://notes.ethereum.org/@ipsilon/Hkn2a2qk0 -[[nodiscard]] constexpr ecc::JacPoint lin_func_and_add( - const ecc::JacPoint& P0, const ecc::Point& P1, std::array& t) noexcept +[[nodiscard]] constexpr ecc::ProjPoint lin_func_and_add( + const ecc::ProjPoint& P0, const ecc::AffinePoint& P1, std::array& t) noexcept { const auto& x0 = P0.x; const auto& y0 = P0.y; @@ -394,7 +313,7 @@ constexpr ecc::JacPoint lin_func_and_dbl( const auto U2 = x1 * z0_squared; const auto S2 = y1 * z0_cubed; const auto H = U2 - x0; // x1 * z0^2 - x0 * z1^2 - const auto R = S2 - y0; // y1 * z0^3 - y0 * z1 ^3 + const auto R = S2 - y0; // y1 * z0^3 - y0 * z1^3 const auto H_squared = H * H; const auto H_cubed = H * H_squared; @@ -403,21 +322,21 @@ constexpr ecc::JacPoint lin_func_and_dbl( const auto V = x0 * H_squared; const auto X3 = R_squared - H_cubed - (V + V); - const auto Y3 = R * (x0 * H_squared - X3) - y0 * H_cubed; + const auto Y3 = R * (V - X3) - y0 * H_cubed; const auto Z3 = H * z0; - t[0] = (z0 * z0_squared * x0 - U2 * z0_cubed); - t[1] = (S2 * z0_squared - y0 * z0_squared); + t[0] = -H * z0_cubed; // = x0·z0³ − U2·z0³ + t[1] = R * z0_squared; // = S2·z0² − y0·z0² t[2] = y0 * U2 - x0 * S2; - return ecc::JacPoint{X3, Y3, Z3}; + return ecc::ProjPoint{X3, Y3, Z3}; } /// Computes points P0 and P1 addition for twisted curve + line defined by untwisted P1 and P2 /// points on the curve (not twisted curve) evaluated at point P. Formula is simplified for P1.z /// == 1. For more details see https://notes.ethereum.org/@ipsilon/Hkn2a2qk0 constexpr void lin_func( - const ecc::JacPoint& P0, const ecc::Point& P1, std::array& t) noexcept + const ecc::ProjPoint& P0, const ecc::AffinePoint& P1, std::array& t) noexcept { const auto& x0 = P0.x; const auto& y0 = P0.y; @@ -432,8 +351,8 @@ constexpr void lin_func( const auto U2 = x1 * z0_squared; const auto S2 = y1 * z0_cubed; - t[0] = (z0 * z0_squared * x0 - U2 * z0_cubed); - t[1] = (S2 * z0_squared - y0 * z0_squared); + t[0] = (x0 - U2) * z0_cubed; // = x0·z0³ − U2·z0³ + t[1] = (S2 - y0) * z0_squared; // = S2·z0² − y0·z0² t[2] = y0 * U2 - x0 * S2; } @@ -457,7 +376,7 @@ constexpr void lin_func( /// Computes `a^2` for `a` from `Fq^4 = Fq^2[V](V^2 - ksi)` where `V` is from Fq^2 extended field. /// For more reference see https://eprint.iacr.org/2010/354.pdf Algorithm 9 -constexpr std::pair fq4_square(const std::pair& a) +constexpr std::pair fq4_square(const std::pair& a) noexcept { const auto& a0 = a.first; const auto& a1 = a.second; @@ -474,7 +393,7 @@ constexpr std::pair fq4_square(const std::pair& a) /// Computes `c^2` for `x` from Fq^12 where `x^(FieldPrime^6 - 1) == 1`. /// This is Fq^12 subgroup called cyclotomic polynomials or group of `r` roots of unity. -constexpr Fq12 cyclotomic_square(const Fq12& c) +constexpr Fq12 cyclotomic_square(const Fq12& c) noexcept { const auto& g = c.coeffs[0]; const auto& h = c.coeffs[1]; @@ -506,7 +425,7 @@ constexpr Fq12 cyclotomic_square(const Fq12& c) /// Computes `cyclotomic_square` N times. template -constexpr Fq12 n_cyclotomic_square(const Fq12& c) +constexpr Fq12 n_cyclotomic_square(const Fq12& c) noexcept { auto r = c; for (int i = 0; i < N; ++i) @@ -517,7 +436,7 @@ constexpr Fq12 n_cyclotomic_square(const Fq12& c) /// Computes `a^X` where `X` is the curve seed parameter /// and `a` is from cyclotomic subgroup of Fq^12. -constexpr Fq12 cyclotomic_pow_to_X(const Fq12& a) +constexpr Fq12 cyclotomic_pow_to_X(const Fq12& a) noexcept { auto t0 = cyclotomic_square(a); auto t2 = cyclotomic_square(t0); diff --git a/evmone/lib/evmone_precompiles/pairing/field_template.hpp b/evmone/lib/evmone_precompiles/pairing/field_template.hpp index 6b773fc..88c9fd3 100644 --- a/evmone/lib/evmone_precompiles/pairing/field_template.hpp +++ b/evmone/lib/evmone_precompiles/pairing/field_template.hpp @@ -4,6 +4,8 @@ #pragma once #include +#include +#include namespace evmmax::ecc { @@ -12,8 +14,8 @@ namespace evmmax::ecc template struct ExtFieldElem { - using ValueT = typename ConfigT::ValueT; - using Base = typename ConfigT::BaseFieldT; + using ValueT = ConfigT::ValueT; + using Base = ConfigT::BaseFieldT; static constexpr auto DEGREE = ConfigT::DEGREE; using CoeffArrT = std::array; @@ -26,12 +28,19 @@ struct ExtFieldElem /// TODO: This constructor may be optimized to avoid copying the array. explicit constexpr ExtFieldElem(const CoeffArrT& cs) noexcept : coeffs{cs} {} + /// Create an element from literal coefficient values, converted to the underlying + /// representation at compile-time. Allows defining constants as e.g. Fq2{1, 2}. + template + requires(sizeof...(Ts) == DEGREE && (std::constructible_from && ...) && + (!std::same_as, ValueT> && ...)) + consteval ExtFieldElem(const Ts&... cs) noexcept : coeffs{ValueT{cs}...} + {} + + /// Returns the conjugate of a degree-2 extension field element: (a, b) → (a, -b). constexpr ExtFieldElem conjugate() const noexcept + requires(DEGREE == 2) { - auto res = this->coeffs; - for (size_t i = 1; i < DEGREE; i += 2) - res[i] = -res[i]; - return ExtFieldElem(res); + return ExtFieldElem({coeffs[0], -coeffs[1]}); } static constexpr ExtFieldElem one() noexcept @@ -41,8 +50,6 @@ struct ExtFieldElem return res; } - static constexpr ExtFieldElem zero() noexcept { return ExtFieldElem{}; } - constexpr ExtFieldElem inv() const noexcept { return inverse(*this); } friend constexpr ExtFieldElem operator+(const ExtFieldElem& e1, const ExtFieldElem& e2) noexcept @@ -69,8 +76,14 @@ struct ExtFieldElem return ExtFieldElem(ret); } - friend constexpr ExtFieldElem operator*(const ExtFieldElem& e1, const ExtFieldElem& e2) noexcept + [[gnu::always_inline]] friend constexpr ExtFieldElem operator*( + const ExtFieldElem& e1, const ExtFieldElem& e2) noexcept { + if constexpr (requires { sqr(e1); }) // Use sqr() if available. + { + if (&e1 == &e2) + return sqr(e1); + } return multiply(e1, e2); } diff --git a/evmone/lib/evmone_precompiles/secp256k1.cpp b/evmone/lib/evmone_precompiles/secp256k1.cpp index cd64164..11b7758 100644 --- a/evmone/lib/evmone_precompiles/secp256k1.cpp +++ b/evmone/lib/evmone_precompiles/secp256k1.cpp @@ -45,8 +45,8 @@ evmc::address to_address(const AffinePoint& pt) noexcept } std::optional secp256k1_ecdsa_recover(std::span hash, - std::span r_bytes, std::span s_bytes, - bool parity) noexcept + std::span r_bytes, std::span s_bytes, bool parity, + RecoveryMode mode) noexcept { // Follows "Elliptic Curve Digital Signature Algorithm - Public key recovery" // https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm#Public_key_recovery @@ -56,7 +56,9 @@ std::optional secp256k1_ecdsa_recover(std::span if (!opt_r.has_value() || *opt_r == 0) [[unlikely]] return std::nullopt; - const auto opt_s = Curve::Fr::from_bytes(s_bytes); + const auto opt_s = mode == RecoveryMode::strict ? + Curve::Fr::from_bytes(s_bytes) : + Curve::Fr::from_bytes(s_bytes); if (!opt_s.has_value() || *opt_s == 0) [[unlikely]] return std::nullopt; @@ -93,11 +95,11 @@ std::optional secp256k1_ecdsa_recover(std::span } std::optional ecrecover(std::span hash, - std::span r_bytes, std::span s_bytes, - bool parity) noexcept + std::span r_bytes, std::span s_bytes, bool parity, + RecoveryMode mode) noexcept { // TODO(C++23): use std::optional::and_then. - const auto pubkey = secp256k1_ecdsa_recover(hash, r_bytes, s_bytes, parity); + const auto pubkey = secp256k1_ecdsa_recover(hash, r_bytes, s_bytes, parity, mode); if (!pubkey.has_value()) return std::nullopt; diff --git a/evmone/lib/evmone_precompiles/secp256k1.hpp b/evmone/lib/evmone_precompiles/secp256k1.hpp index 0bd6ddb..08b5108 100644 --- a/evmone/lib/evmone_precompiles/secp256k1.hpp +++ b/evmone/lib/evmone_precompiles/secp256k1.hpp @@ -57,12 +57,22 @@ evmc::address to_address(std::span pubkey) noexcept; /// Convert the secp256k1 point (uncompressed public key) to Ethereum address. evmc::address to_address(const AffinePoint& pt) noexcept; +/// The strictness of the signer recovery from a signature. +enum class RecoveryMode : bool +{ + strict, ///< Restrict s value range to lower half, prevents signature malleability (EIP-2). + malleable, ///< Full range for s value, signature is malleable. +}; + std::optional secp256k1_ecdsa_recover(std::span hash, - std::span r_bytes, std::span s_bytes, - bool parity) noexcept; + std::span r_bytes, std::span s_bytes, bool parity, + RecoveryMode mode) noexcept; +/// Recovers the address that signed the message @p hash. +/// +/// TODO: Make strict mode the default. std::optional ecrecover(std::span hash, - std::span r_bytes, std::span s_bytes, - bool parity) noexcept; + std::span r_bytes, std::span s_bytes, bool parity, + RecoveryMode mode = RecoveryMode::malleable) noexcept; } // namespace evmmax::secp256k1 diff --git a/evmone/lib/evmone_precompiles/sha256.cpp b/evmone/lib/evmone_precompiles/sha256.cpp index 420a421..672c343 100644 --- a/evmone/lib/evmone_precompiles/sha256.cpp +++ b/evmone/lib/evmone_precompiles/sha256.cpp @@ -135,7 +135,7 @@ static bool calc_chunk(uint8_t chunk[CHUNK_SIZE], struct BufferState* state) return true; } -[[gnu::always_inline, msvc::forceinline]] static void sha_256_implementation( +[[gnu::always_inline, msvc::forceinline]] static inline void sha_256_implementation( uint32_t h[8], const std::byte* input, size_t len) { /* @@ -251,7 +251,7 @@ __attribute__((target("bmi,bmi2"))) static void sha_256_x86_bmi( sha_256_implementation(h, input, len); } -[[gnu::always_inline]] static __m128i set(uint64_t a, uint64_t b) noexcept +[[gnu::always_inline]] static inline __m128i set(uint64_t a, uint64_t b) noexcept { // NOLINTNEXTLINE(*-runtime-int) return _mm_set_epi64x(static_cast(a), static_cast(b)); diff --git a/evmone/test/CMakeLists.txt b/evmone/test/CMakeLists.txt index 187d44b..4261870 100644 --- a/evmone/test/CMakeLists.txt +++ b/evmone/test/CMakeLists.txt @@ -15,25 +15,19 @@ find_package(GTest CONFIG REQUIRED) hunter_add_package(benchmark) find_package(benchmark CONFIG REQUIRED) -hunter_add_package(nlohmann_json) -find_package(nlohmann_json CONFIG REQUIRED) - include(CableBuildInfo) cable_add_buildinfo_library(PROJECT_NAME evmone) -add_subdirectory(utils) add_subdirectory(bench) add_subdirectory(blockchaintest) add_subdirectory(experimental) add_subdirectory(integration) add_subdirectory(internal_benchmarks) add_subdirectory(precompiles_bench) -add_subdirectory(state) add_subdirectory(statetest) -add_subdirectory(t8n) add_subdirectory(unittests) -set(targets evmone-bench evmone-bench-internal evmone-blockchaintest evmone-precompiles-bench evmone-state evmone-statetest evmone-t8n evmone-unittests) +set(targets evmone-bench evmone-bench-internal evmone-blockchaintest evmone-precompiles-bench evmone-state evmone-statetest evmone-unittests) if(EVMONE_FUZZING) add_subdirectory(fuzzer) diff --git a/evmone/test/bench/helpers.hpp b/evmone/test/bench/helpers.hpp index c682ea3..2f0f71a 100644 --- a/evmone/test/bench/helpers.hpp +++ b/evmone/test/bench/helpers.hpp @@ -145,10 +145,12 @@ inline void bench_execute(benchmark::State& state, evmc::VM& vm, bytes_view code } -constexpr auto bench_advanced_execute = bench_execute; -constexpr auto bench_baseline_execute = +// TODO(C++23): use constexpr. +inline auto bench_baseline_execute = bench_execute; inline void bench_evmc_execute(benchmark::State& state, evmc::VM& vm, bytes_view code, diff --git a/evmone/test/blockchaintest/blockchaintest_runner.cpp b/evmone/test/blockchaintest/blockchaintest_runner.cpp index 7c5d8c3..aa821e8 100644 --- a/evmone/test/blockchaintest/blockchaintest_runner.cpp +++ b/evmone/test/blockchaintest/blockchaintest_runner.cpp @@ -4,8 +4,12 @@ #include "blockchaintest_runner.hpp" #include +#include #include #include +#include +#include +#include #include #include #include @@ -21,161 +25,71 @@ constexpr size_t SAFETY_MARGIN = 2 * 1024 * 1024; /// The maximum EL block size when RLP encoded (EIP-7934). constexpr size_t MAX_RLP_BLOCK_SIZE = MAX_BLOCK_SIZE - SAFETY_MARGIN; -struct RejectedTransaction -{ - hash256 hash; - size_t index; - std::string message; -}; - -struct TransitionResult -{ - std::vector receipts; - std::vector rejected; - std::optional> requests; - int64_t gas_used; - state::BloomFilter bloom; - int64_t blob_gas_left; - TestState block_state; -}; - namespace { -TransitionResult apply_block(const TestState& state, evmc::VM& vm, const state::BlockInfo& block, - const state::BlockHashes& block_hashes, const std::vector& txs, - evmc_revision rev, std::optional block_reward) -{ - TestState block_state(state); - system_call_block_start(block_state, block, block_hashes, rev, vm); - - std::vector txs_logs; - int64_t block_gas_left = block.gas_limit; - auto blob_gas_left = static_cast(block.blob_gas_used.value_or(0)); - - std::vector rejected_txs; - std::vector receipts; - - int64_t cumulative_gas_used = 0; - - for (size_t i = 0; i < txs.size(); ++i) - { - const auto& tx = txs[i]; - - const auto computed_tx_hash = keccak256(rlp::encode(tx)); - auto res = test::transition( - block_state, block, block_hashes, tx, rev, vm, block_gas_left, blob_gas_left); - - if (holds_alternative(res)) - { - const auto ec = std::get(res); - rejected_txs.push_back({computed_tx_hash, i, ec.message()}); - } - else - { - auto& receipt = get(res); - - const auto& tx_logs = receipt.logs; - - txs_logs.insert(txs_logs.end(), tx_logs.begin(), tx_logs.end()); - cumulative_gas_used += receipt.gas_used; - receipt.cumulative_gas_used = cumulative_gas_used; - if (rev < EVMC_BYZANTIUM) - receipt.post_state = state::mpt_hash(block_state); - - block_gas_left -= receipt.gas_used; - blob_gas_left -= static_cast(tx.blob_gas_used()); - receipts.emplace_back(std::move(receipt)); - } - } - - auto requests = [&]() -> std::optional> { - std::vector collected; - - if (rev >= EVMC_PRAGUE) - { - auto opt_deposits = collect_deposit_requests(receipts); - if (!opt_deposits.has_value()) - return std::nullopt; - collected.emplace_back(std::move(*opt_deposits)); - } - - auto requests_result = system_call_block_end(block_state, block, block_hashes, rev, vm); - if (!requests_result.has_value()) - return std::nullopt; - std::ranges::move(*requests_result, std::back_inserter(collected)); - - return collected; - }(); - - finalize(block_state, rev, block.coinbase, block_reward, block.ommers, block.withdrawals); - - const auto bloom = compute_bloom_filter(receipts); - - return {std::move(receipts), std::move(rejected_txs), std::move(requests), cumulative_gas_used, - bloom, blob_gas_left, std::move(block_state)}; -} - -bool validate_block(evmc_revision rev, state::BlobParams blob_params, const TestBlock& test_block, - const BlockHeader* parent_header, bool parent_has_ommers) noexcept +/// Validates block-level validity unrelated to individual transactions. +/// +/// Returns an empty error_code if the block is valid, otherwise the specific validation error. +std::error_code validate_block(evmc_revision rev, state::BlobParams blob_params, + const TestBlock& test_block, const BlockHeader* parent_header, bool parent_has_ommers) noexcept { - // NOTE: includes only block validity unrelated to individual txs. See `apply_block`. + using namespace state; - // Fail if parent header was not found. + // Fail if parent header was not found: the block references a parent that is neither the + // genesis nor any previously-accepted block (an unknown or rejected parent). if (parent_header == nullptr) - return false; + return make_error_code(UNKNOWN_PARENT); if (test_block.block_info.number != parent_header->block_number + 1) - return false; + return make_error_code(INVALID_BLOCK_NUMBER); if (test_block.block_info.gas_used > test_block.block_info.gas_limit) - return false; + return make_error_code(INCORRECT_BLOCK_FORMAT); // Some tests have gas limit at INT64_MAX, so we cast to uint64_t to avoid overflow. const auto parent_header_gas_limit_u64 = static_cast(parent_header->gas_limit); const auto test_block_gas_limit_u64 = static_cast(test_block.block_info.gas_limit); if (test_block_gas_limit_u64 >= parent_header_gas_limit_u64 + parent_header_gas_limit_u64 / 1024) - return false; + return make_error_code(INVALID_GASLIMIT); if (test_block_gas_limit_u64 <= parent_header_gas_limit_u64 - parent_header_gas_limit_u64 / 1024) - return false; + return make_error_code(INVALID_GASLIMIT); // Block gas limit minimum from Yellow Paper. if (test_block.block_info.gas_limit < 5000) - return false; + return make_error_code(INVALID_GASLIMIT); // FIXME: Some tests have timestamp not fitting into int64_t, type has to be uint64_t. if (static_cast(test_block.block_info.timestamp) <= static_cast(parent_header->timestamp)) - return false; + return make_error_code(INVALID_BLOCK_TIMESTAMP_OLDER_THAN_PARENT); - if (test_block.block_info.difficulty != state::calculate_difficulty(parent_header->difficulty, - parent_has_ommers, parent_header->timestamp, - test_block.block_info.timestamp, - test_block.block_info.number, rev)) - return false; + if (test_block.block_info.difficulty != + calculate_difficulty(parent_header->difficulty, parent_has_ommers, parent_header->timestamp, + test_block.block_info.timestamp, test_block.block_info.number, rev)) + return make_error_code(INCORRECT_BLOCK_FORMAT); if (rev >= EVMC_PARIS && !test_block.block_info.ommers.empty()) - return false; - + return make_error_code(INCORRECT_BLOCK_FORMAT); for (const auto& ommer : test_block.block_info.ommers) { // Check that ommer block number difference with current block is within allowed range. // https://github.com/ethereum/execution-specs/blob/ee73be5c4d83a2e3c358bd14990878002e52ba9e/src/ethereum/gray_glacier/fork.py#L623 if (ommer.delta < 1 || ommer.delta > 6) - return false; + return make_error_code(INCORRECT_BLOCK_FORMAT); } if (test_block.block_info.extra_data.size() > 32) - return false; + return make_error_code(INCORRECT_BLOCK_FORMAT); if (rev >= EVMC_LONDON) { - const auto calculated_base_fee = state::calc_base_fee( + const auto calculated_base_fee = calc_base_fee( parent_header->gas_limit, parent_header->gas_used, parent_header->base_fee_per_gas); if (test_block.block_info.base_fee != calculated_base_fee) - return false; + return make_error_code(INVALID_BASEFEE_PER_GAS); } if (rev >= EVMC_CANCUN) @@ -183,45 +97,76 @@ bool validate_block(evmc_revision rev, state::BlobParams blob_params, const Test // `excess_blob_gas` and `blob_gas_used` mandatory after Cancun and invalid before. if (!test_block.block_info.excess_blob_gas.has_value() || !test_block.block_info.blob_gas_used.has_value()) - return false; + return make_error_code(INCORRECT_BLOCK_FORMAT); // Check that the excess blob gas was updated correctly. // According to EIP-7918 current blocks params (`rev`) should be used for parent base fee // calculation. const auto parent_blob_base_fee = - state::compute_blob_gas_price(blob_params, parent_header->excess_blob_gas.value_or(0)); + compute_blob_gas_price(blob_params, parent_header->excess_blob_gas.value_or(0)); if (*test_block.block_info.excess_blob_gas != - state::calc_excess_blob_gas(rev, blob_params, parent_header->blob_gas_used.value_or(0), + calc_excess_blob_gas(rev, blob_params, parent_header->blob_gas_used.value_or(0), parent_header->excess_blob_gas.value_or(0), parent_header->base_fee_per_gas, parent_blob_base_fee)) - return false; - - // Ensure the total blob gas spent is at most equal to the limit - if (*test_block.block_info.blob_gas_used > state::max_blob_gas_per_block(blob_params)) - return false; + return make_error_code(INCORRECT_EXCESS_BLOB_GAS); } else { if (test_block.block_info.excess_blob_gas.has_value() || test_block.block_info.blob_gas_used.has_value()) - return false; + return make_error_code(INCORRECT_BLOCK_FORMAT); } + // `slot_number` is mandatory from Amsterdam and invalid before (EIP-7843). + if (test_block.block_info.slot_number.has_value() != (rev >= EVMC_AMSTERDAM)) + return make_error_code(INCORRECT_BLOCK_FORMAT); + // Block is invalid if some of the withdrawal fields failed to be parsed. if (!test_block.withdrawals_parse_success) - return false; + return make_error_code(INCORRECT_BLOCK_FORMAT); - if (rev >= EVMC_OSAKA && test_block.rlp_size > MAX_RLP_BLOCK_SIZE) - return false; + if (rev >= EVMC_OSAKA && test_block.rlp.size() > MAX_RLP_BLOCK_SIZE) + return make_error_code(RLP_BLOCK_LIMIT_EXCEEDED); - return true; + return {}; } -std::optional mining_reward(evmc_revision rev) noexcept +/// Checks the transaction codec against a block's own serialization: every transaction in it must +/// decode, and encode back to the very same bytes. +void expect_transactions_round_trip(bytes_view block_rlp) +{ + bytes_view body; // A block is [header, transactions, ...]. + ASSERT_TRUE(rlp::take_list_payload(block_rlp, body)); + ASSERT_TRUE(block_rlp.empty()) << "trailing bytes after the block"; + bytes_view block_header; + ASSERT_TRUE(rlp::take_list_payload(body, block_header)); // Skipped over. + bytes_view txs; + ASSERT_TRUE(rlp::take_list_payload(body, txs)); + + while (!txs.empty()) + { + const auto item = txs; + rlp::Header h; + ASSERT_TRUE(rlp::decode_header(txs, h)); // Advances txs to the item's payload. + const auto header_size = item.size() - txs.size(); + txs.remove_prefix(h.payload_length); + + // A legacy transaction is an RLP list here, a typed one an RLP string wrapping the + // EIP-2718 envelope; the envelope alone is the transaction. + const auto tx_bytes = h.is_list ? item.substr(0, header_size + h.payload_length) : + item.substr(header_size, h.payload_length); + + const auto tx = state::decode_transaction(tx_bytes); + ASSERT_TRUE(tx.has_value()) << hex(tx_bytes); + EXPECT_EQ(rlp::encode(*tx), tx_bytes); + } +} + +std::optional mining_reward(evmc_revision rev) noexcept { if (rev < EVMC_BYZANTIUM) return 5'000000000'000000000; - if (rev < EVMC_CONSTANTINOPLE) + if (rev < EVMC_PETERSBURG) return 3'000000000'000000000; if (rev < EVMC_PARIS) return 2'000000000'000000000; @@ -287,6 +232,8 @@ void run_blockchain_tests(std::span tests, evmc::VM& vm) std::unordered_map block_data{{{c.genesis_block_header.hash, {&c.genesis_block_header, false, c.pre_state, c.genesis_block_header.difficulty}}}}; const auto* canonical_state = &c.pre_state; + hash256 canonical_state_root; // Skip pre-state root hash computation (maybe not needed). + auto canonical_tip_hash = c.genesis_block_header.hash; intx::uint256 max_total_difficulty = c.genesis_block_header.difficulty; for (size_t i = 0; i < c.test_blocks.size(); ++i) @@ -302,24 +249,32 @@ void run_blockchain_tests(std::span tests, evmc::VM& vm) const auto rev = rev_schedule.get_revision(bi.timestamp); const auto blob_params = get_blob_params(c.network, c.blob_schedule, bi.timestamp); + const auto blob_gas_limit = + static_cast(state::max_blob_gas_per_block(blob_params)); SCOPED_TRACE(std::string{evmc::to_string(rev)} + '/' + std::to_string(case_index) + '/' + c.name + '/' + std::to_string(test_block.block_info.number)); - if (test_block.valid) + // Invalid blocks are skipped: they may carry transactions that do not even decode. + if (test_block.expected_exception.empty()) + expect_transactions_round_trip(test_block.rlp); + + const auto block_error = + validate_block(rev, blob_params, test_block, parent_header, parent_has_ommers); + + if (test_block.expected_exception.empty()) { - ASSERT_TRUE( - validate_block(rev, blob_params, test_block, parent_header, parent_has_ommers)) - << "Expected block to be valid (validate_block)"; + ASSERT_FALSE(block_error) + << "Expected block to be valid (validate_block): " << block_error.message(); // Block being valid guarantees its parent was found. assert(parent_data_it != block_data.end()); const auto& pre_state = parent_data_it->second.post_state; auto res = apply_block(pre_state, vm, bi, block_hashes, test_block.transactions, - rev, mining_reward(rev)); + rev, blob_gas_limit, {.block_reward = mining_reward(rev)}); - ASSERT_TRUE(res.requests.has_value()); + ASSERT_FALSE(res.requests_error); block_hashes[test_block.expected_block_header.block_number] = test_block.expected_block_header.hash; @@ -331,19 +286,24 @@ void run_blockchain_tests(std::span tests, evmc::VM& vm) .total_difficulty = parent_data_it->second.total_difficulty + test_block.block_info.difficulty, }}); + + const auto state_root = state::mpt_hash(inserted_it->second.post_state); + if (inserted_it->second.total_difficulty >= max_total_difficulty) { canonical_state = &inserted_it->second.post_state; + canonical_state_root = state_root; + canonical_tip_hash = test_block.expected_block_header.hash; max_total_difficulty = inserted_it->second.total_difficulty; } EXPECT_TRUE(res.rejected.empty()) << "Invalid transaction in block expected to be valid"; - EXPECT_TRUE(res.blob_gas_left == 0) + EXPECT_EQ(blob_gas_limit - res.blob_gas_left, + static_cast(bi.blob_gas_used.value_or(0))) << "Transactions used more or less blob gas than expected in block header"; - EXPECT_EQ(state::mpt_hash(inserted_it->second.post_state), - test_block.expected_block_header.state_root); + EXPECT_EQ(state_root, test_block.expected_block_header.state_root); if (rev >= EVMC_SHANGHAI) { @@ -357,7 +317,7 @@ void run_blockchain_tests(std::span tests, evmc::VM& vm) state::mpt_hash(res.receipts), test_block.expected_block_header.receipts_root); if (rev >= EVMC_PRAGUE) { - EXPECT_EQ(calculate_requests_hash(*res.requests), + EXPECT_EQ(calculate_requests_hash(res.requests), test_block.expected_block_header.requests_hash); } EXPECT_EQ(res.gas_used, test_block.expected_block_header.gas_used); @@ -366,50 +326,149 @@ void run_blockchain_tests(std::span tests, evmc::VM& vm) } else { - if (!validate_block(rev, blob_params, test_block, parent_header, parent_has_ommers)) + if (block_error) + { + // Block correctly rejected at validation; verify the reason matches the + // fixture's expected exception. + EXPECT_TRUE( + is_expected_block_exception(block_error, test_block.expected_exception)) + << "Block invalidity reason mismatch: got " << block_error.message() + << ", expected " << test_block.expected_exception; continue; + } // Block being valid guarantees its parent was found. assert(parent_data_it != block_data.end()); const auto& pre_state = parent_data_it->second.post_state; - const auto res = apply_block(pre_state, vm, bi, block_hashes, - test_block.transactions, rev, mining_reward(rev)); - if (!res.requests.has_value()) - continue; + // Legacy fixtures name the broken rule in vocabulary evmone does not speak + // (InvalidStateRoot, TooManyUncles); only the spec names can be compared. + const auto names_spec_exception = + test_block.expected_exception.find("Exception.") != std::string::npos; + + // TODO: The transaction senders come from the fixture instead of being recovered + // from the signatures, so evmone never sees the signature the test broke. Such a + // transaction executes as the sender the fixture names and the block is rejected + // by whatever rule that sender happens to break, or by its state root alone. + const auto sender_not_recovered = contains_any( + test_block.expected_exception, "TransactionException.INVALID_SIGNATURE_VRS"); + + const auto res = + apply_block(pre_state, vm, bi, block_hashes, test_block.transactions, rev, + blob_gas_limit, {.block_reward = mining_reward(rev)}); if (!res.rejected.empty()) + { + // A transaction was rejected: the fixture must name that reason, not merely + // some rejection. + const auto& rejected = res.rejected.front(); + if (names_spec_exception && !sender_not_recovered) + { + EXPECT_TRUE( + is_expected_tx_exception(rejected.error, test_block.expected_exception)) + << "Transaction-level invalidity mismatch: got \"" + << rejected.error.message() << "\", expected " + << test_block.expected_exception; + } continue; - if (res.blob_gas_left != 0) + } + if (res.requests_error) + { + if (!sender_not_recovered) + { + EXPECT_TRUE(is_expected_block_exception( + res.requests_error, test_block.expected_exception)) + << "Block invalidity reason mismatch: got " + << res.requests_error.message() << ", expected " + << test_block.expected_exception; + } + continue; + } + // The block executed, so it is invalid only if it computes something other than + // its header claims. Each difference below is the symptom of one BlockException: + // a block failing a check other than the one the fixture names breaks a different + // rule than the test is about. + // TODO: Of the ommers only the count and the distance to their nephew are + // validated, not the ommer headers themselves, so a fixture that breaks an + // ommer's gas limit, number or timestamp reaches execution and lands here. + const auto ommers_not_validated = !test_block.block_info.ommers.empty(); + + // Asserts the fixture names one of @p names, the exceptions the check that just + // fired is the symptom of. Silent where the reason cannot be compared. + const auto expect_fixture_names = [&](std::string_view names) { + if (!names_spec_exception || ommers_not_validated || sender_not_recovered) + return; + EXPECT_TRUE(contains_any(test_block.expected_exception, names)) + << "Block invalidity reason mismatch: the block failed the check for " + << names << ", expected " << test_block.expected_exception; + }; + + if (blob_gas_limit - res.blob_gas_left != + static_cast(bi.blob_gas_used.value_or(0))) + { + expect_fixture_names( + "BlockException.INCORRECT_BLOB_GAS_USED|" + "BlockException.BLOB_GAS_USED_ABOVE_LIMIT"); continue; + } if (state::mpt_hash(res.block_state) != test_block.expected_block_header.state_root) + { + expect_fixture_names("BlockException.INVALID_STATE_ROOT"); continue; + } if (rev >= EVMC_SHANGHAI && state::mpt_hash(test_block.block_info.withdrawals) != test_block.expected_block_header.withdrawal_root) + { + expect_fixture_names("BlockException.INVALID_WITHDRAWALS_ROOT"); continue; + } if (state::mpt_hash(test_block.transactions) != test_block.expected_block_header.transactions_root) + { + expect_fixture_names("BlockException.INVALID_TRANSACTIONS_ROOT"); continue; + } if (state::mpt_hash(res.receipts) != test_block.expected_block_header.receipts_root) + { + expect_fixture_names("BlockException.INVALID_RECEIPTS_ROOT"); continue; - if (rev >= EVMC_PRAGUE && calculate_requests_hash(*res.requests) != + } + if (rev >= EVMC_PRAGUE && calculate_requests_hash(res.requests) != test_block.expected_block_header.requests_hash) + { + expect_fixture_names("BlockException.INVALID_REQUESTS"); continue; + } if (res.gas_used != test_block.expected_block_header.gas_used) + { + expect_fixture_names( + "BlockException.INVALID_GAS_USED|" + "BlockException.GAS_USED_OVERFLOW"); continue; + } if (bytes_view{res.bloom} != bytes_view{test_block.expected_block_header.logs_bloom}) + { + expect_fixture_names("BlockException.INVALID_LOG_BLOOM"); continue; + } EXPECT_TRUE(false) << "Expected block to be invalid but resulted valid"; } } + EXPECT_EQ(canonical_tip_hash, c.expectation.last_block_hash) + << "Canonical chain tip differs from expected `lastblockhash`"; + const auto expected_post_hash = std::holds_alternative(c.expectation.post_state) ? state::mpt_hash(std::get(c.expectation.post_state)) : std::get(c.expectation.post_state); - EXPECT_EQ(state::mpt_hash(*canonical_state), expected_post_hash) + + // Get the final state hash. In case none blocks have been applied, compute genesis one. + const auto canonical_post_hash = + canonical_state_root ? canonical_state_root : state::mpt_hash(c.pre_state); + EXPECT_EQ(canonical_post_hash, expected_post_hash) << "Result state:\n" << print_state(*canonical_state) << (std::holds_alternative(c.expectation.post_state) ? diff --git a/evmone/test/fuzzer/fuzzer.cpp b/evmone/test/fuzzer/fuzzer.cpp index ca3f015..07a4ccf 100644 --- a/evmone/test/fuzzer/fuzzer.cpp +++ b/evmone/test/fuzzer/fuzzer.cpp @@ -22,7 +22,7 @@ inline std::ostream& operator<<(std::ostream& os, const evmc_bytes32& v) return os << hex({v.bytes, sizeof(v.bytes)}); } -inline std::ostream& operator<<(std::ostream& os, const bytes_view& v) +inline std::ostream& operator<<(std::ostream& os, bytes_view v) { return os << hex(v); } @@ -80,14 +80,6 @@ class FuzzHost : public evmc::MockedHost else result.gas_left = msg.gas / (gas_left_factor + 3); - if (msg.kind == EVMC_CREATE || msg.kind == EVMC_CREATE2) - { - // Use the output to fill the create address. - // We still keep the output to check if VM is going to ignore it. - std::memcpy(result.create_address.bytes, result.output_data, - std::min(sizeof(result.create_address), result.output_size)); - } - return result; } }; @@ -202,7 +194,7 @@ fuzz_input populate_input(const uint8_t* data, size_t data_size) noexcept const auto destination_8bits = data[6]; const auto sender_8bits = data[7]; const auto value_8bits = data[8]; - const auto create2_salt_8bits = data[9]; + // data[9] unused (was the create2_salt, dropped from evmc_message). const auto tx_gas_price_8bits = data[10]; const auto tx_origin_8bits = data[11]; @@ -248,9 +240,6 @@ fuzz_input populate_input(const uint8_t* data, size_t data_size) noexcept in.msg.input_data = data; in.msg.value = generate_interesting_value(value_8bits); - // Should be ignored by VMs. - in.msg.create2_salt = generate_interesting_value(create2_salt_8bits); - data += in.msg.input_size; data_size -= in.msg.input_size; @@ -359,7 +348,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t data_size) noe ASSERT_EQ(bytes_view(m1.input_data, m1.input_size), bytes_view(m2.input_data, m2.input_size)); ASSERT_EQ(evmc::uint256be{m1.value}, evmc::uint256be{m2.value}); - ASSERT_EQ(evmc::bytes32{m1.create2_salt}, evmc::bytes32{m2.create2_salt}); } ASSERT(std::equal(ref_host.recorded_logs.begin(), ref_host.recorded_logs.end(), diff --git a/evmone/test/integration/CMakeLists.txt b/evmone/test/integration/CMakeLists.txt index ad030d7..93b5765 100644 --- a/evmone/test/integration/CMakeLists.txt +++ b/evmone/test/integration/CMakeLists.txt @@ -11,6 +11,16 @@ if(TARGET evmone-cli) ${PREFIX}/run PROPERTIES PASS_REGULAR_EXPRESSION "Result: success\nGas used: 3\n") + add_test(NAME ${PREFIX}/run_rev COMMAND evmone-cli run --rev TangerineWhistle 00) + set_tests_properties( + ${PREFIX}/run_rev PROPERTIES PASS_REGULAR_EXPRESSION + "Executing on TangerineWhistle") + + add_test(NAME ${PREFIX}/run_bad_rev COMMAND evmone-cli run --rev NoSuchRev 00) + set_tests_properties( + ${PREFIX}/run_bad_rev PROPERTIES PASS_REGULAR_EXPRESSION + "unknown revision: NoSuchRev") + add_test(NAME ${PREFIX}/version COMMAND evmone-cli --version) set_tests_properties( ${PREFIX}/version PROPERTIES PASS_REGULAR_EXPRESSION "evmone") diff --git a/evmone/test/integration/blockchaintest/CMakeLists.txt b/evmone/test/integration/blockchaintest/CMakeLists.txt index 96cea8f..f15ee53 100644 --- a/evmone/test/integration/blockchaintest/CMakeLists.txt +++ b/evmone/test/integration/blockchaintest/CMakeLists.txt @@ -17,5 +17,21 @@ set_tests_properties( PASS_REGULAR_EXPRESSION ".*2 tests from" ) +# Exercise block-level gas accounting (EIP-7778). +add_test( + NAME ${PREFIX}/eip7778_block_gas + COMMAND evmone-blockchaintest ${TESTS1}/eip7778_block_gas.json +) + +# A block invalid by its signature must not be judged by another check. +add_test( + NAME ${PREFIX}/unrecovered_sender_blob_gas + COMMAND evmone-blockchaintest ${TESTS1}/unrecovered_sender_blob_gas.json +) +add_test( + NAME ${PREFIX}/unrecovered_sender_deposit_log + COMMAND evmone-blockchaintest ${TESTS1}/unrecovered_sender_deposit_log.json +) + get_directory_property(ALL_TESTS TESTS) set_tests_properties(${ALL_TESTS} PROPERTIES ENVIRONMENT LLVM_PROFILE_FILE=${CMAKE_BINARY_DIR}/integration-%p.profraw) diff --git a/evmone/test/integration/blockchaintest/eip7778_block_gas.json b/evmone/test/integration/blockchaintest/eip7778_block_gas.json new file mode 100644 index 0000000..326c57e --- /dev/null +++ b/evmone/test/integration/blockchaintest/eip7778_block_gas.json @@ -0,0 +1,135 @@ +{ + "eip7778_block_gas_excludes_refund": { + "network": "Amsterdam", + "genesisBlockHeader": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "uncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "coinbase": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionsTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x00", + "number": "0x00", + "gasLimit": "0x01000000", + "gasUsed": "0x00", + "timestamp": "0x00", + "extraData": "0x00", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x10", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "blobGasUsed": "0x00", + "excessBlobGas": "0x00", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "requestsHash": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "slotNumber": "0x00", + "hash": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "pre": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "code": "0x", + "nonce": "0x00", + "balance": "0x02540be400" + }, + "0x000000000000000000000000000000000000c0de": { + "code": "0x6000600155", + "nonce": "0x01", + "balance": "0x00", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000042" + } + }, + "0x00000961ef480eb55e80d19ad83579a64c007002": { + "code": "0x00", + "nonce": "0x01", + "balance": "0x00" + }, + "0x0000bbddc7ce488642fb579f8b00f3a590007251": { + "code": "0x00", + "nonce": "0x01", + "balance": "0x00" + } + }, + "blocks": [ + { + "rlp": "0xf90285f90259a00000000000000000000000000000000000000000000000000000000000000001a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794000000000000000000000000000000000000c014a09906207bb91a552f2c9036ff75ffebd15d6262fe1f1dc40afa6e047158ee25e5a06e1d1e299aa4bca4b32c05a8bd8cf631d97c7d9b54b1401c373860db01a38efea0c220348cfca03fd761c800a2af15752cee5ba04f836e61f798c4c6222c0cab02b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800184010000008265960c80a000000000000000000000000000000000000000000000000000000000000000008800000000000000000ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218080a00000000000000000000000000000000000000000000000000000000000000000a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855e6a502e30180800e830186a094000000000000000000000000000000000000c0de8080c0800101c0c0", + "blockHeader": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "uncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "coinbase": "0x000000000000000000000000000000000000c014", + "stateRoot": "0x9906207bb91a552f2c9036ff75ffebd15d6262fe1f1dc40afa6e047158ee25e5", + "transactionsTrie": "0x6e1d1e299aa4bca4b32c05a8bd8cf631d97c7d9b54b1401c373860db01a38efe", + "receiptTrie": "0xc220348cfca03fd761c800a2af15752cee5ba04f836e61f798c4c6222c0cab02", + "bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x00", + "number": "0x01", + "gasLimit": "0x01000000", + "gasUsed": "0x6596", + "timestamp": "0x0c", + "extraData": "0x", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x0e", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "blobGasUsed": "0x00", + "excessBlobGas": "0x00", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "requestsHash": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "slotNumber": "0x00", + "hash": "0x0000000000000000000000000000000000000000000000000000000000000002" + }, + "transactions": [ + { + "type": "0x02", + "chainId": "0x01", + "nonce": "0x00", + "to": "0x000000000000000000000000000000000000c0de", + "value": "0x00", + "input": "0x", + "gasLimit": "0x186a0", + "maxFeePerGas": "0x0e", + "maxPriorityFeePerGas": "0x00", + "accessList": [], + "v": "0x00", + "r": "0x01", + "s": "0x01", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" + } + ], + "withdrawals": [], + "uncleHeaders": [] + } + ], + "lastblockhash": "0x0000000000000000000000000000000000000000000000000000000000000002", + "postState": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "code": "0x", + "nonce": "0x01", + "balance": "0x0254075c4c" + }, + "0x000000000000000000000000000000000000c0de": { + "code": "0x6000600155", + "nonce": "0x01", + "balance": "0x00" + }, + "0x00000961ef480eb55e80d19ad83579a64c007002": { + "code": "0x00", + "nonce": "0x01", + "balance": "0x00" + }, + "0x0000bbddc7ce488642fb579f8b00f3a590007251": { + "code": "0x00", + "nonce": "0x01", + "balance": "0x00" + } + }, + "config": { + "network": "Amsterdam", + "chainid": "0x01" + }, + "genesisRLP": "0x", + "sealEngine": "NoProof", + "_info": {} + } +} diff --git a/evmone/test/integration/blockchaintest/unrecovered_sender_blob_gas.json b/evmone/test/integration/blockchaintest/unrecovered_sender_blob_gas.json new file mode 100644 index 0000000..3cae9b0 --- /dev/null +++ b/evmone/test/integration/blockchaintest/unrecovered_sender_blob_gas.json @@ -0,0 +1,86 @@ +{ + "unrecovered_sender_blob_gas": { + "_info": { + "comment": "The runner takes the sender from the fixture, so it cannot see the broken signature; the transaction executes and the wrong blobGasUsed is the first header mismatch." + }, + "network": "Cancun", + "genesisBlockHeader": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionsTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "number": "0x00", + "gasLimit": "0x01000000", + "gasUsed": "0x00", + "timestamp": "0x00", + "extraData": "0x00", + "baseFeePerGas": "0x10", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "blobGasUsed": "0x00", + "excessBlobGas": "0x00", + "hash": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "pre": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "code": "0x", + "nonce": "0x00", + "balance": "0x02540be400" + } + }, + "blocks": [ + { + "expectException": "TransactionException.INVALID_SIGNATURE_VRS", + "rlp": "0x", + "rlp_decoded": { + "blockHeader": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "coinbase": "0x000000000000000000000000000000000000c014", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionsTrie": "0x0000000000000000000000000000000000000000000000000000000000000000", + "receiptTrie": "0x0000000000000000000000000000000000000000000000000000000000000000", + "bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "number": "0x01", + "gasLimit": "0x01000000", + "gasUsed": "0x00", + "timestamp": "0x0c", + "extraData": "0x", + "baseFeePerGas": "0x0e", + "blobGasUsed": "0x00", + "excessBlobGas": "0x00", + "hash": "0x0000000000000000000000000000000000000000000000000000000000000002" + }, + "transactions": [ + { + "type": "0x03", + "nonce": "0x00", + "to": "0x000000000000000000000000000000000000c0de", + "value": "0x00", + "input": "0x", + "gasLimit": "0x186a0", + "maxFeePerGas": "0x0e", + "maxPriorityFeePerGas": "0x00", + "maxFeePerBlobGas": "0x01", + "blobVersionedHashes": [ + "0x0100000000000000000000000000000000000000000000000000000000000001" + ], + "v": "0x00", + "r": "0x00", + "s": "0x01", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" + } + ] + } + } + ], + "lastblockhash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "postState": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "code": "0x", + "nonce": "0x00", + "balance": "0x02540be400" + } + } + } +} \ No newline at end of file diff --git a/evmone/test/integration/blockchaintest/unrecovered_sender_deposit_log.json b/evmone/test/integration/blockchaintest/unrecovered_sender_deposit_log.json new file mode 100644 index 0000000..5af9cf9 --- /dev/null +++ b/evmone/test/integration/blockchaintest/unrecovered_sender_deposit_log.json @@ -0,0 +1,92 @@ +{ + "unrecovered_sender_deposit_log": { + "_info": { + "comment": "As above, but the block reaches the requests check: the executed transaction makes the deposit contract emit a log with no data." + }, + "network": "Prague", + "genesisBlockHeader": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionsTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "number": "0x00", + "gasLimit": "0x01000000", + "gasUsed": "0x00", + "timestamp": "0x00", + "extraData": "0x00", + "baseFeePerGas": "0x10", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "blobGasUsed": "0x00", + "excessBlobGas": "0x00", + "hash": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "pre": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "code": "0x", + "nonce": "0x00", + "balance": "0x02540be400" + }, + "0x00000000219ab540356cbb839cbe05303d7705fa": { + "code": "0x7f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c560006000a1", + "nonce": "0x01", + "balance": "0x00" + } + }, + "blocks": [ + { + "expectException": "TransactionException.INVALID_SIGNATURE_VRS", + "rlp": "0x", + "rlp_decoded": { + "blockHeader": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "coinbase": "0x000000000000000000000000000000000000c014", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionsTrie": "0x0000000000000000000000000000000000000000000000000000000000000000", + "receiptTrie": "0x0000000000000000000000000000000000000000000000000000000000000000", + "bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "number": "0x01", + "gasLimit": "0x01000000", + "gasUsed": "0x00", + "timestamp": "0x0c", + "extraData": "0x", + "baseFeePerGas": "0x0e", + "blobGasUsed": "0x00", + "excessBlobGas": "0x00", + "hash": "0x0000000000000000000000000000000000000000000000000000000000000002" + }, + "transactions": [ + { + "type": "0x02", + "nonce": "0x00", + "to": "0x00000000219ab540356cbb839cbe05303d7705fa", + "value": "0x00", + "input": "0x", + "gasLimit": "0x186a0", + "maxFeePerGas": "0x0e", + "maxPriorityFeePerGas": "0x00", + "v": "0x00", + "r": "0x00", + "s": "0x01", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" + } + ] + } + } + ], + "lastblockhash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "postState": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "code": "0x", + "nonce": "0x00", + "balance": "0x02540be400" + }, + "0x00000000219ab540356cbb839cbe05303d7705fa": { + "code": "0x7f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c560006000a1", + "nonce": "0x01", + "balance": "0x00" + } + } + } +} \ No newline at end of file diff --git a/evmone/test/integration/statetest/CMakeLists.txt b/evmone/test/integration/statetest/CMakeLists.txt index 5cd1abb..1548a6c 100644 --- a/evmone/test/integration/statetest/CMakeLists.txt +++ b/evmone/test/integration/statetest/CMakeLists.txt @@ -113,7 +113,25 @@ add_test( ) set_tests_properties( ${PREFIX}/tx_invalid_nonce PROPERTIES - PASS_REGULAR_EXPRESSION "unexpected invalid transaction: nonce too high" + PASS_REGULAR_EXPRESSION "unexpected invalid transaction: TransactionException.NONCE_MISMATCH_TOO_HIGH" +) + +add_test( + NAME ${PREFIX}/tx_invalid_signature + COMMAND evmone-statetest ${TESTS_TX}/invalid_signature.json +) +set_tests_properties( + ${PREFIX}/tx_invalid_signature PROPERTIES + PASS_REGULAR_EXPRESSION "unexpected invalid transaction: TransactionException.INVALID_SIGNATURE_VRS" +) + +add_test( + NAME ${PREFIX}/tx_invalid_encoding + COMMAND evmone-statetest ${TESTS_TX}/invalid_encoding.json +) +set_tests_properties( + ${PREFIX}/tx_invalid_encoding PROPERTIES + PASS_REGULAR_EXPRESSION "unexpected invalid transaction: invalid transaction encoding" ) add_test( diff --git a/evmone/test/integration/statetest/tx/invalid_encoding.json b/evmone/test/integration/statetest/tx/invalid_encoding.json new file mode 100644 index 0000000..7b5286b --- /dev/null +++ b/evmone/test/integration/statetest/tx/invalid_encoding.json @@ -0,0 +1,43 @@ +{ + "invalid_encoding": { + "env": { + "currentBaseFee": "0x0a", + "currentCoinbase": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": "0x020000", + "currentGasLimit": "0xff112233445566", + "currentNumber": "0x01", + "currentRandom": "0x0000000000000000000000000000000000000000000000000000000000020000", + "currentTimestamp": "0x03e8" + }, + "post": { + "Shanghai": [ + { + "hash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "indexes": { + "data": 0, + "gas": 0, + "value": 0 + }, + "logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "txbytes": "0xc0" + } + ] + }, + "pre": {}, + "transaction": { + "data": [ + "0x" + ], + "gasLimit": [ + "0x5208" + ], + "gasPrice": "0x0a", + "nonce": "0x00", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "to": "", + "value": [ + "0x00" + ] + } + } +} diff --git a/evmone/test/integration/statetest/tx/invalid_signature.json b/evmone/test/integration/statetest/tx/invalid_signature.json new file mode 100644 index 0000000..43de114 --- /dev/null +++ b/evmone/test/integration/statetest/tx/invalid_signature.json @@ -0,0 +1,43 @@ +{ + "invalid_signature": { + "env": { + "currentBaseFee": "0x0a", + "currentCoinbase": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": "0x020000", + "currentGasLimit": "0xff112233445566", + "currentNumber": "0x01", + "currentRandom": "0x0000000000000000000000000000000000000000000000000000000000020000", + "currentTimestamp": "0x03e8" + }, + "post": { + "Shanghai": [ + { + "hash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "indexes": { + "data": 0, + "gas": 0, + "value": 0 + }, + "logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "txbytes": "0xeb800a8252088080801b01a0fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" + } + ] + }, + "pre": {}, + "transaction": { + "data": [ + "0x" + ], + "gasLimit": [ + "0x5208" + ], + "gasPrice": "0x0a", + "nonce": "0x00", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "to": "", + "value": [ + "0x00" + ] + } + } +} diff --git a/evmone/test/integration/t8n/CMakeLists.txt b/evmone/test/integration/t8n/CMakeLists.txt index de30dea..ae83fe2 100644 --- a/evmone/test/integration/t8n/CMakeLists.txt +++ b/evmone/test/integration/t8n/CMakeLists.txt @@ -2,7 +2,7 @@ # Copyright 2024 The evmone Authors. # SPDX-License-Identifier: Apache-2.0 -# Integration tests for evmone-t8n. +# Integration tests for the `evmone t8n` subcommand. set(PREFIX ${PREFIX}/t8n) @@ -12,7 +12,7 @@ add_test( NAME ${PREFIX}/${TEST_CASE} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/base_fee COMMAND - evmone-t8n + evmone-cli t8n --state.fork London --state.reward 0 --state.chainid 1 @@ -46,7 +46,7 @@ add_test( NAME ${PREFIX}/${TEST_CASE} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/base_fee COMMAND - evmone-t8n + evmone-cli t8n --state.fork Berlin --state.reward 0 --state.chainid 1 @@ -76,7 +76,7 @@ add_test( NAME ${PREFIX}/${TEST_CASE} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_CASE} COMMAND - evmone-t8n + evmone-cli t8n --state.fork Cancun --state.reward 0 --state.chainid 1 @@ -86,7 +86,6 @@ add_test( --output.basedir ${CMAKE_CURRENT_BINARY_DIR}/${TEST_CASE} --output.result out.json --output.alloc outAlloc.json - --output.errorlog error.json --opcode.count opcodes.json ) set_tests_properties(${PREFIX}/${TEST_CASE} PROPERTIES FIXTURES_REQUIRED ${TEST_CASE}) @@ -98,7 +97,7 @@ add_test( string( JOIN ".*" EXPECTED_OUT # Create blob transaction should be rejected: - [=["error": "blob transaction must not be a create transaction"]=] + [=["error": "TransactionException.TYPE_3_TX_CONTRACT_CREATION"]=] ) set_tests_properties( ${PREFIX}/${TEST_CASE}/out.json PROPERTIES @@ -157,7 +156,7 @@ add_test( NAME ${PREFIX}/${TEST_CASE} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_CASE} COMMAND - evmone-t8n + evmone-cli t8n --state.fork Prague --state.reward 0 --state.chainid 1 @@ -192,7 +191,7 @@ add_test( NAME ${PREFIX}/${TEST_CASE} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_CASE} COMMAND - evmone-t8n + evmone-cli t8n --state.fork Osaka --state.reward 0 --state.chainid 1 @@ -220,5 +219,92 @@ set_tests_properties( PASS_REGULAR_EXPRESSION ${EXPECTED_OUT} ) +set(TEST_CASE cancun_create_tx_trace) + +add_test( + NAME ${PREFIX}/${TEST_CASE} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/cancun_create_tx + COMMAND + evmone-cli t8n + --state.fork Cancun + --state.reward 0 + --state.chainid 1 + --input.alloc alloc.json + --input.txs txs.json + --input.env env.json + --output.basedir ${CMAKE_CURRENT_BINARY_DIR}/${TEST_CASE} + --output.result out.json + --trace +) +set_tests_properties(${PREFIX}/${TEST_CASE} PROPERTIES FIXTURES_REQUIRED ${TEST_CASE}) + +# The first tx in this fixture is a successful CREATE with bytecode 0x60015ff3 +# (PUSH1 0x01 PUSH0 RETURN). Its trace must be written to a per-tx file whose +# name includes the computed tx hash. +add_test( + NAME ${PREFIX}/${TEST_CASE}/trace-0.jsonl + COMMAND ${CMAKE_COMMAND} -E cat + ${CMAKE_CURRENT_BINARY_DIR}/${TEST_CASE}/trace-0-0x03141f8608bc78c8b8fb23275febeb6bf40bd348bd68b703a92bf2ce8ac8bdc0.jsonl +) +set_tests_properties( + ${PREFIX}/${TEST_CASE}/trace-0.jsonl PROPERTIES + FIXTURES_CLEANUP ${TEST_CASE} + PASS_REGULAR_EXPRESSION [=["opName":"PUSH1".*"opName":"PUSH0".*"opName":"RETURN"]=] +) + + +add_test(NAME ${PREFIX}/version COMMAND evmone-cli --version) +set_tests_properties( + ${PREFIX}/version PROPERTIES PASS_REGULAR_EXPRESSION "evmone") + +add_test(NAME ${PREFIX}/bad_fork COMMAND evmone-cli t8n --state.fork NoSuchRev) +set_tests_properties( + ${PREFIX}/bad_fork PROPERTIES PASS_REGULAR_EXPRESSION "unknown revision") + + +# Check if chainid max value is accepted. +add_test( + NAME ${PREFIX}/chainid_hex_max + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/base_fee + COMMAND + evmone-cli t8n + --state.fork Paris + --state.chainid 0xffffffffffffffff + --input.env env.json +) + + +# Exercises both --output.body (RLP-encoded transactions) and +# --state.reward -1 (pre-state-only mode that emits just the state root). +set(TEST_CASE prague_pre_state_only) + +add_test( + NAME ${PREFIX}/${TEST_CASE} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/prague_empty_requests + COMMAND + evmone-cli t8n + --state.fork Prague + --state.reward -1 + --state.chainid 1 + --input.alloc alloc.json + --input.txs txs.json + --input.env env.json + --output.basedir ${CMAKE_CURRENT_BINARY_DIR}/${TEST_CASE} + --output.result out.json + --output.body body.rlp +) +set_tests_properties(${PREFIX}/${TEST_CASE} PROPERTIES FIXTURES_REQUIRED ${TEST_CASE}) + +add_test( + NAME ${PREFIX}/${TEST_CASE}/body.rlp + COMMAND ${CMAKE_COMMAND} -E cat ${CMAKE_CURRENT_BINARY_DIR}/${TEST_CASE}/body.rlp +) +set_tests_properties( + ${PREFIX}/${TEST_CASE}/body.rlp PROPERTIES + FIXTURES_CLEANUP ${TEST_CASE} + PASS_REGULAR_EXPRESSION "^0x[0-9a-f]+" +) + + get_directory_property(ALL_TESTS TESTS) set_tests_properties(${ALL_TESTS} PROPERTIES ENVIRONMENT LLVM_PROFILE_FILE=${CMAKE_BINARY_DIR}/integration-%p.profraw) diff --git a/evmone/test/precompiles_bench/CMakeLists.txt b/evmone/test/precompiles_bench/CMakeLists.txt index 28237cf..6bc75c9 100644 --- a/evmone/test/precompiles_bench/CMakeLists.txt +++ b/evmone/test/precompiles_bench/CMakeLists.txt @@ -5,7 +5,7 @@ add_executable(evmone-precompiles-bench) target_compile_features(evmone-precompiles-bench PRIVATE cxx_std_20) target_include_directories(evmone-precompiles-bench PRIVATE ..) -target_link_libraries(evmone-precompiles-bench PRIVATE evmone::state benchmark::benchmark) +target_link_libraries(evmone-precompiles-bench PRIVATE evmone::state benchmark::benchmark intx::intx) target_sources( evmone-precompiles-bench PRIVATE precompiles_bench.cpp diff --git a/evmone/test/precompiles_bench/precompiles_bench.cpp b/evmone/test/precompiles_bench/precompiles_bench.cpp index a64aba1..2df05ce 100644 --- a/evmone/test/precompiles_bench/precompiles_bench.cpp +++ b/evmone/test/precompiles_bench/precompiles_bench.cpp @@ -153,6 +153,18 @@ const inline std::array inputs{ "000aa497c1b40b6f07989a7cca77c3e53dee08632433a90955d0c82f3a445937191c2f6a70d2cdd33e65907a67ebd0520e8394506e64cc43d805a3fd483e56990c0e8dc0367bcba1ec70160c11c981cd60432322c946a0a141cf7955e445e95506c1bcaeed380b606a7c05b696be5264dcb4da87342e6788c7adc536c121720d0bb73da5d6e75c353e3273c9a4196240d83f255f18af276c1ab2f5bff77f0ebb0f7cb99dc5db1aec72f855fc4fc1c6e9c0620405c8ff1343ff958a36cd2c05b301ebed5ab04cf6a52c91c5eeb8115f16d17f65e332972aae330b7f336a12ed5e28cade7e9fe17f6e3b37c95306c0e95601502c5b92c8eaa4b833ca8cb371829c2129813bd7247065ac58eac42c81e874044e199f48c12aa749a9fe6bb6e4bddc1b72b9ab4579283e62445555d5b2921424213d09a776152361c46988b82be8a7111bc8198f932e379b8f9825f01af0f5e5cacbf8bfe274bf674f6eaa6e338e04259f58d438fd6391e158c991e155966218e6a432703a84068a325439657498570ca8a4e8513506772cd613017c98f5d22bef7e6f0f11b020c69a11c42402ae1f0ea63a9620ea5f23af6814584b4839f4b5bc9bc444d6e3a45fdd314f33b5e47d198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa0228e296bbd09b1ab50534a35e2572c874ed6bc2d743d964c35d1ee6e07c5e91119dbba8ead4ff7f04b9824016551efaa4eafdd2d599ab029149d80bb28eb2b50106b9acd3190d55559d315663fd7c9328e349ed9e65609ed04965faa585cc431c440555fec15da4b137a22b124d13d089ed9fbb6255759b86abf9de6e1c761802a73357f1acee0de463c4d3f7535fb23b1317bf6f2ebd078cb7a2e06f882284226254d43204a6add64a6ba65c34c0e6dd13a10888686512efe422afa9608bc6"_hex, "08b36935645d3cf03da1a44e1c500220e192bc25d7c2258b0e0162daa4cb51ce12fa21e6b7c12050ae82728acf6ffc0d223bee2cd2fed72a8e666531ccd1ed7e01ef8a2f7b663d2929b81e5fddded62747fdb102360f83e268eaf61524bb9e6f0870d564fb18fadd334959b16b916f9cac89685972d38b991147913daa2ff4d609cb2d1f22a263b144f56f84b5ca7fc3b0cad00246e0fb824f8f4d364e4501c423758d28bc118dd4899d126d7eeb041bf255cdccd7052a7af68f6ce2911a22ca01ebed5ab04cf6a52c91c5eeb8115f16d17f65e332972aae330b7f336a12ed5e28cade7e9fe17f6e3b37c95306c0e95601502c5b92c8eaa4b833ca8cb371829c2129813bd7247065ac58eac42c81e874044e199f48c12aa749a9fe6bb6e4bddc1b72b9ab4579283e62445555d5b2921424213d09a776152361c46988b82be8a7111bc8198f932e379b8f9825f01af0f5e5cacbf8bfe274bf674f6eaa6e338e04259f58d438fd6391e158c991e155966218e6a432703a84068a3254396574985718895f9bae6485ebc32b6eb32f2606164d70c22eb69295caa770dc4df161c12202fc51b5f6b67577223fcb9443b66a5443f1229380ada014b89412c210851241198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa2b451f25ea40ef8a6bbf6685bdeb5c517d892b7df354afc9b115112402fdbcc6110e32080d1871020cacc07adbf2bd1f18335375798e7c65728d81fcad2a3110233f3ddcd7a051aa58d45f1cb43db3e34a73f4e8186f1ec0f683045e23c14fdb2eaa95d1fec9deced40c5be4803c6e81b565eb02efb180d5060981640116af4b2c2460c2348677dc303426ce941fd7370aa9af05e0b6edfd7bcd74615e3a894c0231a91b920126bf0547ac461f04a4d351db35c21d9936a11d53eb53ce4a13d2"_hex, "1bce8fa5482d9e50dfea4fb1dd94522e9b716f44d9918d6708fac0f0881938632a4338da2f280b176ab6924abb4775b55b508a8e36fff191023fdc5e99ba928b13bb5bd8d9a2b2822aacf155d7b22beebd4fe1910bd0dbb2834f2dc949f65cf60fc09cf79df13a761e90268de37a3595a7e9dc971fcaf7b26b784559036dfbb00c4f7aaa0d95a7cdeb1854ddef2a87b9393b6ce296e3c0ec20af93a0fde37abb2b8903140adc77bc3ed134c34571e16e949eb7d4a827da48859a9614aba82f722d4d9aa7e302d9df41749d5507949d05dbea33fbb16c643b22f599a2be6df2e214bedd503c37ceb061d8ec60209fe345ce89830a19230301f076caff004d19260967032fcbf776d1afc985f88877f182d38480a653f2decaa9794cbc3bf3060c0e187847ad4c798374d0d6732bf501847dd68bc0e071241e0213bc7fc13db7ab304cfbd1e08a704a99f5e847d93f8c3caafddec46b7a0d379da69a4d112346a71739c1b1a457a8c7313123d24d2f9192f896b7c63eea05a9d57f06547ad0cec829ad8a4a19c8558b7586b3748ef5315ba4520e87bf714177ded2b8b0dee8a03e067cb77535c18066291a1f20a16b7fb8f5d609ad5be42d1598bca446703d74c6198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa2d1b4674ee05363271f65f69fb5aa3d24145eb76f76bf49477e7950b8b4a050c18a6763f1cbe6d2334efcd94661dd802125011c7aba93167d8e25320640621882c4e22719c67bf7bddedcc98face8265be63ccdcef8139c221f21326aa48d1bd0700cbe071c394f095926c90427fe8a412216739e7633acb7c02cd719c12ecb92b34f9525b49f109aed50c68544acf38750b4c7334709d328de3973744953fcb0923b1a411590566dc946db78f12e46e568b91150d4b929ff7b4b5aba0fbab7b"_hex, + + // Mainnet samples (block ~25,185,265, May 2026) + "1d524526e2ec0fdb5b77a35faaf390ecfe2c28c2e1120d1b00f666d0aa9c461804c8f8cfe784fae0d790b44387b4437eb9ab47898d6ef99d5d472dda8577fb5b198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa29003fd2741009b7dea9132aa56e6a076ea1e17707b2b974c68b33d7591e9af52278a802eeafeae323ae2a7d58539e0ec2a88a928ab4a393925e8b1b305ad66b22f1acbb03c4508760c2430af35865e7cdf9f3eb1224504fdcc3708ddb954a482a344fad01c2ed0ed73142ae1752429eaea515c6f3f6b941103cc21c2308e1cb159f15b842ba9c8449aa3268f981010d4c7142e5193473d80b464e964845c3f80efd30ac7b6f8d0d3ccbc2207587c2acbad1532dc0293f0d034cf8258cd428b3"_hex, + "2606931fd484cbf0d7e144ddea50b9a56a2c24cebc5556c5c4a0ef3e9856d9bb1543e30d212d3fda57aada82f3abbf2b39cdc54969cfda32fbfc43dae97e0ff2198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa1de97bfec590e3be6311b7ed98f8ac345ea42539918ad4a020d93679bbde6cc9255388d7f36016cbef181483a23e054532d2cb8a426cf957ed2f651cd06f0c3922f1acbb03c4508760c2430af35865e7cdf9f3eb1224504fdcc3708ddb954a482a344fad01c2ed0ed73142ae1752429eaea515c6f3f6b941103cc21c2308e1cb159f15b842ba9c8449aa3268f981010d4c7142e5193473d80b464e964845c3f80efd30ac7b6f8d0d3ccbc2207587c2acbad1532dc0293f0d034cf8258cd428b3"_hex, + "0d2a8ca90a6bbc935b4c5601b22ac33ef2dde40ece0177a8bcc48c9b3ad5e81302c07f1c6a8e6a555b2466e31dab4f86f47abecc69125803c6133fafc45a06b10e95be775ba65324ce17547e7e6c3ec10c11a026fa86dd162413b48a958976dc044b89c742840c0597390076d087edb33d163b924678dcb56b46edda48d222810debf4d508118101f852b52c5a4c6e063671932fd9f5405a018057ea430141e7231231bae0ad91188bc2fbc2a2f8a2104d0d48c703317d0b8564eedc820f04f62d4d9aa7e302d9df41749d5507949d05dbea33fbb16c643b22f599a2be6df2e214bedd503c37ceb061d8ec60209fe345ce89830a19230301f076caff004d19260967032fcbf776d1afc985f88877f182d38480a653f2decaa9794cbc3bf3060c0e187847ad4c798374d0d6732bf501847dd68bc0e071241e0213bc7fc13db7ab304cfbd1e08a704a99f5e847d93f8c3caafddec46b7a0d379da69a4d112346a71739c1b1a457a8c7313123d24d2f9192f896b7c63eea05a9d57f06547ad0cec824f753aaa5ef12833b1bf2df7f64624a233b2cb6845b294b1eb86716de12f42a2982e796d468f156cbbf92284c6dd4fe36171f3fc92555b2df23145ee89e0660198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa2f028bb979d110ca09e2647e87e433211d70a87314c125d06a3b5f20c137794f017db5a88e833cbe7a65e13d14531dff5ef789470ad1cf82b76f05ef664378312fa8fb7f7bf2928301672c5e2c77a89f1fc8828f8fa09240ed74151e66a8181a01638e25e11481a901fd9733b37cae6f378b7b779ffeeadbb09d9414f6cf869d136b8353d9461b3a7d86a4ec07ca246fcd1fdf4bec6c52546c55e4209e4eca6910156d7aca5a1531a821c168754e70a7a3f0f5a8e1dfd0eaf7706949ffaffa39"_hex, + "2199d44354291a353f6b99ad566f21a15c34d48dada610f9cc16142b2ff4aeef02f3651b91df985b7be71398f10c6edc647a731f8627f0368ee0ebe9e51cc345198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa1c4f4f489c6de57a60ee45fc8177b2635022dfa794c3d893c0eb2ca86f0feb2b0fe3fe85ba8e1b3744d301558225d9d91293493952359ce65638bc499186aa1d22f1acbb03c4508760c2430af35865e7cdf9f3eb1224504fdcc3708ddb954a482a344fad01c2ed0ed73142ae1752429eaea515c6f3f6b941103cc21c2308e1cb159f15b842ba9c8449aa3268f981010d4c7142e5193473d80b464e964845c3f80efd30ac7b6f8d0d3ccbc2207587c2acbad1532dc0293f0d034cf8258cd428b3"_hex, + "20c08d5ecc68d6affd1a55cdb6b7f089f2d4283a119eef84cf49e49e4c8fac7a2e43dc58db9767f8e680475641f20392864f7f858aa3cad76eeb9677ca40013f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa01b4671c0087ef8673975406986e49ba7e5e400cb8bd9a4cbcfe56c0242c41e717cfd253db64555a7c1c7158eee6b330af6095ce0ed71d8f16fd57cc725b399d260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c10118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b004fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe422febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55"_hex, + "240967b08e02e5f204c0b76f8820d5625d0974b9b6935bab2e6472dfa8cd752b19856ce7ffc7dc89ae3180d41f37fd9cbc99552b4aea05b52c1c021b033e4602198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa2b05af5eb71902f41c1002e3f99deb0b9609876c6d15ae9ee0331d7a3a00da9b2f80c5f4565f158bf38fba294fe1d9e39c22f179d2955f43bf813519bbda6815260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c10118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b004fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe422febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55"_hex, + "26fc400fab4ed888d4692bd577f97b874061266a4298d3016578e8094a04ad6506ba7410b62c8600d8d5370b7f877a8c00b50e492f8685c8bb68b35ad9173a71198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa14786f19d081d55fb81b6d80f32ee0c407a2d906284a7c82b780b29be67196cb1dd40cf1318a45f36b0dc9389a572975c48c12888d16af99923b3c74ee84003b22f1acbb03c4508760c2430af35865e7cdf9f3eb1224504fdcc3708ddb954a482a344fad01c2ed0ed73142ae1752429eaea515c6f3f6b941103cc21c2308e1cb159f15b842ba9c8449aa3268f981010d4c7142e5193473d80b464e964845c3f80efd30ac7b6f8d0d3ccbc2207587c2acbad1532dc0293f0d034cf8258cd428b3"_hex, + "04fd0547bfc64e3cab80a1db8ed3be256146100f6348e73884aad97308d3661d13ca11f2e82bc81943a861b26cba2894e80f15d205f7bcb176525170c592661f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa166f67e02253a68295d0b699bdcff03fc5dc37e98a4a5f93e759724e0ac3ccd819782ac01f3f73e5178e30d331e87b3f134530903490168c5831cbaa68742a37260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c10118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b004fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe422febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55"_hex, + "2a16b11f883996a50e583bb8d3adcc093097c7236ccd6a5335b69223be910cf800b9a4869dc82ae3989288852ad2ae1319372cfdd28b455eda6a0473622c4c95198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa2eff6eec1c4d6f33974f3d880b3cf73d64089aed3936e815e796697ccf9badeb2b083159b876a6333941f3e49d2efeae19304837316f02c01cb64b2481bac7d622f1acbb03c4508760c2430af35865e7cdf9f3eb1224504fdcc3708ddb954a482a344fad01c2ed0ed73142ae1752429eaea515c6f3f6b941103cc21c2308e1cb159f15b842ba9c8449aa3268f981010d4c7142e5193473d80b464e964845c3f80efd30ac7b6f8d0d3ccbc2207587c2acbad1532dc0293f0d034cf8258cd428b3"_hex, + "0cc1e4b2b002c28a91ec6c5cbe3c3dbb79699750b186f1f5d1e38814393971b52d47f887abb448151c87b719bafe4f42efdfce850c6599de512d2d3c08f3a16a198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa2b18269e40fbf5d6df386e7dbce62f8580116a88e5d061f5393105f5ca71437f2495d4546d6323c637243433a6090f4a50ffafc5d3c6bc9f3ce2ffec2ac04a86260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c10118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b004fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe422febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55"_hex, }; template <> diff --git a/evmone/test/state/CMakeLists.txt b/evmone/test/state/CMakeLists.txt index f340018..29b9fc6 100644 --- a/evmone/test/state/CMakeLists.txt +++ b/evmone/test/state/CMakeLists.txt @@ -12,6 +12,8 @@ target_include_directories(evmone-state PUBLIC ${PROJECT_SOURCE_DIR}) target_sources( evmone-state PRIVATE account.hpp + authorization.hpp + authorization.cpp blob_params.hpp block.hpp block.cpp @@ -28,6 +30,9 @@ target_sources( precompiles_internal.hpp requests.hpp requests.cpp + rlp_common.hpp + rlp_decode.hpp + rlp_decode.cpp state.hpp state.cpp state_diff.hpp @@ -35,6 +40,7 @@ target_sources( system_contracts.hpp system_contracts.cpp transaction.hpp + transaction.cpp ) option(EVMONE_PRECOMPILES_LIBSECP256K1 "Enable precompiles implementations using libsecp256k1 library" OFF) diff --git a/evmone/test/state/account.hpp b/evmone/test/state/account.hpp index f4810ab..b135ec7 100644 --- a/evmone/test/state/account.hpp +++ b/evmone/test/state/account.hpp @@ -29,9 +29,6 @@ struct StorageValue /// The state account. struct Account { - /// The maximum allowed nonce value. - static constexpr auto NonceMax = std::numeric_limits::max(); - /// The keccak256 hash of the empty input. Used to identify empty account's code. static constexpr auto EMPTY_CODE_HASH = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470_bytes32; @@ -44,9 +41,6 @@ struct Account bytes32 code_hash = EMPTY_CODE_HASH; - /// If the account has non-empty initial storage (when accessing the cold account). - bool has_initial_storage = false; - /// The cached and modified account storage entries. std::unordered_map storage; @@ -59,7 +53,18 @@ struct Account /// Empty here only means it has not been loaded from the initial storage. bytes code; + // TODO: Consider moving the flags up (to lower offsets) for shorter x86 machine code (disp8). + + /// The account access status (EIP-2929): warm once accessed earlier in the transaction. + /// Revertible. + evmc_access_status access_status = EVMC_ACCESS_COLD; + + /// The account does not exist in the state. + /// Revertible, usually false→true. + bool nonexistent = false; + /// The account has been destructed and should be erased at the end of a transaction. + /// Revertible. bool destructed = false; /// The account should be erased if it is empty at the end of a transaction. @@ -68,15 +73,24 @@ struct Account /// /// Yellow Paper uses term "delete" but it is a keyword in C++ while /// the term "erase" is used for deleting objects from C++ collections. + /// + /// Revertible. bool erase_if_empty = false; /// The account has been created in the current transaction. + /// + /// FIXME: Not reverted on CREATE rollback; a leaked value suppresses the + /// EIP-161 touch-delete of the now-empty account (state-root divergence). bool just_created = false; - // This account's code has been modified. + /// This account's code has been modified. + /// + /// FIXME: Not reverted on CREATE rollback; a leaked value adds a + /// false-positive code entry to the state diff. bool code_changed = false; - evmc_access_status access_status = EVMC_ACCESS_COLD; + /// If the account has non-empty initial storage (when accessing the cold account). + bool has_initial_storage = false; [[nodiscard]] bool is_empty() const noexcept { diff --git a/evmone/test/state/authorization.cpp b/evmone/test/state/authorization.cpp new file mode 100644 index 0000000..399e1d7 --- /dev/null +++ b/evmone/test/state/authorization.cpp @@ -0,0 +1,40 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2026 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "authorization.hpp" +#include "hash_utils.hpp" +// TODO: Move the RLP encoder down into the state library, next to rlp_common.hpp. It lives in +// evmone.testutils, which links against evmone::state, so this include points the wrong way. +// It works only because the encoder is header-only. +#include "../utils/rlp.hpp" +#include + +namespace evmone::state +{ +namespace +{ +/// Computes the hash the authorization tuple is signed over (EIP-7702): +/// keccak256(0x05 || rlp([chain_id, address, nonce])). +bytes32 compute_authorization_signing_hash(const Authorization& auth) noexcept +{ + static constexpr uint8_t MAGIC = 0x05; + + // TODO: The preimage is at most 66 bytes, so it can be encoded in a local buffer. + // Find helpers in compute_create_address() and process_authorization_list(). + return keccak256(bytes{MAGIC} + rlp::encode_tuple(auth.chain_id, auth.addr, auth.nonce)); +} +} // namespace + +std::optional
recover_authority(const Authorization& auth) noexcept +{ + if (auth.y_parity > 1) + return std::nullopt; + + const auto h = compute_authorization_signing_hash(auth); + const auto r = intx::be::store(auth.r); + const auto s = intx::be::store(auth.s); + return evmmax::secp256k1::ecrecover( + h.bytes, r.bytes, s.bytes, auth.y_parity == 1, evmmax::secp256k1::RecoveryMode::strict); +} +} // namespace evmone::state diff --git a/evmone/test/state/authorization.hpp b/evmone/test/state/authorization.hpp new file mode 100644 index 0000000..41c9ab9 --- /dev/null +++ b/evmone/test/state/authorization.hpp @@ -0,0 +1,36 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2025 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include +#include +#include + +namespace evmone::state +{ +using evmc::address; +using intx::uint256; + +/// The set-code transaction authorization tuple (EIP-7702). +struct Authorization +{ + uint256 chain_id; + address addr; + uint64_t nonce = 0; + + /// The signature's y_parity. Valid values are 0 and 1, but an out-of-range value only + /// invalidates the authorization, not the transaction. + uint8_t y_parity = 0; + + // TODO: ecrecover takes byte spans, so bytes32 may be a better type for r and s. + uint256 r; + uint256 s; +}; + +using AuthorizationList = std::vector; + +/// Recovers the authority (the signer) of an authorization, std::nullopt if invalid (EIP-7702). +[[nodiscard]] std::optional
recover_authority(const Authorization& auth) noexcept; +} // namespace evmone::state diff --git a/evmone/test/state/block.hpp b/evmone/test/state/block.hpp index fdb8698..295f989 100644 --- a/evmone/test/state/block.hpp +++ b/evmone/test/state/block.hpp @@ -51,6 +51,9 @@ struct BlockInfo /// The EIP-1559 base fee, since London. uint64_t base_fee = 0; + /// The chain id transactions must target. + uint64_t chain_id = 1; + /// The "blob gas used" parameter from EIP-4844 std::optional blob_gas_used; @@ -61,6 +64,9 @@ struct BlockInfo /// Blob gas price from EIP-4844, computed from excess_blob_gas. std::optional blob_base_fee; + /// The beacon chain slot number (EIP-7843). Absent before Amsterdam. + std::optional slot_number; + std::vector ommers; std::vector withdrawals; }; diff --git a/evmone/test/state/bloom_filter.cpp b/evmone/test/state/bloom_filter.cpp index cc2e7e7..9ab8a4a 100644 --- a/evmone/test/state/bloom_filter.cpp +++ b/evmone/test/state/bloom_filter.cpp @@ -13,7 +13,7 @@ namespace /// Adds an entry to the bloom filter. /// based on /// https://ethereum.github.io/execution-specs/autoapi/ethereum/shanghai/bloom/index.html#add-to-bloom -inline void add_to(BloomFilter& bf, const bytes_view& entry) +inline void add_to(BloomFilter& bf, bytes_view entry) { const auto hash = keccak256(entry); @@ -54,7 +54,7 @@ BloomFilter compute_bloom_filter(std::span receipts) n return res; } -BloomFilter bloom_filter_from_bytes(const bytes_view& data) noexcept +BloomFilter bloom_filter_from_bytes(bytes_view data) noexcept { assert(data.size() == 256); BloomFilter res; diff --git a/evmone/test/state/bloom_filter.hpp b/evmone/test/state/bloom_filter.hpp index fcb8a02..aba77e3 100644 --- a/evmone/test/state/bloom_filter.hpp +++ b/evmone/test/state/bloom_filter.hpp @@ -31,6 +31,6 @@ struct BloomFilter std::span receipts) noexcept; /// Loads BloomFilter from bytes_view -BloomFilter bloom_filter_from_bytes(const bytes_view& data) noexcept; +BloomFilter bloom_filter_from_bytes(bytes_view data) noexcept; } // namespace evmone::state diff --git a/evmone/test/state/errors.hpp b/evmone/test/state/errors.hpp index e42e60b..2a66edd 100644 --- a/evmone/test/state/errors.hpp +++ b/evmone/test/state/errors.hpp @@ -9,29 +9,53 @@ namespace evmone::state { +/// The reasons a transaction or a block is rejected. +/// +/// The message of each is the execution-spec-tests exception name for the same rule, so a test can +/// compare a rejection against the `expectException` its fixture states. Where the specs name more +/// than one exception for a rule, the message is the canonical one and the test harness carries the +/// alternatives; the few rules the specs do not name at all keep a plain message. enum ErrorCode : int // NOLINT(*-use-enum-class) { SUCCESS = 0, INTRINSIC_GAS_TOO_LOW, - TX_TYPE_NOT_SUPPORTED, - INSUFFICIENT_FUNDS, - NONCE_HAS_MAX_VALUE, + TYPE_NOT_SUPPORTED, + INSUFFICIENT_ACCOUNT_FUNDS, + NONCE_IS_MAX, NONCE_TOO_HIGH, NONCE_TOO_LOW, - TIP_GT_FEE_CAP, - FEE_CAP_LESS_THAN_BLOCKS, - BLOB_FEE_CAP_LESS_THAN_BLOCKS, - GAS_LIMIT_REACHED, + PRIORITY_GREATER_THAN_MAX_FEE_PER_GAS, + INSUFFICIENT_MAX_FEE_PER_GAS, + INSUFFICIENT_MAX_FEE_PER_BLOB_GAS, + GAS_ALLOWANCE_EXCEEDED, SENDER_NOT_EOA, - INIT_CODE_SIZE_LIMIT_EXCEEDED, + INITCODE_SIZE_EXCEEDED, CREATE_BLOB_TX, EMPTY_BLOB_HASHES_LIST, INVALID_BLOB_HASH_VERSION, BLOB_GAS_LIMIT_EXCEEDED, CREATE_SET_CODE_TX, EMPTY_AUTHORIZATION_LIST, - MAX_GAS_LIMIT_EXCEEDED, + GAS_LIMIT_EXCEEDS_MAXIMUM, + INVALID_CHAIN_ID, + INVALID_ENCODING, + INVALID_SIGNATURE, UNKNOWN_ERROR, + + // Block-level validation. + INCORRECT_BLOCK_FORMAT, + INVALID_GASLIMIT, + INVALID_BASEFEE_PER_GAS, + INCORRECT_EXCESS_BLOB_GAS, + RLP_BLOCK_LIMIT_EXCEEDED, + INVALID_BLOCK_TIMESTAMP_OLDER_THAN_PARENT, + UNKNOWN_PARENT, + INVALID_BLOCK_NUMBER, + + // Block requests collection (EIP-7685). + INVALID_DEPOSIT_EVENT_LAYOUT, + SYSTEM_CONTRACT_EMPTY, + SYSTEM_CONTRACT_CALL_FAILED, }; /// Obtains a reference to the static error category object for evmone errors. @@ -48,45 +72,75 @@ inline const std::error_category& evmone_category() noexcept case SUCCESS: return ""; case INTRINSIC_GAS_TOO_LOW: - return "intrinsic gas too low"; - case TX_TYPE_NOT_SUPPORTED: - return "transaction type not supported"; - case INSUFFICIENT_FUNDS: - return "insufficient funds for gas * price + value"; - case NONCE_HAS_MAX_VALUE: - return "nonce has max value:"; + return "TransactionException.INTRINSIC_GAS_TOO_LOW"; + case TYPE_NOT_SUPPORTED: + return "TransactionException.TYPE_NOT_SUPPORTED"; + case INSUFFICIENT_ACCOUNT_FUNDS: + return "TransactionException.INSUFFICIENT_ACCOUNT_FUNDS"; + case NONCE_IS_MAX: + return "TransactionException.NONCE_IS_MAX"; case NONCE_TOO_HIGH: - return "nonce too high"; + return "TransactionException.NONCE_MISMATCH_TOO_HIGH"; case NONCE_TOO_LOW: - return "nonce too low"; - case TIP_GT_FEE_CAP: - return "max priority fee per gas higher than max fee per gas"; - case FEE_CAP_LESS_THAN_BLOCKS: - return "max fee per gas less than block base fee"; - case BLOB_FEE_CAP_LESS_THAN_BLOCKS: - return "max blob fee per gas less than block base fee"; - case GAS_LIMIT_REACHED: - return "gas limit reached"; + return "TransactionException.NONCE_MISMATCH_TOO_LOW"; + case PRIORITY_GREATER_THAN_MAX_FEE_PER_GAS: + return "TransactionException.PRIORITY_GREATER_THAN_MAX_FEE_PER_GAS"; + case INSUFFICIENT_MAX_FEE_PER_GAS: + return "TransactionException.INSUFFICIENT_MAX_FEE_PER_GAS"; + case INSUFFICIENT_MAX_FEE_PER_BLOB_GAS: + return "TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS"; + case GAS_ALLOWANCE_EXCEEDED: + return "TransactionException.GAS_ALLOWANCE_EXCEEDED"; case SENDER_NOT_EOA: - return "sender not an eoa:"; - case INIT_CODE_SIZE_LIMIT_EXCEEDED: - return "max initcode size exceeded"; + return "TransactionException.SENDER_NOT_EOA"; + case INITCODE_SIZE_EXCEEDED: + return "TransactionException.INITCODE_SIZE_EXCEEDED"; case CREATE_BLOB_TX: - return "blob transaction must not be a create transaction"; + return "TransactionException.TYPE_3_TX_CONTRACT_CREATION"; case EMPTY_BLOB_HASHES_LIST: - return "empty blob hashes list"; + return "TransactionException.TYPE_3_TX_ZERO_BLOBS"; case INVALID_BLOB_HASH_VERSION: - return "invalid blob hash version"; + return "TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH"; case BLOB_GAS_LIMIT_EXCEEDED: - return "blob gas limit exceeded"; + return "TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED"; case CREATE_SET_CODE_TX: - return "set code transaction must not be a create transaction"; + return "TransactionException.TYPE_4_TX_CONTRACT_CREATION"; case EMPTY_AUTHORIZATION_LIST: - return "empty authorization list"; - case MAX_GAS_LIMIT_EXCEEDED: - return "max gas limit exceeded"; + return "TransactionException.TYPE_4_EMPTY_AUTHORIZATION_LIST"; + case GAS_LIMIT_EXCEEDS_MAXIMUM: + return "TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM"; + case INVALID_CHAIN_ID: + return "TransactionException.INVALID_CHAINID"; + case INVALID_ENCODING: + // The execution specs name every way an encoding can be malformed separately + // (RLP_*), so there is no single constant standing for this one. + return "invalid transaction encoding"; + case INVALID_SIGNATURE: + return "TransactionException.INVALID_SIGNATURE_VRS"; case UNKNOWN_ERROR: - return "Unknown error"; + return "unknown error"; + case INCORRECT_BLOCK_FORMAT: + return "BlockException.INCORRECT_BLOCK_FORMAT"; + case INVALID_GASLIMIT: + return "BlockException.INVALID_GASLIMIT"; + case INVALID_BASEFEE_PER_GAS: + return "BlockException.INVALID_BASEFEE_PER_GAS"; + case INCORRECT_EXCESS_BLOB_GAS: + return "BlockException.INCORRECT_EXCESS_BLOB_GAS"; + case RLP_BLOCK_LIMIT_EXCEEDED: + return "BlockException.RLP_BLOCK_LIMIT_EXCEEDED"; + case INVALID_BLOCK_TIMESTAMP_OLDER_THAN_PARENT: + return "BlockException.INVALID_BLOCK_TIMESTAMP_OLDER_THAN_PARENT"; + case UNKNOWN_PARENT: + return "BlockException.UNKNOWN_PARENT"; + case INVALID_BLOCK_NUMBER: + return "BlockException.INVALID_BLOCK_NUMBER"; + case INVALID_DEPOSIT_EVENT_LAYOUT: + return "BlockException.INVALID_DEPOSIT_EVENT_LAYOUT"; + case SYSTEM_CONTRACT_EMPTY: + return "BlockException.SYSTEM_CONTRACT_EMPTY"; + case SYSTEM_CONTRACT_CALL_FAILED: + return "BlockException.SYSTEM_CONTRACT_CALL_FAILED"; default: assert(false); return "Wrong error code"; diff --git a/evmone/test/state/ethash_difficulty.cpp b/evmone/test/state/ethash_difficulty.cpp index 7363398..31254f2 100644 --- a/evmone/test/state/ethash_difficulty.cpp +++ b/evmone/test/state/ethash_difficulty.cpp @@ -18,7 +18,6 @@ int64_t get_bomb_delay(evmc_revision rev) noexcept return 0; case EVMC_BYZANTIUM: return 3'000'000; - case EVMC_CONSTANTINOPLE: case EVMC_PETERSBURG: case EVMC_ISTANBUL: return 5'000'000; diff --git a/evmone/test/state/host.cpp b/evmone/test/state/host.cpp index e335b95..bbefb81 100644 --- a/evmone/test/state/host.cpp +++ b/evmone/test/state/host.cpp @@ -4,6 +4,7 @@ #include "host.hpp" #include "precompiles.hpp" +#include "system_contracts.hpp" #include namespace evmone::state @@ -61,9 +62,7 @@ evmc_storage_status Host::set_storage( status = EVMC_STORAGE_MODIFIED_RESTORED; // X → Y → X } - // In Berlin this is handled in access_storage(). - if (m_rev < EVMC_BERLIN) - m_state.journal_storage_change(addr, key, storage_slot); + m_state.journal_storage_change(storage_slot); storage_slot.current = value; // Update current value. return status; } @@ -74,6 +73,12 @@ uint256be Host::get_balance(const address& addr) const noexcept return (acc != nullptr) ? intx::be::store(acc->balance) : uint256be{}; } +uint64_t Host::get_nonce(const address& addr) const noexcept +{ + const auto* const acc = m_state.find(addr); + return (acc != nullptr) ? acc->nonce : 0; +} + namespace { /// Check if an existing account is the "create collision" @@ -126,7 +131,7 @@ size_t Host::copy_code(const address& addr, size_t code_offset, uint8_t* buffer_ bool Host::selfdestruct(const address& addr, const address& beneficiary) noexcept { if (m_state.find(beneficiary) == nullptr) - m_state.journal_create(beneficiary, false); + m_state.journal_new_account(beneficiary); auto& acc = m_state.get(addr); const auto balance = acc.balance; auto& beneficiary_acc = m_state.touch(beneficiary); @@ -142,131 +147,53 @@ bool Host::selfdestruct(const address& addr, const address& beneficiary) noexcep acc.balance = 0; beneficiary_acc.balance += balance; // Keep balance if acc is the beneficiary. + if (m_rev >= EVMC_AMSTERDAM) + emit_transfer_log(m_logs, addr, beneficiary, balance); + // Return "selfdestruct not registered". // In practice this affects only refunds before Cancun. return false; } - // Transfer may happen multiple times per single account as account's balance - // can be increased with a call following previous selfdestruct. - beneficiary_acc.balance += balance; - acc.balance = 0; // Zero balance if acc is the beneficiary. + if (m_rev < EVMC_AMSTERDAM || beneficiary != addr) + { + // Transfer may happen multiple times per single account as account's balance + // can be increased with a call following previous selfdestruct. + beneficiary_acc.balance += balance; + acc.balance = 0; // Zero balance if acc is the beneficiary (before EIP-8246) + } + + if (m_rev >= EVMC_AMSTERDAM) + emit_transfer_log(m_logs, addr, beneficiary, balance); // Mark the destruction if not done already. if (!acc.destructed) { - m_state.journal_destruct(addr); + m_state.journal_account_flags(addr, acc); acc.destructed = true; return true; } return false; } -address compute_create_address(const address& sender, uint64_t sender_nonce) noexcept -{ - static constexpr auto RLP_STR_BASE = 0x80; - static constexpr auto RLP_LIST_BASE = 0xc0; - static constexpr auto ADDRESS_SIZE = sizeof(sender); - static constexpr std::ptrdiff_t MAX_NONCE_SIZE = sizeof(sender_nonce); - - uint8_t buffer[ADDRESS_SIZE + MAX_NONCE_SIZE + 3]; // 3 for RLP prefix bytes. - auto p = &buffer[1]; // Skip RLP list prefix for now. - *p++ = RLP_STR_BASE + ADDRESS_SIZE; // Set RLP string prefix for address. - p = std::copy_n(sender.bytes, ADDRESS_SIZE, p); - - if (sender_nonce < RLP_STR_BASE) // Short integer encoding including 0 as empty string (0x80). - { - *p++ = sender_nonce != 0 ? static_cast(sender_nonce) : RLP_STR_BASE; - } - else // Prefixed integer encoding. - { - // TODO: bit_width returns int after [LWG 3656](https://cplusplus.github.io/LWG/issue3656). - // NOLINTNEXTLINE(readability-redundant-casting) - const auto num_nonzero_bytes = static_cast((std::bit_width(sender_nonce) + 7) / 8); - *p++ = static_cast(RLP_STR_BASE + num_nonzero_bytes); - intx::be::unsafe::store(p, sender_nonce); - p = std::shift_left(p, p + MAX_NONCE_SIZE, MAX_NONCE_SIZE - num_nonzero_bytes); - } - - const auto total_size = static_cast(p - buffer); - buffer[0] = static_cast(RLP_LIST_BASE + (total_size - 1)); // Set the RLP list prefix. - - const auto base_hash = keccak256({buffer, total_size}); - address addr; - std::copy_n(&base_hash.bytes[sizeof(base_hash) - ADDRESS_SIZE], ADDRESS_SIZE, addr.bytes); - return addr; -} - -address compute_create2_address( - const address& sender, const bytes32& salt, bytes_view init_code) noexcept -{ - const auto init_code_hash = keccak256(init_code); - uint8_t buffer[1 + sizeof(sender) + sizeof(salt) + sizeof(init_code_hash)]; - static_assert(std::size(buffer) == 85); - auto it = std::begin(buffer); - *it++ = 0xff; - it = std::copy_n(sender.bytes, sizeof(sender), it); - it = std::copy_n(salt.bytes, sizeof(salt), it); - std::copy_n(init_code_hash.bytes, sizeof(init_code_hash), it); - const auto base_hash = keccak256({buffer, std::size(buffer)}); - address addr; - std::copy_n(&base_hash.bytes[sizeof(base_hash) - sizeof(addr)], sizeof(addr), addr.bytes); - return addr; -} - -std::optional Host::prepare_message(evmc_message msg) noexcept -{ - assert(msg.kind != EVMC_EOFCREATE); - if (msg.depth == 0 || msg.kind == EVMC_CREATE || msg.kind == EVMC_CREATE2) - { - auto& sender_acc = m_state.get(msg.sender); - - // EIP-2681 (already checked for depth 0 during transaction validation). - if (sender_acc.nonce == Account::NonceMax) - return {}; // Light early exception. - - if (msg.depth != 0) - { - m_state.journal_bump_nonce(msg.sender); - ++sender_acc.nonce; // Bump sender nonce. - } - - if (msg.kind == EVMC_CREATE || msg.kind == EVMC_CREATE2) - { - // Compute and set the address of the account being created. - assert(msg.recipient == address{}); - assert(msg.code_address == address{}); - // Nonce was already incremented, but creation calculation needs non-incremented value - assert(sender_acc.nonce != 0); - const auto creation_sender_nonce = sender_acc.nonce - 1; - if (msg.kind == EVMC_CREATE) - msg.recipient = compute_create_address(msg.sender, creation_sender_nonce); - else - { - assert(msg.kind == EVMC_CREATE2); - msg.recipient = compute_create2_address( - msg.sender, msg.create2_salt, {msg.input_data, msg.input_size}); - } - - // By EIP-2929, the access to new created address is never reverted. - access_account(msg.recipient); - } - } - - return msg; -} - evmc::Result Host::create(const evmc_message& msg) noexcept { assert(msg.kind == EVMC_CREATE || msg.kind == EVMC_CREATE2); + assert(msg.recipient != address{}); // Must be computed already. + // TODO: find()+insert() probes m_modified twice for a new recipient. auto* new_acc = m_state.find(msg.recipient); - const bool new_acc_exists = new_acc != nullptr; - if (!new_acc_exists) + if (new_acc == nullptr) + { new_acc = &m_state.insert(msg.recipient); - else if (is_create_collision(*new_acc)) - return evmc::Result{EVMC_FAILURE}; // TODO: Add EVMC errors for creation failures. - m_state.journal_create(msg.recipient, new_acc_exists); + m_state.journal_new_account(msg.recipient); + } + else + { + if (is_create_collision(*new_acc)) + return evmc::Result{EVMC_FAILURE}; // TODO: Add EVMC errors for creation failures. + m_state.journal_create(msg.recipient); + } assert(new_acc != nullptr); assert(new_acc->nonce == 0); @@ -284,81 +211,86 @@ evmc::Result Host::create(const evmc_message& msg) noexcept sender_acc.balance -= value; new_acc->balance += value; // The new account may be prefunded. + if (m_rev >= EVMC_AMSTERDAM) + emit_transfer_log(m_logs, msg.sender, msg.recipient, value); + auto create_msg = msg; create_msg.input_data = nullptr; create_msg.input_size = 0; const bytes_view initcode{msg.input_data, msg.input_size}; auto result = m_vm.execute(*this, m_rev, create_msg, initcode.data(), initcode.size()); if (result.status_code != EVMC_SUCCESS) - { - result.create_address = msg.recipient; return result; - } auto gas_left = result.gas_left; assert(gas_left >= 0); const bytes_view code{result.output_data, result.output_size}; - if (m_rev >= EVMC_SPURIOUS_DRAGON && code.size() > MAX_CODE_SIZE) + const size_t max_code_size = m_rev >= EVMC_AMSTERDAM ? MAX_CODE_SIZE_AMSTERDAM : MAX_CODE_SIZE; + if (m_rev >= EVMC_SPURIOUS_DRAGON && code.size() > max_code_size) return evmc::Result{EVMC_FAILURE}; + // Reject new contract code starting with the 0xEF byte (EIP-3541). + if (m_rev >= EVMC_LONDON && code.starts_with(0xEF)) + return evmc::Result{EVMC_CONTRACT_VALIDATION_FAILURE}; + // Code deployment cost. const auto cost = std::ssize(code) * 200; gas_left -= cost; if (gas_left < 0) { return (m_rev == EVMC_FRONTIER) ? - evmc::Result{EVMC_SUCCESS, result.gas_left, result.gas_refund, msg.recipient} : + evmc::Result{EVMC_SUCCESS, result.gas_left, result.gas_refund} : evmc::Result{EVMC_FAILURE}; } if (!code.empty()) { - // EIP-3541: Reject new contract code starting with the 0xEF byte. - if (m_rev >= EVMC_LONDON && code[0] == 0xEF) - return evmc::Result{EVMC_CONTRACT_VALIDATION_FAILURE}; - new_acc->code_hash = keccak256(code); new_acc->code = code; new_acc->code_changed = true; } - return evmc::Result{result.status_code, gas_left, result.gas_refund, msg.recipient}; + return evmc::Result{result.status_code, gas_left, result.gas_refund}; } evmc::Result Host::execute_message(const evmc_message& msg) noexcept { - assert(msg.kind != EVMC_EOFCREATE); if (msg.kind == EVMC_CREATE || msg.kind == EVMC_CREATE2) return create(msg); if (msg.kind == EVMC_CALL) { - const auto exists = m_state.find(msg.recipient) != nullptr; - if (!exists) - m_state.journal_create(msg.recipient, exists); - } + auto* recipient_acc = m_state.find(msg.recipient); + if (recipient_acc == nullptr) + m_state.journal_new_account(msg.recipient); + // TODO: Both branches will insert new account so better to do it in common path. - if (msg.kind == EVMC_CALL) - { if (evmc::is_zero(msg.value)) + { m_state.touch(msg.recipient); + } else { // We skip touching if we send value, because account cannot end up empty. // It will either have value, or code that transfers this value out, or will be // selfdestructed anyway. - auto& dst_acc = m_state.get_or_insert(msg.recipient); + if (recipient_acc == nullptr) + recipient_acc = &m_state.insert(msg.recipient); // Transfer value: sender → recipient. // The sender's balance is already checked therefore the sender account must exist. const auto value = intx::be::load(msg.value); - assert(m_state.get(msg.sender).balance >= value); - m_state.journal_balance_change(msg.sender, m_state.get(msg.sender).balance); - m_state.journal_balance_change(msg.recipient, dst_acc.balance); - m_state.get(msg.sender).balance -= value; - dst_acc.balance += value; + auto& sender_acc = m_state.get(msg.sender); + assert(sender_acc.balance >= value); + m_state.journal_balance_change(msg.sender, sender_acc.balance); + m_state.journal_balance_change(msg.recipient, recipient_acc->balance); + sender_acc.balance -= value; + recipient_acc->balance += value; + + if (m_rev >= EVMC_AMSTERDAM) + emit_transfer_log(m_logs, msg.sender, msg.recipient, value); } } @@ -374,30 +306,40 @@ evmc::Result Host::execute_message(const evmc_message& msg) noexcept return m_vm.execute(*this, m_rev, msg, code.data(), code.size()); } -evmc::Result Host::call(const evmc_message& orig_msg) noexcept +evmc::Result Host::call(const evmc_message& msg) noexcept { - const auto msg = prepare_message(orig_msg); - if (!msg.has_value()) - return evmc::Result{EVMC_FAILURE, orig_msg.gas}; // Light exception. + if (msg.depth != 0 && (msg.kind == EVMC_CREATE || msg.kind == EVMC_CREATE2)) + { + // Bump the creator's nonce (already done for depth 0). Not reverted if creation fails. + auto& sender_acc = m_state.get(msg.sender); + assert(sender_acc.nonce != MAX_NONCE); + m_state.journal_bump_nonce(msg.sender); + ++sender_acc.nonce; + } const auto logs_checkpoint = m_logs.size(); const auto state_checkpoint = m_state.checkpoint(); - auto result = execute_message(*msg); + auto result = execute_message(msg); if (result.status_code != EVMC_SUCCESS) { - static constexpr auto addr_03 = 0x03_address; - auto* const acc_03 = m_state.find(addr_03); - const auto is_03_touched = acc_03 != nullptr && acc_03->erase_if_empty; + // The 0x03 (RIPEMD-160) touch quirk: a touch on this address is + // never reverted. It only matters when the account is empty, so gate it by rev range. + static constexpr auto ADDR_03 = 0x03_address; + bool is_03_touched = false; + if (m_rev < EVMC_PARIS && m_rev >= EVMC_SPURIOUS_DRAGON) [[unlikely]] + { + const auto* const acc_03 = m_state.find(ADDR_03); + is_03_touched = acc_03 != nullptr && acc_03->erase_if_empty; + } // Revert. m_state.rollback(state_checkpoint); m_logs.resize(logs_checkpoint); - // The 0x03 quirk: the touch on this address is never reverted. - if (is_03_touched && m_rev >= EVMC_SPURIOUS_DRAGON) - m_state.touch(addr_03); + if (is_03_touched) [[unlikely]] + m_state.touch(ADDR_03); } return result; } @@ -419,11 +361,12 @@ evmc_tx_context Host::get_tx_context() const noexcept m_block.timestamp, m_block.gas_limit, m_block.prev_randao, - 0x01_bytes32, // Chain ID is expected to be 1. + uint256be{m_block.chain_id}, uint256be{m_block.base_fee}, intx::be::store(m_block.blob_base_fee.value_or(0)), m_tx.blob_hashes.data(), m_tx.blob_hashes.size(), + m_block.slot_number.value_or(0), }; } @@ -443,21 +386,32 @@ evmc_access_status Host::access_account(const address& addr) noexcept if (m_rev < EVMC_BERLIN) return EVMC_ACCESS_COLD; // Ignore before Berlin. - auto& acc = m_state.get_or_insert(addr, {.erase_if_empty = true}); + auto* acc = m_state.find(addr); - if (acc.access_status == EVMC_ACCESS_WARM || is_precompile(m_rev, addr)) + if (acc != nullptr && acc->access_status == EVMC_ACCESS_WARM) return EVMC_ACCESS_WARM; - m_state.journal_access_account(addr); - acc.access_status = EVMC_ACCESS_WARM; + if (is_precompile(m_rev, addr)) // Precompiles are always warm. Don't insert to state. + return EVMC_ACCESS_WARM; + + // TODO: On a modified-set miss the account is looked up twice. This can be improved with + // a try_emplace-like API, but the miss happens only in ~39% of the calls on Mainnet. + if (acc == nullptr) + acc = &m_state.insert(addr, {.erase_if_empty = true}); + + m_state.journal_account_flags(addr, *acc); + acc->access_status = EVMC_ACCESS_WARM; return EVMC_ACCESS_COLD; } evmc_access_status Host::access_storage(const address& addr, const bytes32& key) noexcept { auto& storage_slot = m_state.get_storage(addr, key); - m_state.journal_storage_change(addr, key, storage_slot); - return std::exchange(storage_slot.access_status, EVMC_ACCESS_WARM); + if (storage_slot.access_status == EVMC_ACCESS_WARM) + return EVMC_ACCESS_WARM; // Nothing changes, skip journaling. + m_state.journal_storage_change(storage_slot); + storage_slot.access_status = EVMC_ACCESS_WARM; + return EVMC_ACCESS_COLD; } @@ -472,7 +426,7 @@ void Host::set_transient_storage( const address& addr, const bytes32& key, const bytes32& value) noexcept { auto& slot = m_state.get(addr).transient_storage[key]; - m_state.journal_transient_storage_change(addr, key, slot); + m_state.journal_transient_storage_change(slot); slot = value; } } // namespace evmone::state diff --git a/evmone/test/state/host.hpp b/evmone/test/state/host.hpp index 32408bb..4b2ffcf 100644 --- a/evmone/test/state/host.hpp +++ b/evmone/test/state/host.hpp @@ -6,34 +6,12 @@ #include "state.hpp" #include "state_view.hpp" -#include +#include namespace evmone::state { using evmc::uint256be; -/// Computes the address of to-be-created contract with the CREATE scheme. -/// -/// Computes the new account address for the contract creation context of the CREATE instruction -/// or a create transaction. -/// This is defined by 𝐀𝐃𝐃𝐑 in Yellow Paper, 7. Contract Creation, (88-90), the case for ζ = ∅. -/// -/// @param sender The address of the message sender. YP: 𝑠. -/// @param sender_nonce The sender's nonce before the increase. YP: 𝑛. -/// @return The address computed with the CREATE scheme. -[[nodiscard]] address compute_create_address(const address& sender, uint64_t sender_nonce) noexcept; - -/// Computes the address of to-be-created contract with the CREATE2 scheme. -/// -/// Computes the new account address for the contract creation context of the CREATE2 instruction. -/// -/// @param sender The address of the message sender. -/// @param salt The salt. -/// @param init_code The init_code to hash (initcode or initcontainer). -/// @return The address computed with the scheme. -[[nodiscard]] address compute_create2_address( - const address& sender, const bytes32& salt, bytes_view init_code) noexcept; - class Host : public evmc::Host { evmc_revision m_rev; @@ -71,6 +49,8 @@ class Host : public evmc::Host [[nodiscard]] uint256be get_balance(const address& addr) const noexcept override; + [[nodiscard]] uint64_t get_nonce(const address& addr) const noexcept override; + [[nodiscard]] size_t get_code_size(const address& addr) const noexcept override; [[nodiscard]] bytes32 get_code_hash(const address& addr) const noexcept override; @@ -95,14 +75,6 @@ class Host : public evmc::Host private: evmc_access_status access_storage(const address& addr, const bytes32& key) noexcept override; - /// Prepares message for execution. - /// - /// This contains mostly checks and logic related to the sender - /// which may finally be moved to EVM. - /// Any state modification is not reverted. - /// @return Modified message or std::nullopt in case of EVM exception. - std::optional prepare_message(evmc_message msg) noexcept; - evmc::Result execute_message(const evmc_message& msg) noexcept; }; } // namespace evmone::state diff --git a/evmone/test/state/precompiles.cpp b/evmone/test/state/precompiles.cpp index 3ae3bae..557eb1b 100644 --- a/evmone/test/state/precompiles.cpp +++ b/evmone/test/state/precompiles.cpp @@ -546,21 +546,25 @@ ExecutionResult ecpairing_execute(const uint8_t* input, size_t input_size, uint8 if (input_size % PAIR_SIZE != 0) return {EVMC_PRECOMPILE_FAILURE, 0}; - std::vector> pairs; + std::vector> pairs; pairs.reserve(input_size / PAIR_SIZE); // TODO: may throw std::bad_alloc. for (auto input_ptr = input; input_ptr != input + input_size; input_ptr += PAIR_SIZE) { - const evmmax::bn254::Point p{ - intx::be::unsafe::load(input_ptr), - intx::be::unsafe::load(input_ptr + 32), - }; - const evmmax::bn254::ExtPoint q{ - {intx::be::unsafe::load(input_ptr + 96), - intx::be::unsafe::load(input_ptr + 64)}, - {intx::be::unsafe::load(input_ptr + 160), - intx::be::unsafe::load(input_ptr + 128)}, - }; - pairs.emplace_back(p, q); + namespace bn = evmmax::bn254; + const auto p = bn::AffinePoint::from_bytes(std::span{input_ptr, 64}); + if (!p.has_value()) [[unlikely]] + return {EVMC_PRECOMPILE_FAILURE, 0}; + + // G2 EVM ABI feeds the imaginary coefficient before the real one for each Fq², + // so swap the offsets when reading into (real, imaginary) order. + const auto qx_real = bn::Fq::from_bytes(std::span{input_ptr + 96, 32}); + const auto qx_imag = bn::Fq::from_bytes(std::span{input_ptr + 64, 32}); + const auto qy_real = bn::Fq::from_bytes(std::span{input_ptr + 160, 32}); + const auto qy_imag = bn::Fq::from_bytes(std::span{input_ptr + 128, 32}); + if (!qx_real || !qx_imag || !qy_real || !qy_imag) [[unlikely]] + return {EVMC_PRECOMPILE_FAILURE, 0}; + const bn::ExtPoint q{bn::Fq2({*qx_real, *qx_imag}), bn::Fq2({*qy_real, *qy_imag})}; + pairs.emplace_back(*p, q); } const auto res = evmmax::bn254::pairing_check(pairs); diff --git a/evmone/test/state/rlp_common.hpp b/evmone/test/state/rlp_common.hpp new file mode 100644 index 0000000..473bf52 --- /dev/null +++ b/evmone/test/state/rlp_common.hpp @@ -0,0 +1,20 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2026 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +/// @file +/// RLP prefix encoding constants (Yellow Paper Appendix B), shared by the encoder and the decoder. + +#include +#include + +namespace evmone::rlp +{ +/// The largest payload encoded in the short form; longer payloads use the long form. +constexpr size_t SHORT_LENGTH_LIMIT = 55; +/// Base of a byte-string prefix: a short string is this byte plus its length (0x80..0xb7). +constexpr uint8_t SHORT_STRING_BASE = 0x80; +/// Base of a list prefix: a short list is this byte plus its payload length (0xc0..0xf7). +constexpr uint8_t SHORT_LIST_BASE = 0xc0; +} // namespace evmone::rlp diff --git a/evmone/test/state/rlp_decode.cpp b/evmone/test/state/rlp_decode.cpp new file mode 100644 index 0000000..b5439a8 --- /dev/null +++ b/evmone/test/state/rlp_decode.cpp @@ -0,0 +1,117 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2026 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "rlp_decode.hpp" +#include +#include + +namespace evmone::rlp +{ +namespace +{ +/// Decodes the long form of a header: the payload length is a big-endian integer of +/// @p len_of_len bytes following the prefix. The string and list variants differ only in the +/// decoded kind, so @tparam IsList selects it. +template +[[nodiscard]] bool decode_long_header(bytes_view& input, Header& out, uint8_t len_of_len) noexcept +{ + const auto input_len = input.size(); + if (len_of_len >= input_len) + return false; + + // Canonicality: the encoded length must not have leading zero bytes. + if (input[1] == 0) + return false; + + const auto payload_len = load(input.substr(1, len_of_len)); + // Overflow-safe form of `1 + len_of_len + payload_len > input_len` + // (len_of_len < input_len is guaranteed above). + if (payload_len >= input_len - len_of_len) + return false; + + // Canonicality: the long form is reserved for payloads longer than the short-form maximum. + if (payload_len <= SHORT_LENGTH_LIMIT) + return false; + + input.remove_prefix(1 + len_of_len); + assert(payload_len <= std::numeric_limits::max()); // Inputs stay well below 4 GiB. + out = {static_cast(payload_len), IsList}; + return true; +} +} // namespace + +bool decode_header(bytes_view& input, Header& out) noexcept +{ + constexpr uint8_t LONG_STRING_BASE = SHORT_STRING_BASE + SHORT_LENGTH_LIMIT; // 0xb7 + constexpr uint8_t LONG_LIST_BASE = SHORT_LIST_BASE + SHORT_LENGTH_LIMIT; // 0xf7 + + const auto input_len = input.size(); + + if (input_len == 0) + return false; + + const auto prefix = input[0]; + + if (prefix < SHORT_STRING_BASE) // [0x00, 0x7f] a single byte is its own encoding. + { + out = {1, false}; + return true; + } + else if (prefix <= LONG_STRING_BASE) // [0x80, 0xb7] short string + { + const uint8_t len = prefix - SHORT_STRING_BASE; + if (len >= input_len) + return false; + + // Canonicality: a single byte < 0x80 must be encoded as itself, not as + // a 1-byte string with the 0x81 prefix. + if (len == 1 && input[1] < SHORT_STRING_BASE) + return false; + + input.remove_prefix(1); + out = {len, false}; + return true; + } + else if (prefix < SHORT_LIST_BASE) // [0xb8, 0xbf] long string + { + return decode_long_header( + input, out, static_cast(prefix - LONG_STRING_BASE)); + } + else if (prefix <= LONG_LIST_BASE) // [0xc0, 0xf7] short list + { + const uint8_t list_len = prefix - SHORT_LIST_BASE; + if (list_len >= input_len) + return false; + + input.remove_prefix(1); + out = {list_len, true}; + return true; + } + else // [0xf8, 0xff] long list + { + return decode_long_header(input, out, static_cast(prefix - LONG_LIST_BASE)); + } +} + +bool decode(bytes_view& from, bytes& to) noexcept +{ + Header h; + if (!decode_header(from, h) || h.is_list) + return false; + + to = from.substr(0, h.payload_length); + from.remove_prefix(h.payload_length); + return true; +} + +bool decode(bytes_view& from, evmc::bytes32& to) noexcept +{ + return decode(from, to.bytes); +} + +bool decode(bytes_view& from, evmc::address& to) noexcept +{ + return decode(from, to.bytes); +} +} // namespace evmone::rlp diff --git a/evmone/test/state/rlp_decode.hpp b/evmone/test/state/rlp_decode.hpp new file mode 100644 index 0000000..161dca5 --- /dev/null +++ b/evmone/test/state/rlp_decode.hpp @@ -0,0 +1,149 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2026 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include "rlp_common.hpp" +#include +#include +#include +#include +#include +#include +#include + +/// RLP decoding primitives, the counterpart of the encoding in test/utils/rlp.hpp. +/// Each decode returns false on malformed input; none throw. A failure may leave @p from and the +/// destination partially updated, so a caller must reject the whole input rather than resume. +/// +/// TODO: Prefer view types over copies of the input. Every decoded payload is a subrange of the +/// input, so a byte string could be handed out as a bytes_view (and a list as a range of views) +/// instead of being copied into bytes/std::vector. That requires the destination fields +/// (Transaction::data and friends) to be views over an input buffer outliving them. +namespace evmone::rlp +{ +using evmc::bytes; +using evmc::bytes_view; + +template +concept UnsignedIntegral = std::unsigned_integral || std::same_as; + +/// Loads an unsigned integer from big-endian bytes no wider than T. +template +[[nodiscard]] inline T load(bytes_view input) noexcept +{ + return intx::be::load(std::span{input.data(), input.size()}); +} + +struct Header +{ + /// The payload length. 32-bit: it never exceeds the input size and a decoded input (a + /// transaction or block) is far below 4 GiB, so it always widens to size_t without a cast. + uint32_t payload_length = 0; + bool is_list = false; +}; + +/// Decodes the RLP header, advancing @p input to the payload; a single byte in [0x00, 0x7f] is its +/// own payload, so @p input is left at it. On success the payload fits: payload_length <= +/// input.size(). Returns false on malformed input, leaving @p input unchanged. +[[nodiscard]] bool decode_header(bytes_view& input, Header& out) noexcept; + +/// Reads an RLP list header, advances @p from past the list, and returns its bounded payload. +[[nodiscard]] inline bool take_list_payload(bytes_view& from, bytes_view& payload) noexcept +{ + Header h; + if (!decode_header(from, h) || !h.is_list) + return false; + payload = from.substr(0, h.payload_length); + from.remove_prefix(h.payload_length); + return true; +} + +/// Decodes a variable-length unsigned integer: any big-endian payload up to sizeof(T) bytes, +/// with no leading zeros (non-canonical), so the zero value is the empty payload 0x80. +template +[[nodiscard]] bool decode(bytes_view& from, T& to) noexcept +{ + Header h; + if (!decode_header(from, h) || h.is_list || sizeof(T) < h.payload_length) + return false; + + // Reject leading zeros (non-canonical integer). + if (h.payload_length > 0 && from[0] == 0) + return false; + + to = load(from.substr(0, h.payload_length)); + from.remove_prefix(h.payload_length); + return true; +} + +/// Decodes a variable-length byte string of any length. +[[nodiscard]] bool decode(bytes_view& from, bytes& to) noexcept; + +/// Decodes a fixed-width value: the payload must be exactly the type's size (32 bytes for a hash, +/// 20 for an address). A shorter, longer, or zero-padded encoding is rejected. +[[nodiscard]] bool decode(bytes_view& from, evmc::bytes32& to) noexcept; +[[nodiscard]] bool decode(bytes_view& from, evmc::address& to) noexcept; + +/// Decodes a fixed-width field into a byte span: the payload must be exactly N bytes. +template +[[nodiscard]] bool decode(bytes_view& from, std::span to) noexcept +{ + Header h; + if (!decode_header(from, h) || h.is_list || h.payload_length != to.size()) + return false; + + std::ranges::copy(from.substr(0, to.size()), to.begin()); + from.remove_prefix(to.size()); + return true; +} + +template +[[nodiscard]] bool decode(bytes_view& from, uint8_t (&to)[N]) noexcept +{ + return decode(from, std::span(to)); +} + +// Forward declaration for the vector-of-pairs case. +template +[[nodiscard]] bool decode(bytes_view& from, std::pair& p) noexcept; + +/// Decodes an RLP list into a vector, one element after another until the payload is exhausted. +/// A malformed element leaves @p to intact. +template +[[nodiscard]] bool decode(bytes_view& from, std::vector& to) noexcept +{ + bytes_view payload_view; + if (!take_list_payload(from, payload_view)) + return false; + + std::vector elements; // Assigned to `to` only on success, leaving it intact on failure. + while (!payload_view.empty()) + { + elements.emplace_back(); + if (!decode(payload_view, elements.back())) + return false; + } + + to = std::move(elements); + return true; +} + +/// Decodes a two-element RLP list into a pair. +template +[[nodiscard]] bool decode(bytes_view& from, std::pair& p) noexcept +{ + bytes_view payload_view; + if (!take_list_payload(from, payload_view)) + return false; + + return decode(payload_view, p.first) && decode(payload_view, p.second) && payload_view.empty(); +} + +/// Decodes a run of fields in order, stopping at the first failure. +template +[[nodiscard]] inline bool decode_multi(bytes_view& from, Ts&... items) noexcept +{ + return (decode(from, items) && ...); +} +} // namespace evmone::rlp diff --git a/evmone/test/state/state.cpp b/evmone/test/state/state.cpp index 92affe8..1046e29 100644 --- a/evmone/test/state/state.cpp +++ b/evmone/test/state/state.cpp @@ -5,11 +5,12 @@ #include "state.hpp" #include "../utils/stdx/utility.hpp" #include "host.hpp" +#include "precompiles.hpp" #include "state_view.hpp" #include #include -#include #include +#include using namespace intx; @@ -17,8 +18,6 @@ namespace evmone::state { namespace { -/// Secp256k1's N/2 is the upper bound of the signature's s value. -constexpr auto SECP256K1N_OVER_2 = evmmax::secp256k1::Curve::ORDER / 2; /// EIP-7702: The cost of authorization that sets delegation to an account that didn't exist before. constexpr auto AUTHORIZATION_EMPTY_ACCOUNT_COST = 25000; /// EIP-7702: The cost of authorization that sets delegation to an account that already exists. @@ -38,15 +37,18 @@ size_t compute_tx_data_tokens(evmc_revision rev, bytes_view data) noexcept return (nonzero_byte_multiplier * num_nonzero_bytes) + num_zero_bytes; } -int64_t compute_access_list_cost(const AccessList& access_list) noexcept +struct AccessListCounts { - static constexpr auto ADDRESS_COST = 2400; - static constexpr auto STORAGE_KEY_COST = 1900; + size_t num_addresses = 0; + size_t num_storage_keys = 0; +}; - int64_t cost = 0; - for (const auto& [_, keys] : access_list) - cost += ADDRESS_COST + static_cast(keys.size()) * STORAGE_KEY_COST; - return cost; +AccessListCounts count_access_list(const AccessList& access_list) noexcept +{ + size_t num_storage_keys = 0; + for (const auto& keys : access_list | std::views::values) + num_storage_keys += keys.size(); + return {access_list.size(), num_storage_keys}; } struct TransactionCost @@ -55,14 +57,19 @@ struct TransactionCost int64_t min = 0; }; -/// Compute the transaction intrinsic gas 𝑔₀ (Yellow Paper, 6.2) and minimal gas (EIP-7623). +/// Compute the transaction intrinsic gas 𝑔₀ (Yellow Paper, 6.2) and minimal gas (floor cost). TransactionCost compute_tx_intrinsic_cost(evmc_revision rev, const Transaction& tx) noexcept { static constexpr auto TX_BASE_COST = 21000; static constexpr auto TX_CREATE_COST = 32000; + static constexpr auto ACCESS_LIST_ADDRESS_COST = 2400; + static constexpr auto ACCESS_LIST_STORAGE_KEY_COST = 1900; + static constexpr auto ACCESS_LIST_ADDRESS_BYTES = 20; + static constexpr auto ACCESS_LIST_STORAGE_KEY_BYTES = 32; static constexpr auto DATA_TOKEN_COST = 4; static constexpr auto INITCODE_WORD_COST = 2; static constexpr auto TOTAL_COST_FLOOR_PER_TOKEN = 10; + static constexpr auto TOTAL_COST_FLOOR_PER_BYTE = 16 * 4; const auto is_create = !tx.to.has_value(); @@ -71,7 +78,12 @@ TransactionCost compute_tx_intrinsic_cost(evmc_revision rev, const Transaction& const auto num_tokens = static_cast(compute_tx_data_tokens(rev, tx.data)); const auto data_cost = num_tokens * DATA_TOKEN_COST; - const auto access_list_cost = compute_access_list_cost(tx.access_list); + const auto [num_addresses, num_storage_keys] = count_access_list(tx.access_list); + const auto access_list_num_bytes = + static_cast(num_addresses * ACCESS_LIST_ADDRESS_BYTES + + num_storage_keys * ACCESS_LIST_STORAGE_KEY_BYTES); + const auto access_list_cost = static_cast( + num_addresses * ACCESS_LIST_ADDRESS_COST + num_storage_keys * ACCESS_LIST_STORAGE_KEY_COST); const auto auth_list_cost = static_cast(tx.authorization_list.size()) * AUTHORIZATION_EMPTY_ACCOUNT_COST; @@ -79,12 +91,22 @@ TransactionCost compute_tx_intrinsic_cost(evmc_revision rev, const Transaction& const auto initcode_cost = (is_create && rev >= EVMC_SHANGHAI) ? INITCODE_WORD_COST * num_words(tx.data.size()) : 0; - const auto intrinsic_cost = - TX_BASE_COST + create_cost + data_cost + access_list_cost + auth_list_cost + initcode_cost; + // Charge a flat cost per access-list byte (EIP-7981). + const auto access_list_data_cost = + (rev >= EVMC_AMSTERDAM) ? access_list_num_bytes * TOTAL_COST_FLOOR_PER_BYTE : 0; + + const auto intrinsic_cost = TX_BASE_COST + create_cost + data_cost + access_list_data_cost + + access_list_cost + auth_list_cost + initcode_cost; + + int64_t data_min_cost = 0; + if (rev >= EVMC_AMSTERDAM) // Unified cost per byte (EIP-7976). + data_min_cost = TOTAL_COST_FLOOR_PER_BYTE * static_cast(tx.data.size()); + else if (rev >= EVMC_PRAGUE) // Cost per token capturing num of zero-nonzero bytes (EIP-7623). + data_min_cost = TOTAL_COST_FLOOR_PER_TOKEN * num_tokens; - // EIP-7623: Compute the minimum cost for the transaction by. If disabled, just use 0. + // Compute "floor" cost (EIP-7623). const auto min_cost = - rev >= EVMC_PRAGUE ? TX_BASE_COST + num_tokens * TOTAL_COST_FLOOR_PER_TOKEN : 0; + (rev >= EVMC_PRAGUE) ? TX_BASE_COST + data_min_cost + access_list_data_cost : 0; return {intrinsic_cost, min_cost}; } @@ -100,33 +122,25 @@ int64_t process_authorization_list( continue; // 2. Verify the nonce is less than 2**64 - 1. - if (auth.nonce == Account::NonceMax) + if (auth.nonce == MAX_NONCE) continue; - // 3. Verify if the signer has been successfully recovered from the signature. + // 3. Verify if the authority has been successfully recovered from the signature. // authority = ecrecover(...) - // y_parity must be 0 or 1 for EIP-7702/2930 signatures. - if (auth.v > 1) - continue; - // TODO: We actually only do "partial" verification by assuming the signature is valid - // when the test has the signer specified. - if (!auth.signer.has_value()) - continue; - - // s value must be less than or equal to secp256k1n/2, as specified in EIP-2. - if (auth.s > SECP256K1N_OVER_2) + const auto authority_addr = recover_authority(auth); + if (!authority_addr.has_value()) continue; // Get or create the authority account. // It is still empty at this point until nonce bump following successful authorization. - auto& authority = state.get_or_insert(*auth.signer, {.erase_if_empty = true}); + auto& authority = state.get_or_insert(*authority_addr, {.erase_if_empty = true}); // 4. Add authority to accessed_addresses (as defined in EIP-2929.) authority.access_status = EVMC_ACCESS_WARM; // 5. Verify the code of authority is either empty or already delegated. if (authority.code_hash != Account::EMPTY_CODE_HASH && - !is_code_delegated(state.get_code(*auth.signer))) + !is_code_delegated(state.get_code(*authority_addr))) continue; // 6. Verify the nonce of authority is equal to nonce. @@ -161,13 +175,16 @@ int64_t process_authorization_list( // 8. Set the code of authority to be 0xef0100 || address. This is a delegation designation. else { - auto new_code = bytes(DELEGATION_MAGIC) + bytes(auth.addr); - if (authority.code != new_code) + uint8_t designation_buf[std::size(DELEGATION_MAGIC) + sizeof(auth.addr)]; + const auto it = std::ranges::copy(DELEGATION_MAGIC, std::begin(designation_buf)).out; + std::ranges::copy(auth.addr.bytes, it); + const bytes_view designation{designation_buf, std::size(designation_buf)}; + if (authority.code != designation) { // We are doing this only if the code is different to make the state diff precise. authority.code_changed = true; - authority.code = std::move(new_code); - authority.code_hash = keccak256(authority.code); + authority.code = designation; + authority.code_hash = keccak256(designation); } } @@ -179,7 +196,7 @@ int64_t process_authorization_list( evmc_message build_message(const Transaction& tx, int64_t execution_gas_limit) noexcept { - const auto recipient = tx.to.has_value() ? *tx.to : evmc::address{}; + const auto recipient = tx.to.has_value() ? *tx.to : compute_create_address(tx.sender, tx.nonce); return { .kind = tx.to.has_value() ? EVMC_CALL : EVMC_CREATE, @@ -191,7 +208,6 @@ evmc_message build_message(const Transaction& tx, int64_t execution_gas_limit) n .input_data = tx.data.data(), .input_size = tx.data.size(), .value = intx::be::store(tx.value), - .create2_salt = {}, .code_address = recipient, .code = nullptr, .code_size = 0, @@ -202,13 +218,23 @@ evmc_message build_message(const Transaction& tx, int64_t execution_gas_limit) n StateDiff State::build_diff(evmc_revision rev) const { StateDiff diff; + diff.modified_accounts.reserve(m_modified.size()); for (const auto& [addr, m] : m_modified) { + if (m.nonexistent) + continue; if (m.destructed) { - // TODO: This must be done even for just_created - // because destructed may pre-date just_created. Add test to evmone (EEST has it). - diff.deleted_accounts.emplace_back(addr); + if (rev >= EVMC_AMSTERDAM && m.balance != 0) + { + // Preserve the balance of the self-destructed account, no burn (EIP-8246). + diff.modified_accounts.emplace_back(StateDiff::Entry{addr, 0, m.balance}); + } + else + { + // Delete account. This must be done also for pre-funded just_created account. + diff.deleted_accounts.emplace_back(addr); + } continue; } if (m.erase_if_empty && rev >= EVMC_SPURIOUS_DRAGON && m.is_empty()) @@ -241,17 +267,22 @@ StateDiff State::build_diff(evmc_revision rev) const Account& State::insert(const address& addr, Account account) { - const auto r = m_modified.insert({addr, std::move(account)}); - assert(r.second); - return r.first->second; + assert(!account.nonexistent); // No need to insert nonexistent accounts. + const auto [it, inserted] = m_modified.try_emplace(addr, std::move(account)); + if (!inserted) + { + assert(it->second.nonexistent); // Overwrite only nonexistent accounts. + it->second = std::move(account); + } + return it->second; } Account* State::find(const address& addr) noexcept { - // TODO: Avoid double lookup (find+insert) and not cached initial state lookup for non-existent - // accounts. If we want to cache non-existent account we need a proper flag for it. + // TODO: Avoid the double lookup (find+insert). Nonexistent accounts are still re-queried from + // the initial state on every call; they could be cached as nonexistent nodes. if (const auto it = m_modified.find(addr); it != m_modified.end()) - return &it->second; + return it->second.nonexistent ? nullptr : &it->second; if (const auto cacc = m_initial.get_account(addr); cacc) return &insert(addr, {.nonce = cacc->nonce, .balance = cacc->balance, @@ -291,8 +322,8 @@ Account& State::touch(const address& addr) auto& acc = get_or_insert(addr, {.erase_if_empty = true}); if (!acc.erase_if_empty && acc.is_empty()) { + journal_account_flags(addr, acc); acc.erase_if_empty = true; - m_journal.emplace_back(JournalTouched{addr}); } return acc; } @@ -315,16 +346,14 @@ void State::journal_balance_change(const address& addr, const intx::uint256& pre m_journal.emplace_back(JournalBalanceChange{{addr}, prev_balance}); } -void State::journal_storage_change( - const address& addr, const bytes32& key, const StorageValue& value) +void State::journal_storage_change(StorageValue& slot) { - m_journal.emplace_back(JournalStorageChange{{addr}, key, value.current, value.access_status}); + m_journal.emplace_back(JournalStorageChange{&slot, slot.current, slot.access_status}); } -void State::journal_transient_storage_change( - const address& addr, const bytes32& key, const bytes32& value) +void State::journal_transient_storage_change(bytes32& slot) { - m_journal.emplace_back(JournalTransientStorageChange{{addr}, key, value}); + m_journal.emplace_back(JournalTransientStorageChange{&slot, slot}); } void State::journal_bump_nonce(const address& addr) @@ -332,19 +361,21 @@ void State::journal_bump_nonce(const address& addr) m_journal.emplace_back(JournalNonceBump{addr}); } -void State::journal_create(const address& addr, bool existed) +void State::journal_create(const address& addr) { - m_journal.emplace_back(JournalCreate{{addr}, existed}); + m_journal.emplace_back(JournalCreate{{addr}}); } -void State::journal_destruct(const address& addr) +void State::journal_new_account(const address& addr) { - m_journal.emplace_back(JournalDestruct{addr}); + // Revert restores the account to "nonexistent". The other flags are irrelevant/default. + m_journal.emplace_back(JournalAccountFlags{{addr}, EVMC_ACCESS_COLD, true, false, false}); } -void State::journal_access_account(const address& addr) +void State::journal_account_flags(const address& addr, const Account& acc) { - m_journal.emplace_back(JournalAccessAccount{addr}); + m_journal.emplace_back(JournalAccountFlags{ + {addr}, acc.access_status, acc.nonexistent, acc.destructed, acc.erase_if_empty}); } void State::rollback(size_t checkpoint) @@ -358,47 +389,33 @@ void State::rollback(size_t checkpoint) { get(e.addr).nonce -= 1; } - else if constexpr (std::is_same_v) - { - get(e.addr).erase_if_empty = false; - } - else if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) { - get(e.addr).destructed = false; - } - else if constexpr (std::is_same_v) - { - get(e.addr).access_status = EVMC_ACCESS_COLD; + auto& a = get(e.addr); + a.access_status = e.access_status; + a.nonexistent = e.nonexistent; + a.destructed = e.destructed; + a.erase_if_empty = e.erase_if_empty; + // TODO: On restoring nonexistent (un-created create) the node keeps its + // code/storage/transient buffers until tx end; could clear them here. } else if constexpr (std::is_same_v) { - if (e.existed) - { - // This account is not always "touched". TODO: Why? - auto& a = get(e.addr); - a.nonce = 0; - a.code_hash = Account::EMPTY_CODE_HASH; - a.code.clear(); - } - else - { - // TODO: Before Spurious Dragon we don't clear empty accounts ("erasable") - // so we need to delete them here explicitly. - // This should be changed by tuning "erasable" flag - // and clear in all revisions. - m_modified.erase(e.addr); - } + // Revert a create over a pre-existing account. + // TODO: Why this account is not always "touched"? + auto& a = get(e.addr); + a.nonce = 0; + a.code_hash = Account::EMPTY_CODE_HASH; + a.code.clear(); } else if constexpr (std::is_same_v) { - auto& s = get(e.addr).storage.find(e.key)->second; - s.current = e.prev_value; - s.access_status = e.prev_access_status; + e.slot->current = e.prev_value; + e.slot->access_status = e.prev_access_status; } else if constexpr (std::is_same_v) { - auto& s = get(e.addr).transient_storage.find(e.key)->second; - s = e.prev_value; + *e.slot = e.prev_value; } else if constexpr (std::is_same_v) { @@ -421,11 +438,14 @@ std::variant validate_transaction( const StateView& state_view, const BlockInfo& block, const Transaction& tx, evmc_revision rev, int64_t block_gas_left, int64_t blob_gas_left) noexcept { + if (tx.chain_id_protected() && tx.chain_id != block.chain_id) + return make_error_code(INVALID_CHAIN_ID); + switch (tx.type) // Validate "special" transaction types. { case Transaction::Type::blob: if (rev < EVMC_CANCUN) - return make_error_code(TX_TYPE_NOT_SUPPORTED); + return make_error_code(TYPE_NOT_SUPPORTED); if (!tx.to.has_value()) return make_error_code(CREATE_BLOB_TX); if (tx.blob_hashes.empty()) @@ -435,7 +455,7 @@ std::variant validate_transaction( assert(block.blob_base_fee.has_value()); if (tx.max_blob_gas_price < *block.blob_base_fee) - return make_error_code(BLOB_FEE_CAP_LESS_THAN_BLOCKS); + return make_error_code(INSUFFICIENT_MAX_FEE_PER_BLOB_GAS); if (std::ranges::any_of(tx.blob_hashes, [](const auto& h) { return h.bytes[0] != 0x01; })) return make_error_code(INVALID_BLOB_HASH_VERSION); @@ -445,7 +465,7 @@ std::variant validate_transaction( case Transaction::Type::set_code: if (rev < EVMC_PRAGUE) - return make_error_code(TX_TYPE_NOT_SUPPORTED); + return make_error_code(TYPE_NOT_SUPPORTED); if (!tx.to.has_value()) return make_error_code(CREATE_SET_CODE_TX); if (tx.authorization_list.empty()) @@ -461,15 +481,15 @@ std::variant validate_transaction( case Transaction::Type::blob: case Transaction::Type::eip1559: if (rev < EVMC_LONDON) - return make_error_code(TX_TYPE_NOT_SUPPORTED); + return make_error_code(TYPE_NOT_SUPPORTED); if (tx.max_priority_gas_price > tx.max_gas_price) - return make_error_code(TIP_GT_FEE_CAP); // Priority gas price is too high. + return make_error_code(PRIORITY_GREATER_THAN_MAX_FEE_PER_GAS); [[fallthrough]]; case Transaction::Type::access_list: if (rev < EVMC_BERLIN) - return make_error_code(TX_TYPE_NOT_SUPPORTED); + return make_error_code(TYPE_NOT_SUPPORTED); [[fallthrough]]; case Transaction::Type::legacy:; @@ -478,13 +498,13 @@ std::variant validate_transaction( assert(tx.max_priority_gas_price <= tx.max_gas_price); if (rev >= EVMC_OSAKA && tx.gas_limit > MAX_TX_GAS_LIMIT) - return make_error_code(MAX_GAS_LIMIT_EXCEEDED); + return make_error_code(GAS_LIMIT_EXCEEDS_MAXIMUM); if (tx.gas_limit > block_gas_left) - return make_error_code(GAS_LIMIT_REACHED); + return make_error_code(GAS_ALLOWANCE_EXCEEDED); if (tx.max_gas_price < block.base_fee) - return make_error_code(FEE_CAP_LESS_THAN_BLOCKS); + return make_error_code(INSUFFICIENT_MAX_FEE_PER_GAS); // We need some information about the sender so lookup the account in the state. // TODO: During transaction execution this account will be also needed, so we may pass it along. @@ -495,8 +515,8 @@ std::variant validate_transaction( !is_code_delegated(state_view.get_account_code(tx.sender))) return make_error_code(SENDER_NOT_EOA); // Origin must not be a contract (EIP-3607). - if (sender_acc.nonce == Account::NonceMax) // Nonce value limit (EIP-2681). - return make_error_code(NONCE_HAS_MAX_VALUE); + if (sender_acc.nonce == MAX_NONCE) // Nonce value limit (EIP-2681). + return make_error_code(NONCE_IS_MAX); if (sender_acc.nonce < tx.nonce) return make_error_code(NONCE_TOO_HIGH); @@ -504,9 +524,11 @@ std::variant validate_transaction( if (sender_acc.nonce > tx.nonce) return make_error_code(NONCE_TOO_LOW); - // initcode size is limited by EIP-3860. - if (rev >= EVMC_SHANGHAI && !tx.to.has_value() && tx.data.size() > MAX_INITCODE_SIZE) - return make_error_code(INIT_CODE_SIZE_LIMIT_EXCEEDED); + // Initcode size is limited by EIP-3860, raised for Amsterdam by EIP-7954. + const size_t max_initcode_size = + rev >= EVMC_AMSTERDAM ? MAX_INITCODE_SIZE_AMSTERDAM : MAX_INITCODE_SIZE; + if (rev >= EVMC_SHANGHAI && !tx.to.has_value() && tx.data.size() > max_initcode_size) + return make_error_code(INITCODE_SIZE_EXCEEDED); // Compute and check if sender has enough balance for the theoretical maximum transaction cost. // Note this is different from tx_max_cost computed with effective gas price later. @@ -521,7 +543,7 @@ std::variant validate_transaction( max_total_fee += total_blob_gas * tx.max_blob_gas_price; } if (sender_acc.balance < max_total_fee) - return make_error_code(INSUFFICIENT_FUNDS); + return make_error_code(INSUFFICIENT_ACCOUNT_FUNDS); const auto [intrinsic_cost, min_cost] = compute_tx_intrinsic_cost(rev, tx); if (tx.gas_limit < std::max(intrinsic_cost, min_cost)) @@ -565,8 +587,8 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc State state{state_view}; auto& sender_acc = state.get_or_insert(tx.sender); - assert(sender_acc.nonce < Account::NonceMax); // Required for valid tx. - ++sender_acc.nonce; // Bump sender nonce. + assert(sender_acc.nonce < MAX_NONCE); // Required for valid tx. + ++sender_acc.nonce; // Bump sender nonce. const auto delegation_refund = process_authorization_list(state, tx.chain_id, tx.authorization_list); @@ -596,12 +618,15 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc Host host{rev, vm, state, block, block_hashes, tx}; - sender_acc.access_status = EVMC_ACCESS_WARM; // Tx sender is always warm. - if (tx.to.has_value()) - host.access_account(*tx.to); + auto message = build_message(tx, tx_props.execution_gas_limit); + + sender_acc.access_status = EVMC_ACCESS_WARM; // Sender is always warm. + host.access_account(message.recipient); // Recipient (incl. create address) is always warm. for (const auto& [a, storage_keys] : tx.access_list) { host.access_account(a); + if (is_precompile(rev, a)) // Precompile storage is never accessed. + continue; for (const auto& key : storage_keys) state.get_storage(a, key).access_status = EVMC_ACCESS_WARM; } @@ -611,7 +636,6 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc if (rev >= EVMC_SHANGHAI) host.access_account(block.coinbase); - auto message = build_message(tx, tx_props.execution_gas_limit); if (tx.to.has_value()) { if (const auto delegate = get_delegate_address(host, *tx.to)) @@ -624,23 +648,27 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc const auto result = host.call(message); - auto gas_used = tx.gas_limit - result.gas_left; + const auto gas_used_b4_refund = tx.gas_limit - result.gas_left; const auto max_refund_quotient = rev >= EVMC_LONDON ? 5 : 2; - const auto refund_limit = gas_used / max_refund_quotient; + const auto refund_limit = gas_used_b4_refund / max_refund_quotient; const auto refund = std::min(delegation_refund + result.gas_refund, refund_limit); - gas_used -= refund; + auto gas_used = gas_used_b4_refund - refund; assert(gas_used > 0); - // EIP-7623: The gas used by the transaction must be at least the min_gas_cost. + // The gas used by the transaction must be at least the min_gas_cost (EIP-7623). gas_used = std::max(gas_used, tx_props.min_gas_cost); + // For block gas accounting, compute the gas refund capped by the min gas cost (EIP-7778). + const auto block_gas_used = std::max(gas_used_b4_refund, tx_props.min_gas_cost); + const auto gas_refund = block_gas_used - gas_used; + sender_acc.balance += tx_max_cost - gas_used * effective_gas_price; state.touch(block.coinbase).balance += gas_used * priority_gas_price; // Cumulative gas used is unknown in this scope. - TransactionReceipt receipt{ - tx.type, result.status_code, gas_used, {}, host.take_logs(), {}, state.build_diff(rev)}; + TransactionReceipt receipt{tx.type, result.status_code, gas_used, gas_refund, {}, + host.take_logs(), {}, state.build_diff(rev)}; // Cannot put it into constructor call because logs are std::moved from host instance. receipt.logs_bloom_filter = compute_bloom_filter(receipt.logs); diff --git a/evmone/test/state/state.hpp b/evmone/test/state/state.hpp index 6d916a1..7240704 100644 --- a/evmone/test/state/state.hpp +++ b/evmone/test/state/state.hpp @@ -29,19 +29,24 @@ class State intx::uint256 prev_balance; }; - struct JournalTouched : JournalBase - {}; + struct JournalAccountFlags : JournalBase + { + evmc_access_status access_status; + bool nonexistent; + bool destructed; + bool erase_if_empty; + }; - struct JournalStorageChange : JournalBase + struct JournalStorageChange { - bytes32 key; + StorageValue* slot = nullptr; ///< Storage slot in a node-based container (stable refs). bytes32 prev_value; evmc_access_status prev_access_status; }; - struct JournalTransientStorageChange : JournalBase + struct JournalTransientStorageChange { - bytes32 key; + bytes32* slot = nullptr; ///< T-storage slot in a node-based container (stable refs). bytes32 prev_value; }; @@ -49,19 +54,10 @@ class State {}; struct JournalCreate : JournalBase - { - bool existed; - }; - - struct JournalDestruct : JournalBase - {}; - - struct JournalAccessAccount : JournalBase {}; - using JournalEntry = - std::variant; + using JournalEntry = std::variant; /// The read-only view of the initial (cold) state. const StateView& m_initial; @@ -113,18 +109,19 @@ class State void journal_balance_change(const address& addr, const intx::uint256& prev_balance); - void journal_storage_change(const address& addr, const bytes32& key, const StorageValue& value); + void journal_storage_change(StorageValue& slot); - void journal_transient_storage_change( - const address& addr, const bytes32& key, const bytes32& value); + void journal_transient_storage_change(bytes32& slot); void journal_bump_nonce(const address& addr); - void journal_create(const address& addr, bool existed); + /// Journals a create over a pre-existing account; revert resets its nonce and code. + void journal_create(const address& addr); - void journal_destruct(const address& addr); + /// Journals a new-account creation; revert un-creates it (restores "does not exist"). + void journal_new_account(const address& addr); - void journal_access_account(const address& addr); + void journal_account_flags(const address& addr, const Account& acc); /// @} }; diff --git a/evmone/test/state/system_contracts.cpp b/evmone/test/state/system_contracts.cpp index 80b137e..7a5cd43 100644 --- a/evmone/test/state/system_contracts.cpp +++ b/evmone/test/state/system_contracts.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "system_contracts.hpp" +#include "errors.hpp" #include "host.hpp" #include "state_view.hpp" @@ -10,6 +11,15 @@ namespace evmone::state { namespace { +/// Convert an address to a 32-byte value, left-padded with zeros. +/// TODO: Deduplicate with to_bytes32 in test/utils/utils.hpp. +bytes32 to_bytes32(const address& addr) noexcept +{ + bytes32 res{}; + std::copy_n(addr.bytes, sizeof(addr), &res.bytes[sizeof(res) - sizeof(addr)]); + return res; +} + /// Information about a registered "storage" system contract. They are executed at the block start /// to store additional information in the State. struct StorageSystemContract @@ -106,7 +116,7 @@ StateDiff system_call_block_start(const StateView& state_view, const BlockInfo& return state.build_diff(rev); } -std::optional system_call_block_end(const StateView& state_view, +std::variant system_call_block_end(const StateView& state_view, const BlockInfo& block, const BlockHashes& block_hashes, evmc_revision rev, evmc::VM& vm) { State state{state_view}; @@ -119,13 +129,30 @@ std::optional system_call_block_end(const StateView& state_view, // Fail if the target account doesn't exist. This is by EIP-7002 and EIP-7251 spec. const auto code = state_view.get_account_code(addr); if (code.empty()) - return std::nullopt; + return make_error_code(SYSTEM_CONTRACT_EMPTY); const auto res = execute_system_call(state, block, block_hashes, rev, vm, addr, code, {}); if (res.status_code != EVMC_SUCCESS) - return std::nullopt; + return make_error_code(SYSTEM_CONTRACT_CALL_FAILED); requests.emplace_back(request_type, bytes_view{res.output_data, res.output_size}); } return RequestsResult{state.build_diff(rev), requests}; } + +void emit_transfer_log( + std::vector& logs, const address& sender, const address& recipient, const uint256& amount) +{ + /// The ETH transfer log topic (EIP-7708): keccak256("Transfer(address,address,uint256)") + constexpr auto TRANSFER_EVENT_TOPIC = + 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef_bytes32; + + if (amount == 0) // No log for 0 value transfers. + return; + + if (sender == recipient) // No log for self transfers (balance unchanged). + return; + + logs.push_back({SYSTEM_ADDRESS, bytes{intx::be::store(amount)}, + {TRANSFER_EVENT_TOPIC, to_bytes32(sender), to_bytes32(recipient)}}); +} } // namespace evmone::state diff --git a/evmone/test/state/system_contracts.hpp b/evmone/test/state/system_contracts.hpp index 4a29bb0..aaccaaf 100644 --- a/evmone/test/state/system_contracts.hpp +++ b/evmone/test/state/system_contracts.hpp @@ -4,7 +4,10 @@ #pragma once #include "requests.hpp" +#include "transaction.hpp" #include +#include +#include namespace evmone::state { @@ -49,7 +52,13 @@ struct RequestsResult /// /// Executes code of pre-defined accounts via pseudo-transaction from the system sender (0xff...fe). /// The sender's nonce is not increased. -/// @return The collected requests and state diff or std::nullopt if the execution has failed. -[[nodiscard]] std::optional system_call_block_end(const StateView& state_view, - const BlockInfo& block, const BlockHashes& block_hashes, evmc_revision rev, evmc::VM& vm); +/// @return The collected requests and state diff, or the error code identifying why the block +/// requests collection failed. +[[nodiscard]] std::variant system_call_block_end( + const StateView& state_view, const BlockInfo& block, const BlockHashes& block_hashes, + evmc_revision rev, evmc::VM& vm); + +/// Emit an ETH transfer log (LOG3) from SYSTEM_ADDRESS for a value transfer (EIP-7708). +void emit_transfer_log( + std::vector& logs, const address& sender, const address& recipient, const uint256& amount); } // namespace evmone::state diff --git a/evmone/test/state/transaction.cpp b/evmone/test/state/transaction.cpp new file mode 100644 index 0000000..31d37eb --- /dev/null +++ b/evmone/test/state/transaction.cpp @@ -0,0 +1,176 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2026 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "transaction.hpp" +// TODO: The RLP encoder belongs in the state library, see the note in authorization.cpp. +#include "../utils/rlp.hpp" +#include "../utils/stdx/utility.hpp" +#include "hash_utils.hpp" +#include "rlp_decode.hpp" +#include + +namespace evmone::state +{ +bool decode(bytes_view& from, Authorization& to) noexcept +{ + bytes_view payload; + if (!rlp::take_list_payload(from, payload)) + return false; + + // An out-of-range y_parity only invalidates the authorization, so anything fitting its uint8_t + // is accepted here; recover_authority rejects the values above 1. + return rlp::decode_multi(payload, to.chain_id, to.addr, to.nonce, to.y_parity, to.r, to.s) && + payload.empty(); +} + +namespace +{ +[[nodiscard]] bool decode_transaction_body(bytes_view& from, Transaction& to) noexcept +{ + if (from.empty()) [[unlikely]] + return false; + + bytes_view body; + if (from[0] >= rlp::SHORT_LIST_BASE) // Legacy: the item is an RLP list. + { + to.type = Transaction::Type::legacy; + if (!rlp::take_list_payload(from, body)) + return false; + } + else // Typed (EIP-2718): a raw type byte followed by the RLP list. + { + // The type is a single byte in [0x00, 0x7f], not an RLP item; reading it directly rejects + // a non-canonical RLP-string form such as 0x81 0x02. + const auto t = from[0]; + from.remove_prefix(1); + + if (t == stdx::to_underlying(Transaction::Type::legacy) || + t > stdx::to_underlying(Transaction::Type::set_code)) [[unlikely]] + return false; + + to.type = static_cast(t); + + if (!rlp::take_list_payload(from, body) || !rlp::decode(body, to.chain_id)) + return false; + } + + if (!rlp::decode(body, to.nonce)) + return false; + + // EIP-1559 and the later types (blob, set-code) carry a separate priority fee per gas; + // earlier types reuse the single gas price for both caps (set below). + const auto has_priority_gas_price = to.type >= Transaction::Type::eip1559; + if (has_priority_gas_price) + { + if (!rlp::decode(body, to.max_priority_gas_price)) + return false; + } + + if (!rlp::decode(body, to.max_gas_price)) + return false; + + if (!has_priority_gas_price) + to.max_priority_gas_price = to.max_gas_price; + + uint64_t gas_limit{}; + if (!rlp::decode(body, gas_limit) || + gas_limit > static_cast(std::numeric_limits::max())) + return false; // gas_limit must fit the signed Transaction::gas_limit. + to.gas_limit = static_cast(gas_limit); + + // Empty "to" (0x80) is a CREATE transaction; otherwise a 20-byte recipient. The blob and + // set-code types forbid the CREATE form, but that is enforced later in validate_transaction. + if (!body.empty() && body[0] == rlp::SHORT_STRING_BASE) // Empty string. + { + to.to = std::nullopt; + body.remove_prefix(1); + } + else + { + address recipient; + if (!rlp::decode(body, recipient)) // Requires exactly 20 bytes. + return false; + to.to = recipient; + } + + if (!rlp::decode_multi(body, to.value, to.data)) + return false; + + if (to.type == Transaction::Type::legacy) + { + // Legacy v carries the recovery id and, since EIP-155, the chain id. It is kept verbatim, + // the way rlp_encode() writes it, and the chain id derived alongside. Requiring the whole + // v to fit uint64_t bounds the chain id to 2**63 - 18, the same limit the JSON transaction + // loader has. + if (!rlp::decode(body, to.v)) + return false; + + if (to.v >= 35) // EIP-155: v = 35 + 2 * chain_id + y_parity. + to.chain_id = (to.v - 35) / 2; + else if (to.v != 27 && to.v != 28) // Pre-EIP-155: bound to no chain, chain_id unused. + return false; + } + else + { + if (!rlp::decode(body, to.access_list)) + return false; + if (to.type == Transaction::Type::blob) + { + if (!rlp::decode_multi(body, to.max_blob_gas_price, to.blob_hashes)) + return false; + } + else if (to.type == Transaction::Type::set_code) + { + if (!rlp::decode(body, to.authorization_list)) + return false; + } + if (!rlp::decode(body, to.v) || to.v > 1) + return false; + } + + return rlp::decode_multi(body, to.r, to.s) && body.empty(); +} +} // namespace + +std::optional decode_transaction(bytes_view data) noexcept +{ + Transaction tx; + if (!decode_transaction_body(data, tx) || !data.empty()) + return std::nullopt; // Malformed transaction or trailing data. + return tx; +} + +std::optional
recover_sender(const Transaction& tx, bytes_view txbytes) noexcept +{ + // The signing preimage is the transaction's encoding without the trailing (v, r, s). + const auto typed = tx.type != Transaction::Type::legacy; + auto envelope = txbytes.substr(typed ? 1 : 0); // Skip the EIP-2718 type byte. + bytes_view payload; + [[maybe_unused]] const auto is_list = rlp::take_list_payload(envelope, payload); + assert(is_list); // tx has been decoded from txbytes, so its list header is valid. + + // Find the length of the encoded signature to find the preimage length. + // The decoder accepts only canonical integers, so re-encoding (v, r, s) gives their wire sizes. + const auto signature_size = + rlp::encode(tx.v).size() + rlp::encode(tx.r).size() + rlp::encode(tx.s).size(); + assert(signature_size <= payload.size()); + auto preimage = bytes{payload.substr(0, payload.size() - signature_size)}; + + // Protected legacy transactions sign chain_id by appending (chain_id, 0, 0) (EIP-155). + // TODO: Allocate bytes only in this case; use views for typed transactions. + if (!typed && tx.chain_id_protected()) + preimage += rlp::encode(tx.chain_id) + rlp::encode(uint64_t{}) + rlp::encode(uint64_t{}); + + // A typed v is {0, 1}. A legacy v is 27 + y_parity, or 35 + 2 * chain_id + y_parity (EIP-155): + // both bases are odd, so an even v means y_parity 1. + const auto y_parity = typed ? tx.v != 0 : tx.v % 2 == 0; + + const auto h = keccak256((typed ? bytes{stdx::to_underlying(tx.type)} : bytes{}) + + rlp::internal::wrap_list(preimage)); + const auto r_bytes = intx::be::store(tx.r); + const auto s_bytes = intx::be::store(tx.s); + return evmmax::secp256k1::ecrecover( + h.bytes, r_bytes.bytes, s_bytes.bytes, y_parity, evmmax::secp256k1::RecoveryMode::strict); +} +} // namespace evmone::state diff --git a/evmone/test/state/transaction.hpp b/evmone/test/state/transaction.hpp index e7f947a..70879ae 100644 --- a/evmone/test/state/transaction.hpp +++ b/evmone/test/state/transaction.hpp @@ -4,6 +4,7 @@ #pragma once +#include "authorization.hpp" #include "blob_params.hpp" #include "bloom_filter.hpp" #include "state_diff.hpp" @@ -18,19 +19,10 @@ constexpr auto MAX_TX_GAS_LIMIT = 0x1000000; // 2**24 using AccessList = std::vector>>; -struct Authorization -{ - intx::uint256 chain_id; - address addr; - uint64_t nonce = 0; - /// Signer is empty if it cannot be ecrecovered from r, s, v. - std::optional
signer; - intx::uint256 r; - intx::uint256 s; - intx::uint256 v; -}; - -using AuthorizationList = std::vector; +/// Decodes an EIP-7702 authorization. +/// +/// Declared here (not file-local) so the generic rlp::decode(std::vector&) finds it by ADL. +[[nodiscard]] bool decode(bytes_view& from, Authorization& to) noexcept; struct Transaction { @@ -63,6 +55,12 @@ struct Transaction /// Returns amount of blob gas used by this transaction [[nodiscard]] uint64_t blob_gas_used() const { return GAS_PER_BLOB * blob_hashes.size(); } + /// Whether the transaction specifies expected chain id. Always true for typed transactions. + [[nodiscard]] bool chain_id_protected() const noexcept + { + return type != Type::legacy || v >= 35; + } + Type type = Type::legacy; bytes data; int64_t gas_limit = 0; @@ -78,10 +76,28 @@ struct Transaction uint64_t nonce = 0; intx::uint256 r; intx::uint256 s; - uint8_t v = 0; + + /// The verbatim v value of the signature. + /// It encodes y_parity and for legacy transactions chain id. + uint64_t v = 0; AuthorizationList authorization_list; }; +/// Decodes a transaction from its complete serialization @p data. +/// +/// Handles the legacy RLP list and the EIP-2718 typed envelope (type byte followed by an RLP list). +[[nodiscard]] std::optional decode_transaction(bytes_view data) noexcept; + +/// Recovers the sender (the signer) of the transaction @p tx decoded from @p txbytes, +/// or std::nullopt if the signature is invalid. +/// +/// The serialization is needed as well because the signing preimage is a slice of it; @p tx must +/// be what decode_transaction(@p txbytes) returned. +/// +/// The recovery is strict at every revision: r, s in [1, secp256k1n) and low s (EIP-2). +[[nodiscard]] std::optional
recover_sender( + const Transaction& tx, bytes_view txbytes) noexcept; + /// Transaction properties computed during the validation needed for the execution. struct TransactionProperties { @@ -113,9 +129,13 @@ struct TransactionReceipt Transaction::Type type = Transaction::Type::legacy; evmc_status_code status = EVMC_INTERNAL_ERROR; - /// Amount of gas used by this transaction. + /// Amount of gas used by this transaction (after refund, with the min gas applied). int64_t gas_used = 0; + /// Amount of gas refund applied to gas_used (capped by the min gas cost of EIP-7623). + /// Effectively, the difference between "block" and "user" gas. + int64_t gas_refund = 0; + /// Amount of gas used by this and previous transactions in the block. int64_t cumulative_gas_used = 0; std::vector logs; diff --git a/evmone/test/statetest/statetest_runner.cpp b/evmone/test/statetest/statetest_runner.cpp index 6c607c1..8d2ea43 100644 --- a/evmone/test/statetest/statetest_runner.cpp +++ b/evmone/test/statetest/statetest_runner.cpp @@ -3,8 +3,10 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include #include +#include #include namespace evmone::test @@ -24,12 +26,40 @@ void run_state_test(const StateTransitionTest& test, evmc::VM& vm, bool trace_su // continue; const auto& expected = cases[case_index]; - const auto tx = test.multi_tx.get(expected.indexes); auto state = test.pre_state; const auto blob_params = get_blob_params(rev, test.blob_schedule); - const auto res = transition(state, block, test.block_hashes, tx, rev, vm, - block.gas_limit, static_cast(state::max_blob_gas_per_block(blob_params))); + std::optional tx; + std::error_code error; + if (expected.txbytes.has_value()) + { + tx = state::decode_transaction(*expected.txbytes); + if (!tx.has_value()) + { + error = make_error_code(state::INVALID_ENCODING); + } + else + { + // Decoding is the inverse of encoding: what decoded must encode back exactly. + EXPECT_EQ(rlp::encode(*tx), *expected.txbytes); + + // Recover the signer, as a node does, instead of taking it from JSON. + const auto sender = state::recover_sender(*tx, *expected.txbytes); + if (sender.has_value()) + tx->sender = *sender; + else + error = make_error_code(state::INVALID_SIGNATURE); + } + } + else + { + tx = test.multi_tx.get(expected.indexes); + } + + const auto res = + error ? error : + transition(state, block, test.block_hashes, *tx, rev, vm, block.gas_limit, + static_cast(state::max_blob_gas_per_block(blob_params))); if (holds_alternative(res)) { @@ -55,10 +85,18 @@ void run_state_test(const StateTransitionTest& test, evmc::VM& vm, bool trace_su std::clog << R"("stateRoot":"0x)" << hex(state_root) << "\"}\n"; } - if (expected.exception) + if (!expected.exception.empty()) { ASSERT_FALSE(holds_alternative(res)) << "unexpected valid transaction"; + + // The transaction must be rejected for the reason the fixture states, not merely + // rejected: a wrong reason is a wrong implementation of the rule being tested. + const auto& reason = get(res); + EXPECT_TRUE(is_expected_tx_exception(reason, expected.exception)) + << "transaction rejected as \"" << reason.message() << "\", expected " + << expected.exception; + EXPECT_EQ(logs_hash(std::vector()), expected.logs_hash); } else diff --git a/evmone/test/t8n/CMakeLists.txt b/evmone/test/t8n/CMakeLists.txt deleted file mode 100644 index d78c80e..0000000 --- a/evmone/test/t8n/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -# evmone: Fast Ethereum Virtual Machine implementation -# Copyright 2022 The evmone Authors. -# SPDX-License-Identifier: Apache-2.0 - -add_executable(evmone-t8n) -target_link_libraries(evmone-t8n PRIVATE evmone::state evmone::testutils evmc::evmc evmone evmone-buildinfo) -target_sources(evmone-t8n PRIVATE t8n.cpp) diff --git a/evmone/test/t8n/t8n.cpp b/evmone/test/t8n/t8n.cpp deleted file mode 100644 index 4d01c4c..0000000 --- a/evmone/test/t8n/t8n.cpp +++ /dev/null @@ -1,326 +0,0 @@ -// evmone: Fast Ethereum Virtual Machine implementation -// Copyright 2023 The evmone Authors. -// SPDX-License-Identifier: Apache-2.0 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; -namespace json = nlohmann; -using namespace evmone; -using namespace evmone::test; -using namespace std::literals; - -int main(int argc, const char* argv[]) -{ - evmc_revision rev = {}; - fs::path alloc_file; - fs::path env_file; - fs::path txs_file; - fs::path blob_params_file; - fs::path output_dir; - fs::path output_result_file; - fs::path output_alloc_file; - fs::path output_body_file; - std::optional block_reward; - uint64_t chain_id = 0; - bool trace = false; - fs::path opcode_count_filename; - std::string opcode_count_file; - bool pre_state_only = false; - - try - { - for (int i = 0; i < argc; ++i) - { - const std::string_view arg{argv[i]}; - - if (arg == "-v" || arg == "--version") - { - std::cout << "evmone-t8n " EVMONE_VERSION "\n"; - return 0; - } - if (arg == "--state.fork" && ++i < argc) - rev = to_rev(argv[i]); - else if (arg == "--input.alloc" && ++i < argc) - alloc_file = argv[i]; - else if (arg == "--input.env" && ++i < argc) - env_file = argv[i]; - else if (arg == "--input.txs" && ++i < argc) - txs_file = argv[i]; - else if (arg == "--input.blobParams" && ++i < argc) - blob_params_file = argv[i]; - else if (arg == "--output.basedir" && ++i < argc) - { - output_dir = argv[i]; - fs::create_directories(output_dir); - } - else if (arg == "--output.result" && ++i < argc) - output_result_file = argv[i]; - else if (arg == "--output.alloc" && ++i < argc) - output_alloc_file = argv[i]; - else if (arg == "--state.chainid" && ++i < argc) - chain_id = intx::from_string(argv[i]); - else if (arg == "--output.body" && ++i < argc) - output_body_file = argv[i]; - else if (arg == "--trace") - trace = true; - else if (arg == "--opcode.count" && ++i < argc) - opcode_count_filename = argv[i]; - else if (arg == "--state.reward" && ++i < argc) - { - if (argv[i] == "-1"sv) // Hack to compute the root hash of the pre-state. - pre_state_only = true; - else - block_reward = intx::from_string(argv[i]); - } - } - - state::BlockInfo block; - TestBlockHashes block_hashes; - TestState state; - - state::BlobParams blob_params; - - if (!blob_params_file.empty()) - { - const auto j = json::json::parse(std::ifstream{blob_params_file}, nullptr, false); - blob_params = from_json(j); - } - else - { - // Use hardcoded blob schedule if no blob config file is provided. - blob_params = get_blob_params(rev); - } - - if (!alloc_file.empty()) - { - const auto j = json::json::parse(std::ifstream{alloc_file}, nullptr, false); - state = from_json(j); - validate_state(state, rev); - } - if (!env_file.empty()) - { - const auto j = json::json::parse(std::ifstream{env_file}); - block = from_json_with_rev(j, rev, blob_params); - block_hashes = from_json(j); - } - - json::json j_result; - - // Difficulty was received from upstream. No need to calc - // TODO: Check if it's needed by the blockchain test. If not remove if statement true branch - if (block.difficulty != 0) - j_result["currentDifficulty"] = hex0x(block.difficulty); - else - { - const auto current_difficulty = state::calculate_difficulty(block.parent_difficulty, - block.parent_ommers_hash != EmptyListHash, block.parent_timestamp, block.timestamp, - block.number, rev); - - j_result["currentDifficulty"] = hex0x(current_difficulty); - block.difficulty = current_difficulty; - - if (rev < EVMC_PARIS) // Override prev_randao with difficulty pre-Merge - block.prev_randao = intx::be::store(intx::uint256{current_difficulty}); - } - - if (rev >= EVMC_LONDON) - j_result["currentBaseFee"] = hex0x(block.base_fee); - - int64_t cumulative_gas_used = 0; - auto blob_gas_left = static_cast(state::max_blob_gas_per_block(blob_params)); - std::vector transactions; - std::vector receipts; - int64_t block_gas_left = block.gas_limit; - std::vector requests; - - // Parse and execute transactions - if (!txs_file.empty()) - { - const auto j_txs = json::json::parse(std::ifstream{txs_file}); - - evmc::VM vm{evmc_create_evmone()}; - - if (trace) - vm.set_option("trace", "1"); - if (!opcode_count_filename.empty()) - { - opcode_count_file = (output_dir / opcode_count_filename).string(); - vm.set_option("opcode.count", opcode_count_file.c_str()); - } - - std::vector txs_logs; - - if (j_txs.is_array()) - { - j_result["receipts"] = json::json::array(); - j_result["rejected"] = json::json::array(); - - if (!pre_state_only) - test::system_call_block_start(state, block, block_hashes, rev, vm); - - for (size_t i = 0; i < j_txs.size(); ++i) - { - auto tx = test::from_json(j_txs[i]); - tx.chain_id = chain_id; - - const auto computed_tx_hash = keccak256(rlp::encode(tx)); - const auto computed_tx_hash_str = hex0x(computed_tx_hash); - - if (j_txs[i].contains("hash")) - { - const auto loaded_tx_hash_opt = - evmc::from_hex(j_txs[i]["hash"].get()); - - if (loaded_tx_hash_opt != computed_tx_hash) - throw std::logic_error("transaction hash mismatched: computed " + - computed_tx_hash_str + ", expected " + - hex0x(loaded_tx_hash_opt.value())); - } - - std::ofstream trace_file_output; - const auto orig_clog_buf = std::clog.rdbuf(); - if (trace) - { - const auto output_filename = - output_dir / - ("trace-" + std::to_string(i) + "-" + computed_tx_hash_str + ".jsonl"); - - // `trace` flag enables trace logging to std::clog. - // Redirect std::clog to the output file. - trace_file_output.open(output_filename); - std::clog.rdbuf(trace_file_output.rdbuf()); - } - - auto res = test::transition( - state, block, block_hashes, tx, rev, vm, block_gas_left, blob_gas_left); - - if (holds_alternative(res)) - { - const auto ec = std::get(res); - json::json j_rejected_tx; - j_rejected_tx["hash"] = computed_tx_hash_str; - j_rejected_tx["index"] = i; - j_rejected_tx["error"] = ec.message(); - j_result["rejected"].push_back(j_rejected_tx); - } - else - { - auto& receipt = get(res); - - const auto& tx_logs = receipt.logs; - - txs_logs.insert(txs_logs.end(), tx_logs.begin(), tx_logs.end()); - auto& j_receipt = j_result["receipts"][j_result["receipts"].size()]; - - j_receipt["transactionHash"] = computed_tx_hash_str; - j_receipt["gasUsed"] = hex0x(static_cast(receipt.gas_used)); - cumulative_gas_used += receipt.gas_used; - receipt.cumulative_gas_used = cumulative_gas_used; - if (rev < EVMC_BYZANTIUM) - receipt.post_state = state::mpt_hash(state); - j_receipt["cumulativeGasUsed"] = hex0x(cumulative_gas_used); - - j_receipt["blockHash"] = hex0x(bytes32{}); - j_receipt["contractAddress"] = hex0x(address{}); - j_receipt["logsBloom"] = hex0x(receipt.logs_bloom_filter); - j_receipt["logs"] = json::json::array(); // FIXME: Add to_json - j_receipt["root"] = ""; - j_receipt["status"] = "0x1"; - j_receipt["transactionIndex"] = hex0x(i); - blob_gas_left -= static_cast(tx.blob_gas_used()); - transactions.emplace_back(std::move(tx)); - block_gas_left -= receipt.gas_used; - receipts.emplace_back(std::move(receipt)); - } - - // Restore original std::clog buffer (otherwise std::clog crashes at exit). - if (trace) - std::clog.rdbuf(orig_clog_buf); - } - } - - if (!pre_state_only && rev >= EVMC_PRAGUE) - { - auto deposits_result = collect_deposit_requests(receipts); - if (deposits_result.has_value()) - requests.emplace_back(std::move(*deposits_result)); - else - // Report invalid block in the JSON result when deposit collection fails. - j_result["blockException"] = "invalid deposit event layout"; - auto requests_result = system_call_block_end(state, block, block_hashes, rev, vm); - if (requests_result.has_value()) - std::ranges::move(*requests_result, std::back_inserter(requests)); - else - // Report invalid block in the JSON result when requests fail. - j_result["blockException"] = "system contract empty or failed"; - } - - test::finalize( - state, rev, block.coinbase, block_reward, block.ommers, block.withdrawals); - - j_result["logsHash"] = hex0x(logs_hash(txs_logs)); - j_result["stateRoot"] = hex0x(state::mpt_hash(state)); - } - - j_result["logsBloom"] = hex0x(compute_bloom_filter(receipts)); - j_result["receiptsRoot"] = hex0x(state::mpt_hash(receipts)); - if (rev >= EVMC_SHANGHAI) - j_result["withdrawalsRoot"] = hex0x(state::mpt_hash(block.withdrawals)); - - j_result["txRoot"] = hex0x(state::mpt_hash(transactions)); - j_result["gasUsed"] = hex0x(cumulative_gas_used); - if (rev >= EVMC_CANCUN) - { - j_result["blobGasUsed"] = hex0x( - static_cast(state::max_blob_gas_per_block(blob_params)) - blob_gas_left); - if (block.excess_blob_gas.has_value()) - j_result["currentExcessBlobGas"] = hex0x(*block.excess_blob_gas); - } - if (rev >= EVMC_PRAGUE) - { - // EIP-7685: General purpose execution layer requests - j_result["requests"] = json::json::array(); - for (const auto& r : requests) - { - if (!r.data().empty()) - // Only report non-empty requests. Include the leading type byte. - j_result["requests"].emplace_back(hex0x(r.raw_data)); - } - - auto requests_hash = calculate_requests_hash(requests); - - j_result["requestsHash"] = hex0x(requests_hash); - } - - std::ofstream{output_dir / output_result_file} << std::setw(2) << j_result; - - // Print out current state to outAlloc file - std::ofstream{output_dir / output_alloc_file} << std::setw(2) << to_json(TestState{state}); - - if (!output_body_file.empty()) - std::ofstream{output_dir / output_body_file} << hex0x(rlp::encode(transactions)); - } - catch (const std::exception& e) - { - std::cerr << e.what() << '\n'; - return 1; - } - - return 0; -} diff --git a/evmone/test/unittests/CMakeLists.txt b/evmone/test/unittests/CMakeLists.txt index 458473d..3bb936f 100644 --- a/evmone/test/unittests/CMakeLists.txt +++ b/evmone/test/unittests/CMakeLists.txt @@ -10,6 +10,7 @@ target_sources( baseline_analysis_test.cpp blockchaintest_loader_test.cpp bytecode_test.cpp + create_address_test.cpp evm_fixture.cpp evm_fixture.hpp evm_test.cpp @@ -21,7 +22,9 @@ target_sources( evm_eip3860_initcode_test.cpp evm_eip4844_blobhash_test.cpp evm_eip7516_blobbasefee_test.cpp + evm_eip7843_slotnum_test.cpp evm_eip7939_clz_test.cpp + evm_eip8024_swapn_dupn_exchange_test.cpp evm_memory_test.cpp evm_state_test.cpp evm_storage_test.cpp @@ -49,14 +52,15 @@ target_sources( precompiles_secp256r1_test.cpp precompiles_sha256_test.cpp precompiles_expmod_test.cpp + state_authorization_test.cpp state_block_test.cpp state_bloom_filter_test.cpp state_deposit_requests_test.cpp state_difficulty_test.cpp state_mpt_hash_test.cpp state_mpt_test.cpp - state_new_account_address_test.cpp state_precompiles_test.cpp + state_rlp_decode_test.cpp state_rlp_test.cpp state_system_call_test.cpp state_transition.hpp @@ -65,6 +69,7 @@ target_sources( state_transition_call_test.cpp state_transition_create_test.cpp state_transition_eip7702_test.cpp + state_transition_eip7778_block_gas_test.cpp state_transition_extcode_test.cpp state_transition_selfdestruct_test.cpp state_transition_snippets_test.cpp @@ -78,10 +83,21 @@ target_sources( statetest_loader_tx_test.cpp statetest_logs_hash_test.cpp statetest_withdrawals_test.cpp + tooling_run_test.cpp + tooling_t8n_test.cpp tracing_test.cpp ) -target_link_libraries(evmone-unittests PRIVATE evmone evmone::evmmax evmone::experimental evmone::state evmone::testutils GTest::gtest GTest::gtest_main) +target_link_libraries(evmone-unittests PRIVATE evmone evmone::evmmax evmone::experimental evmone::precompiles evmone::state evmone::testutils blst::blst GTest::gtest GTest::gtest_main) target_include_directories(evmone-unittests PRIVATE ${evmone_private_include_dir}) +if(NOT CMAKE_CXX_CLANG_TIDY) + target_precompile_headers( + evmone-unittests PRIVATE + + + + + ) +endif() gtest_discover_tests( evmone-unittests diff --git a/evmone/test/unittests/baseline_analysis_test.cpp b/evmone/test/unittests/baseline_analysis_test.cpp index fb2bc63..bc7be83 100644 --- a/evmone/test/unittests/baseline_analysis_test.cpp +++ b/evmone/test/unittests/baseline_analysis_test.cpp @@ -13,7 +13,6 @@ TEST(baseline_analysis, legacy) const auto code = push(1) + ret_top(); const auto analysis = evmone::baseline::analyze(code); - EXPECT_EQ(analysis.executable_code(), code); - EXPECT_EQ(analysis.raw_code(), code); - EXPECT_NE(analysis.raw_code().data(), code.data()) << "copy should be made"; + EXPECT_EQ(analysis.code(), code); + EXPECT_NE(analysis.code().data(), code.data()) << "copy should be made"; } diff --git a/evmone/test/unittests/blockchaintest_loader_test.cpp b/evmone/test/unittests/blockchaintest_loader_test.cpp index 1662b39..4b6dce5 100644 --- a/evmone/test/unittests/blockchaintest_loader_test.cpp +++ b/evmone/test/unittests/blockchaintest_loader_test.cpp @@ -152,6 +152,7 @@ TEST(json_loader, blockchain_test) EXPECT_EQ(btt[0].test_blocks[0].block_info.gas_limit, 0x016345785d8a0000); EXPECT_EQ(btt[0].test_blocks[0].block_info.number, 5); EXPECT_EQ(btt[0].test_blocks[0].block_info.timestamp, 0x03e8); + EXPECT_FALSE(btt[0].test_blocks[0].block_info.slot_number.has_value()); EXPECT_EQ(btt[0].test_blocks[0].block_info.ommers.size(), 1); EXPECT_EQ(btt[0].test_blocks[0].block_info.ommers[0].beneficiary, 0xb94f5374fce5ed0000000097c15331677e6ebf0b_address); @@ -384,3 +385,87 @@ TEST(json_loader, blockchain_test_pre_paris) EXPECT_EQ(btt[0].test_blocks[0].block_info.prev_randao, 0x0000000000000000000000000000000000000000000000000000000000020000_bytes32); } + +/// EIP-7843: `slotNumber` absent and `slotNumber` present-and-zero are different encodings. +/// The block header validation requires the field exactly from Amsterdam, so the loader must +/// keep them distinguishable; loading it as a plain integer collapsed both to 0. +TEST(json_loader, blockchain_test_slot_number_absent_differs_from_zero) +{ + // NOTE: fake `rlp` field! Never decoded by loader, only size is read and checked. + // The second block intentionally omits `slotNumber`; the loader does not validate headers. + std::istringstream input{R"({ + "000-slotnum": { + "network": "Amsterdam", + "genesisBlockHeader": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionsTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "number": "0x00", + "gasLimit": "0x01000000", + "gasUsed": "0x00", + "timestamp": "0x00", + "extraData": "0x00", + "slotNumber": "0x00", + "hash": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "pre": {}, + "blocks": [ + { + "rlp": "0x00", + "blockHeader": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "coinbase": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionsTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "number": "0x01", + "gasLimit": "0x01000000", + "gasUsed": "0x00", + "timestamp": "0x0c", + "extraData": "0x", + "slotNumber": "0x00", + "hash": "0x0000000000000000000000000000000000000000000000000000000000000002" + }, + "transactions": [], + "withdrawals": [], + "uncleHeaders": [] + }, + { + "rlp": "0x00", + "blockHeader": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000002", + "coinbase": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionsTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "number": "0x02", + "gasLimit": "0x01000000", + "gasUsed": "0x00", + "timestamp": "0x0d", + "extraData": "0x", + "hash": "0x0000000000000000000000000000000000000000000000000000000000000003" + }, + "transactions": [], + "withdrawals": [], + "uncleHeaders": [] + } + ], + "lastblockhash": "0x0000000000000000000000000000000000000000000000000000000000000003", + "postState": {}, + "sealEngine": "NoProof" + } + })"}; + + const auto btt = load_blockchain_tests(input); + + ASSERT_EQ(btt.size(), 1); + ASSERT_EQ(btt[0].test_blocks.size(), 2); + EXPECT_EQ(btt[0].genesis_block_header.slot_number, 0); + EXPECT_EQ(btt[0].test_blocks[0].block_info.slot_number, 0); + EXPECT_FALSE(btt[0].test_blocks[1].block_info.slot_number.has_value()); +} diff --git a/evmone/test/unittests/state_new_account_address_test.cpp b/evmone/test/unittests/create_address_test.cpp similarity index 93% rename from evmone/test/unittests/state_new_account_address_test.cpp rename to evmone/test/unittests/create_address_test.cpp index 39f047e..e209f19 100644 --- a/evmone/test/unittests/state_new_account_address_test.cpp +++ b/evmone/test/unittests/create_address_test.cpp @@ -2,16 +2,16 @@ // Copyright 2023 The evmone Authors. // SPDX-License-Identifier: Apache-2.0 +#include #include -#include #include using namespace evmc; using namespace evmc::literals; -TEST(state_new_account_address, create_examples) +TEST(create_address, create_examples) { - static constexpr auto addr = evmone::state::compute_create_address; + static constexpr auto addr = evmone::compute_create_address; static constexpr auto S0 = 0x00_address; EXPECT_EQ(addr(S0, 0), 0xbd770416a3345f91e4b34576cb804a576fa48eb1_address); @@ -34,10 +34,10 @@ TEST(state_new_account_address, create_examples) EXPECT_EQ(beacon_roots2, 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02_address); } -TEST(state_new_account_address, create_nonces) +TEST(create_address, create_nonces) { // Explore nonce values from all ranges giving RLP encoding schemes. - static constexpr auto addr = evmone::state::compute_create_address; + static constexpr auto addr = evmone::compute_create_address; struct TestCase { @@ -81,7 +81,7 @@ TEST(state_new_account_address, create_nonces) } } -TEST(state_new_account_address, create_rlp) +TEST(create_address, create_rlp) { // Compute the RLP payload for keccak256 hash producing the final CREATE address. // This test is to visualize what RLP inputs are reaching the final keccak256 hash. @@ -118,9 +118,9 @@ TEST(state_new_account_address, create_rlp) EXPECT_EQ(rlp(S, 0xffffffffffffffff).length() / 2, 31u); } -TEST(state_new_account_address, create2) +TEST(create_address, create2) { - static constexpr auto addr = evmone::state::compute_create2_address; + static constexpr auto addr = evmone::compute_create2_address; static constexpr address SENDERS[] = { 0x00_address, 0x01_address, 0x8000000000000000000000000000000000000000_address}; static constexpr auto z0 = 0x00_bytes32; diff --git a/evmone/test/unittests/evm_calls_test.cpp b/evmone/test/unittests/evm_calls_test.cpp index 17bea76..261d2e2 100644 --- a/evmone/test/unittests/evm_calls_test.cpp +++ b/evmone/test/unittests/evm_calls_test.cpp @@ -5,6 +5,7 @@ /// This file contains EVM unit tests that perform any kind of calls. #include "evm_fixture.hpp" +#include using namespace evmc::literals; using namespace evmone::test; @@ -76,14 +77,13 @@ TEST_P(evm, create) auto call_output = bytes{0xa, 0xb, 0xc}; host.call_result.output_data = call_output.data(); host.call_result.output_size = call_output.size(); - host.call_result.create_address = 0xcc010203040506070809010203040506070809ce_address; host.call_result.gas_left = 200000; execute(300000, sstore(1, create().value(1).input(0, 0x20))); EXPECT_GAS_USED(EVMC_SUCCESS, 115816); - EXPECT_EQ(account.storage[0x01_bytes32].current, - 0x000000000000000000000000cc010203040506070809010203040506070809ce_bytes32); + const auto expected_addr = evmone::compute_create_address(msg.recipient, 0); + EXPECT_EQ(account.storage[0x01_bytes32].current, to_bytes32(expected_addr)); ASSERT_EQ(host.recorded_calls.size(), 1); const auto& call_msg = host.recorded_calls.back(); @@ -91,7 +91,6 @@ TEST_P(evm, create) EXPECT_EQ(call_msg.gas, 263801); EXPECT_EQ(call_msg.value, 0x01_bytes32); EXPECT_EQ(call_msg.input_size, 0x20); - EXPECT_EQ(call_msg.create2_salt, 0x00_bytes32); } TEST_P(evm, create_gas) @@ -111,14 +110,13 @@ TEST_P(evm, create_gas) TEST_P(evm, create2) { - rev = EVMC_CONSTANTINOPLE; + rev = EVMC_PETERSBURG; auto& account = host.accounts[msg.recipient]; account.set_balance(1); const bytes call_output{0xa, 0xb, 0xc}; host.call_result.output_data = call_output.data(); host.call_result.output_size = call_output.size(); - host.call_result.create_address = 0xc2010203040506070809010203040506070809ce_address; host.call_result.gas_left = 200000; execute(300000, sstore(1, create2().value(1).input(0, 0x41).salt(0x5a))); EXPECT_GAS_USED(EVMC_SUCCESS, 115817); @@ -129,15 +127,18 @@ TEST_P(evm, create2) EXPECT_EQ(call_msg.gas, 263775); EXPECT_EQ(call_msg.value, 0x01_bytes32); EXPECT_EQ(call_msg.input_size, 0x41); - EXPECT_EQ(call_msg.create2_salt, 0x5a_bytes32); - EXPECT_EQ(account.storage[0x01_bytes32].current, - 0x000000000000000000000000c2010203040506070809010203040506070809ce_bytes32); + // The VM computes the created address itself: CREATE2 with salt 0x5a over + // the 0x41-byte all-zero initcode read from the empty memory. + const auto initcode = bytes(0x41, 0); + const auto expected_addr = + evmone::compute_create2_address(msg.recipient, 0x5a_bytes32, initcode); + EXPECT_EQ(account.storage[0x01_bytes32].current, to_bytes32(expected_addr)); } TEST_P(evm, create2_salt_cost) { - rev = EVMC_CONSTANTINOPLE; + rev = EVMC_PETERSBURG; const auto code = create2().input(0, 0x20); execute(32021, code); @@ -155,7 +156,7 @@ TEST_P(evm, create2_salt_cost) TEST_P(evm, create_balance_too_low) { - rev = EVMC_CONSTANTINOPLE; + rev = EVMC_PETERSBURG; host.accounts[msg.recipient].set_balance(1); for (auto op : {OP_CREATE, OP_CREATE2}) { @@ -168,12 +169,13 @@ TEST_P(evm, create_balance_too_low) TEST_P(evm, create_failure) { - host.call_result.create_address = 0x00000000000000000000000000000000000000ce_address; - const auto create_address = - bytes_view{host.call_result.create_address.bytes, sizeof(host.call_result.create_address)}; - rev = EVMC_CONSTANTINOPLE; + rev = EVMC_PETERSBURG; for (auto op : {OP_CREATE, OP_CREATE2}) { + const auto computed_addr = op == OP_CREATE ? + evmone::compute_create_address(msg.recipient, 0) : + evmone::compute_create2_address(msg.recipient, {}, {}); + const auto create_address = bytes_view{computed_addr.bytes, sizeof(computed_addr)}; const auto code = push(0) + (3 * OP_DUP1) + op + ret_top(); host.call_result.status_code = EVMC_SUCCESS; @@ -267,7 +269,7 @@ TEST_P(evm, call_with_value_depth_limit) TEST_P(evm, call_depth_limit) { - rev = EVMC_CONSTANTINOPLE; + rev = EVMC_PETERSBURG; msg.depth = 1024; for (auto op : {OP_CALL, OP_CALLCODE, OP_DELEGATECALL, OP_STATICCALL, OP_CREATE, OP_CREATE2}) @@ -649,7 +651,7 @@ TEST_P(evm, call_value) TEST_P(evm, create_oog_after) { - rev = EVMC_CONSTANTINOPLE; + rev = EVMC_PETERSBURG; for (auto op : {OP_CREATE, OP_CREATE2}) { auto code = 4 * push(0) + op + OP_SELFDESTRUCT; @@ -797,7 +799,10 @@ TEST_P(evm, call_gas_refund_aggregation_different_calls) TEST_P(evm, call_gas_refund_aggregation_same_calls) { rev = EVMC_LONDON; - host.accounts[msg.recipient].set_balance(2); + // The first CREATE pushes its address, which the second CREATE then pops as its value argument, + // the balance must cover it. + host.accounts[msg.recipient].balance = + intx::be::store(intx::uint256{1} << 200); host.call_result.status_code = EVMC_SUCCESS; host.call_result.gas_refund = 1; diff --git a/evmone/test/unittests/evm_eip3860_initcode_test.cpp b/evmone/test/unittests/evm_eip3860_initcode_test.cpp index 34943ca..7ecd94c 100644 --- a/evmone/test/unittests/evm_eip3860_initcode_test.cpp +++ b/evmone/test/unittests/evm_eip3860_initcode_test.cpp @@ -14,7 +14,6 @@ inline constexpr size_t initcode_size_limit = 0xc000; TEST_P(evm, create_initcode_limit) { - host.call_result.create_address = 0x02_address; for (const auto& c : {create().input(0, calldataload(0)) + ret_top(), create2().input(0, calldataload(0)) + ret_top()}) { @@ -30,7 +29,9 @@ TEST_P(evm, create_initcode_limit) } else { - EXPECT_OUTPUT_INT(2); + EXPECT_STATUS(EVMC_SUCCESS); + ASSERT_EQ(result.output_size, 32); + EXPECT_NE(intx::be::unsafe::load(result.output_data), 0); } } } diff --git a/evmone/test/unittests/evm_eip7843_slotnum_test.cpp b/evmone/test/unittests/evm_eip7843_slotnum_test.cpp new file mode 100644 index 0000000..eaa3333 --- /dev/null +++ b/evmone/test/unittests/evm_eip7843_slotnum_test.cpp @@ -0,0 +1,42 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2026 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +/// This file contains EVM unit tests for EIP-7843: SLOTNUM opcode. +/// https://eips.ethereum.org/EIPS/eip-7843 + +#include "evm_fixture.hpp" + +using namespace evmone::test; + +TEST_P(evm, slotnum_values) +{ + rev = EVMC_AMSTERDAM; + for (const auto slot_number : {0ull, 0x123456789abcdef0ull, 0xffffffffffffffffull}) + { + host.tx_context.block_slot_number = slot_number; + execute(OP_SLOTNUM + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(slot_number); + } +} + +TEST_P(evm, slotnum_gas_cost) +{ + rev = EVMC_AMSTERDAM; + host.tx_context.block_slot_number = 1; + execute(bytecode{} + OP_SLOTNUM); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_EQ(gas_used, 2); +} + +TEST_P(evm, slotnum_undefined_before_amsterdam) +{ + // SLOTNUM (opcode 0x4b) is introduced in Amsterdam; undefined in earlier forks. + for (const auto r : {EVMC_FRONTIER, EVMC_OSAKA}) + { + rev = r; + execute(bytecode{} + OP_SLOTNUM); + EXPECT_EQ(result.status_code, EVMC_UNDEFINED_INSTRUCTION) << "fork " << r; + } +} diff --git a/evmone/test/unittests/evm_eip8024_swapn_dupn_exchange_test.cpp b/evmone/test/unittests/evm_eip8024_swapn_dupn_exchange_test.cpp new file mode 100644 index 0000000..6b7d5e5 --- /dev/null +++ b/evmone/test/unittests/evm_eip8024_swapn_dupn_exchange_test.cpp @@ -0,0 +1,234 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2026 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +/// This file contains EVM unit tests for EIP-8024: SWAPN, DUPN, EXCHANGE. +/// https://eips.ethereum.org/EIPS/eip-8024 + +#include "evm_fixture.hpp" + +using namespace evmone::test; + +TEST_P(evm, dupn_basic) +{ + if (is_advanced()) + return; + + rev = EVMC_AMSTERDAM; + // imm=0x80 → n=17. Push 17 items, DUP17 duplicates the bottom one. + const auto code = push(1) + 16 * OP_PUSH0 + "e680" + ret_top(); + execute(code); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(1); +} + +TEST_P(evm, dupn_end_of_code) +{ + if (is_advanced()) + return; + + rev = EVMC_AMSTERDAM; + // DUPN at end of code: implicit immediate is 0x00 → n=145. + const auto code = push(1) + 144 * OP_PUSH0 + "e6"; + execute(code); + EXPECT_STATUS(EVMC_SUCCESS); +} + +TEST_P(evm, dupn_invalid_immediate) +{ + if (is_advanced()) + return; + + rev = EVMC_AMSTERDAM; + // 0x5b is in the forbidden range [0x5b–0x7f]. + execute(17 * OP_PUSH0 + "e65b"); + EXPECT_STATUS(EVMC_UNDEFINED_INSTRUCTION); +} + +TEST_P(evm, dupn_stack_overflow) +{ + if (is_advanced()) + return; + + rev = EVMC_AMSTERDAM; + // Regression: DUPN overflow with stack at limit (1024) must not cause UB + // in the stack pointer adjustment (stack_end + stack_height_change). + execute(1024 * OP_PUSH0 + "e680"); + EXPECT_STATUS(EVMC_STACK_OVERFLOW); +} + +TEST_P(evm, dupn_stack_underflow) +{ + if (is_advanced()) + return; + + rev = EVMC_AMSTERDAM; + // imm=0x80 → n=17, but only 16 items on stack. + execute(16 * OP_PUSH0 + "e680"); + EXPECT_STATUS(EVMC_STACK_UNDERFLOW); +} + +TEST_P(evm, dupn_out_of_gas) +{ + if (is_advanced()) + return; + + rev = EVMC_AMSTERDAM; + // 17 PUSH0 (2 gas each = 34) + DUPN (3 gas) = 37 total. + const auto code = 17 * OP_PUSH0 + "e680"; + execute(36, code); + EXPECT_STATUS(EVMC_OUT_OF_GAS); + execute(37, code); + EXPECT_STATUS(EVMC_SUCCESS); +} + +TEST_P(evm, swapn_basic) +{ + if (is_advanced()) + return; + + rev = EVMC_AMSTERDAM; + // imm=0x80 → n=17. SWAP17: swap top with the 17th item. + const auto code = push(2) + 16 * OP_PUSH0 + push(1) + "e780" + ret_top(); + execute(code); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(2); +} + +TEST_P(evm, swapn_invalid_immediate) +{ + if (is_advanced()) + return; + + rev = EVMC_AMSTERDAM; + // 0x5b is in the forbidden range [0x5b–0x7f]. + execute(18 * OP_PUSH0 + "e75b"); + EXPECT_STATUS(EVMC_UNDEFINED_INSTRUCTION); +} + +TEST_P(evm, swapn_invalid_immediate_boundaries) +{ + if (is_advanced()) + return; + + rev = EVMC_AMSTERDAM; + // Verify boundary values of the forbidden range [0x5b–0x7f]. + for (const auto* hex : {"e75b", "e75c", "e75f", "e760", "e77e", "e77f"}) + { + execute(18 * OP_PUSH0 + hex); + EXPECT_STATUS(EVMC_UNDEFINED_INSTRUCTION); + } + // Adjacent valid values: 0x5a → n=235, 0x80 → n=17. + execute(236 * OP_PUSH0 + "e75a"); + EXPECT_STATUS(EVMC_SUCCESS); + execute(18 * OP_PUSH0 + "e780"); + EXPECT_STATUS(EVMC_SUCCESS); +} + +TEST_P(evm, swapn_stack_underflow) +{ + if (is_advanced()) + return; + + rev = EVMC_AMSTERDAM; + // imm=0x80 → n=17, SWAPN needs n+1=18 items but only 17. + execute(17 * OP_PUSH0 + "e780"); + EXPECT_STATUS(EVMC_STACK_UNDERFLOW); +} + +TEST_P(evm, swapn_out_of_gas) +{ + if (is_advanced()) + return; + + rev = EVMC_AMSTERDAM; + // 18 PUSH0 (36 gas) + SWAPN (3 gas) = 39 total. + const auto code = 18 * OP_PUSH0 + "e780"; + execute(38, code); + EXPECT_STATUS(EVMC_OUT_OF_GAS); + execute(39, code); + EXPECT_STATUS(EVMC_SUCCESS); +} + +TEST_P(evm, exchange_basic) +{ + if (is_advanced()) + return; + + rev = EVMC_AMSTERDAM; + // imm=0x8e → (n=1, m=2). Swaps stack[1] and stack[2]. + const auto code = push(0) + push(1) + push(2) + "e88e" + OP_SWAP1 + ret_top(); + execute(code); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(0); +} + +TEST_P(evm, exchange_max_m) +{ + if (is_advanced()) + return; + + rev = EVMC_AMSTERDAM; + // imm=0x8f → (n=1, m=29), the maximum m value. + // Regression: branchless decode off-by-one would produce (1, 30). + const auto code = push(99) + 29 * OP_PUSH0 + "e88f" + OP_SWAP1 + ret_top(); + execute(code); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(99); +} + +TEST_P(evm, exchange_invalid_immediate) +{ + if (is_advanced()) + return; + + rev = EVMC_AMSTERDAM; + // 0x52 is the first byte in the forbidden range [0x52–0x7f]. + execute(3 * OP_PUSH0 + "e852"); + EXPECT_STATUS(EVMC_UNDEFINED_INSTRUCTION); +} + +TEST_P(evm, exchange_stack_underflow) +{ + if (is_advanced()) + return; + + rev = EVMC_AMSTERDAM; + // imm=0x8e → (n=1, m=2), needs m+1=3 items but only 2. + execute(2 * OP_PUSH0 + "e88e"); + EXPECT_STATUS(EVMC_STACK_UNDERFLOW); +} + +TEST_P(evm, exchange_out_of_gas) +{ + if (is_advanced()) + return; + + rev = EVMC_AMSTERDAM; + // 3 PUSH0 (6 gas) + EXCHANGE (3 gas) = 9 total. + const auto code = 3 * OP_PUSH0 + "e88e"; + execute(8, code); + EXPECT_STATUS(EVMC_OUT_OF_GAS); + execute(9, code); + EXPECT_STATUS(EVMC_SUCCESS); +} + +TEST_P(evm, dupn_immediate_0x5b_is_jumpdest) +{ + if (is_advanced()) + return; + + // Code: PUSH1(4) JUMP DUPN 0x5b PUSH1(1) ret_top + // 0x5b is in the forbidden immediate range, so DUPN is invalid, + // but JUMPDEST analysis is unchanged: 0x5b IS a valid jump target. + rev = EVMC_AMSTERDAM; + execute(push(4) + OP_JUMP + "e65b" + push(1) + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(1); + + // Pre-Amsterdam: 0xe6 is undefined single-byte opcode, 0x5b is JUMPDEST. Same result. + rev = EVMC_OSAKA; + execute(push(4) + OP_JUMP + "e65b" + push(1) + ret_top()); + EXPECT_STATUS(EVMC_SUCCESS); + EXPECT_OUTPUT_INT(1); +} diff --git a/evmone/test/unittests/evm_other_test.cpp b/evmone/test/unittests/evm_other_test.cpp index 6eb46e6..7e266ff 100644 --- a/evmone/test/unittests/evm_other_test.cpp +++ b/evmone/test/unittests/evm_other_test.cpp @@ -85,9 +85,10 @@ TEST_P(evm, evmone_block_gas_cost_overflow_create) execute(gas_max - 1, code); EXPECT_STATUS(EVMC_OUT_OF_GAS); - if (!host.recorded_calls.empty()) // turbo + if (!host.recorded_calls.empty()) // Advanced. { - EXPECT_EQ(host.recorded_calls.size(), 3); // baseline + // Baseline: The first CREATE pushes its address, the next fails the memory check. + EXPECT_EQ(host.recorded_calls.size(), 1); } } diff --git a/evmone/test/unittests/evm_state_test.cpp b/evmone/test/unittests/evm_state_test.cpp index f51e643..d2b666b 100644 --- a/evmone/test/unittests/evm_state_test.cpp +++ b/evmone/test/unittests/evm_state_test.cpp @@ -129,7 +129,7 @@ TEST_P(evm, selfbalance) // instruction as a result) auto code = bytecode{} + push(1) + OP_SELFBALANCE + mstore(0) + ret(32 - 6, 6); - rev = EVMC_CONSTANTINOPLE; + rev = EVMC_PETERSBURG; execute(code); EXPECT_EQ(result.status_code, EVMC_UNDEFINED_INSTRUCTION); @@ -506,7 +506,7 @@ TEST_P(evm, extcodehash) execute(code); EXPECT_EQ(result.status_code, EVMC_UNDEFINED_INSTRUCTION); - rev = EVMC_CONSTANTINOPLE; + rev = EVMC_PETERSBURG; execute(code); EXPECT_EQ(gas_used, 418); ASSERT_EQ(result.output_size, 32); diff --git a/evmone/test/unittests/evm_storage_test.cpp b/evmone/test/unittests/evm_storage_test.cpp index eb1fd99..2c23990 100644 --- a/evmone/test/unittests/evm_storage_test.cpp +++ b/evmone/test/unittests/evm_storage_test.cpp @@ -66,7 +66,7 @@ TEST_P(evm, sstore_cost) constexpr auto v1 = 0x01_bytes32; - for (auto r : {EVMC_BYZANTIUM, EVMC_CONSTANTINOPLE, EVMC_PETERSBURG, EVMC_ISTANBUL}) + for (auto r : {EVMC_BYZANTIUM, EVMC_PETERSBURG, EVMC_ISTANBUL}) { rev = r; @@ -103,8 +103,6 @@ TEST_P(evm, sstore_cost) EXPECT_EQ(result.status_code, EVMC_SUCCESS); if (rev >= EVMC_ISTANBUL) EXPECT_EQ(gas_used, 806); - else if (rev == EVMC_CONSTANTINOPLE) - EXPECT_EQ(gas_used, 206); else EXPECT_EQ(gas_used, 5006); execute(205, sstore(1, 1)); @@ -116,8 +114,6 @@ TEST_P(evm, sstore_cost) EXPECT_EQ(result.status_code, EVMC_SUCCESS); if (rev >= EVMC_ISTANBUL) EXPECT_EQ(gas_used, 20812); - else if (rev == EVMC_CONSTANTINOPLE) - EXPECT_EQ(gas_used, 20212); else EXPECT_EQ(gas_used, 25012); @@ -128,8 +124,6 @@ TEST_P(evm, sstore_cost) EXPECT_EQ(result.status_code, EVMC_SUCCESS); if (rev >= EVMC_ISTANBUL) EXPECT_EQ(gas_used, 806); - else if (rev == EVMC_CONSTANTINOPLE) - EXPECT_EQ(gas_used, 206); else EXPECT_EQ(gas_used, 5006); @@ -139,8 +133,6 @@ TEST_P(evm, sstore_cost) EXPECT_EQ(result.status_code, EVMC_SUCCESS); if (rev >= EVMC_ISTANBUL) EXPECT_EQ(gas_used, 20812); - else if (rev == EVMC_CONSTANTINOPLE) - EXPECT_EQ(gas_used, 20212); else EXPECT_EQ(gas_used, 25012); @@ -151,8 +143,6 @@ TEST_P(evm, sstore_cost) EXPECT_EQ(result.status_code, EVMC_SUCCESS); if (rev >= EVMC_ISTANBUL) EXPECT_EQ(gas_used, 5812); - else if (rev == EVMC_CONSTANTINOPLE) - EXPECT_EQ(gas_used, 5212); else EXPECT_EQ(gas_used, 10012); @@ -163,8 +153,6 @@ TEST_P(evm, sstore_cost) EXPECT_EQ(result.status_code, EVMC_SUCCESS); if (rev >= EVMC_ISTANBUL) EXPECT_EQ(gas_used, 5812); - else if (rev == EVMC_CONSTANTINOPLE) - EXPECT_EQ(gas_used, 5212); else EXPECT_EQ(gas_used, 10012); } @@ -255,12 +243,11 @@ TEST_P(evm, sstore_cost_net_gas_metering) }; std::array cost_constants{}; - cost_constants[EVMC_CONSTANTINOPLE] = {200, 20000, 5000, 15000}; cost_constants[EVMC_ISTANBUL] = {800, 20000, 5000, 15000}; cost_constants[EVMC_BERLIN] = {100, 20000, 2900, 15000}; cost_constants[EVMC_LONDON] = {100, 20000, 2900, 4800}; - for (const auto r : {EVMC_CONSTANTINOPLE, EVMC_ISTANBUL, EVMC_BERLIN, EVMC_LONDON}) + for (const auto r : {EVMC_ISTANBUL, EVMC_BERLIN, EVMC_LONDON}) { rev = r; const auto& c = cost_constants.at(static_cast(r)); @@ -292,10 +279,6 @@ TEST_P(evm, sstore_below_stipend) execute(2306, code); EXPECT_EQ(result.status_code, EVMC_OUT_OF_GAS); - rev = EVMC_CONSTANTINOPLE; - execute(2306, code); - EXPECT_EQ(result.status_code, EVMC_SUCCESS); - rev = EVMC_ISTANBUL; execute(2306, code); EXPECT_EQ(result.status_code, EVMC_OUT_OF_GAS); diff --git a/evmone/test/unittests/evm_test.cpp b/evmone/test/unittests/evm_test.cpp index 1e41942..2a60c4b 100644 --- a/evmone/test/unittests/evm_test.cpp +++ b/evmone/test/unittests/evm_test.cpp @@ -91,6 +91,20 @@ TEST_P(evm, dup_stack_overflow) EXPECT_STATUS(EVMC_STACK_OVERFLOW); } +TEST_P(evm, push1_stack_overflow) +{ + for (const auto r : + {EVMC_FRONTIER, EVMC_BYZANTIUM, EVMC_OSAKA, EVMC_AMSTERDAM, EVMC_MAX_REVISION}) + { + rev = r; + // PUSH1 has an immediate byte, so it must not bypass the stack overflow check. + execute(1024 * push(1)); + EXPECT_STATUS(EVMC_SUCCESS); + execute(1025 * push(1)); + EXPECT_STATUS(EVMC_STACK_OVERFLOW); + } +} + TEST_P(evm, dup_stack_underflow) { for (int i = 0; i < 16; ++i) @@ -338,7 +352,7 @@ TEST_P(evm, signextend) TEST_P(evm, signextend_31) { - rev = EVMC_CONSTANTINOPLE; + rev = EVMC_PETERSBURG; execute(bytecode{"61010160000360081c601e0b60005260206000f3"}); EXPECT_GAS_USED(EVMC_SUCCESS, 38); @@ -567,7 +581,7 @@ TEST_P(evm, return_empty_buffer_at_high_offset) TEST_P(evm, shl) { const bytecode code = "600560011b6000526001601ff3"; - rev = EVMC_CONSTANTINOPLE; + rev = EVMC_PETERSBURG; execute(code); EXPECT_EQ(gas_used, 24); EXPECT_EQ(result.status_code, EVMC_SUCCESS); @@ -578,7 +592,7 @@ TEST_P(evm, shl) TEST_P(evm, shr) { const bytecode code = "600560011c6000526001601ff3"; - rev = EVMC_CONSTANTINOPLE; + rev = EVMC_PETERSBURG; execute(code); EXPECT_EQ(gas_used, 24); EXPECT_EQ(result.status_code, EVMC_SUCCESS); @@ -589,7 +603,7 @@ TEST_P(evm, shr) TEST_P(evm, sar) { const bytecode code = "600160000360021d60005260016000f3"; - rev = EVMC_CONSTANTINOPLE; + rev = EVMC_PETERSBURG; execute(code); EXPECT_EQ(gas_used, 30); EXPECT_EQ(result.status_code, EVMC_SUCCESS); @@ -600,7 +614,7 @@ TEST_P(evm, sar) TEST_P(evm, sar_01) { const bytecode code = "600060011d60005260016000f3"; - rev = EVMC_CONSTANTINOPLE; + rev = EVMC_PETERSBURG; execute(code); EXPECT_EQ(gas_used, 24); EXPECT_EQ(result.status_code, EVMC_SUCCESS); @@ -610,7 +624,7 @@ TEST_P(evm, sar_01) TEST_P(evm, shift_overflow) { - rev = EVMC_CONSTANTINOPLE; + rev = EVMC_PETERSBURG; for (auto op : {OP_SHL, OP_SHR, OP_SAR}) { execute(not_(0) + 0x100 + op + ret_top()); @@ -680,7 +694,7 @@ TEST_P(evm, staticmode) { auto code_prefix = 1 + 6 * OP_DUP1; - rev = EVMC_CONSTANTINOPLE; + rev = EVMC_PETERSBURG; for (auto op : {OP_SSTORE, OP_LOG0, OP_LOG1, OP_LOG2, OP_LOG3, OP_LOG4, OP_CALL, OP_CREATE, OP_CREATE2, OP_SELFDESTRUCT}) { diff --git a/evmone/test/unittests/evm_undefined_instructions_test.cpp b/evmone/test/unittests/evm_undefined_instructions_test.cpp index 96aafa3..558a24e 100644 --- a/evmone/test/unittests/evm_undefined_instructions_test.cpp +++ b/evmone/test/unittests/evm_undefined_instructions_test.cpp @@ -10,21 +10,21 @@ using namespace evmone::test; TEST_P(evm, dupn_undefined) { - rev = EVMC_MAX_REVISION; + rev = EVMC_OSAKA; execute(push(1) + "e6" + "00"); EXPECT_STATUS(EVMC_UNDEFINED_INSTRUCTION); } TEST_P(evm, swapn_undefined) { - rev = EVMC_MAX_REVISION; + rev = EVMC_OSAKA; execute(push(1) + push(2) + "e7" + "00"); EXPECT_STATUS(EVMC_UNDEFINED_INSTRUCTION); } TEST_P(evm, exchange_undefined) { - rev = EVMC_MAX_REVISION; + rev = EVMC_OSAKA; execute(push(1) + push(2) + push(3) + "e8" + "00"); EXPECT_STATUS(EVMC_UNDEFINED_INSTRUCTION); } diff --git a/evmone/test/unittests/evmmax_bn254_pairing_test.cpp b/evmone/test/unittests/evmmax_bn254_pairing_test.cpp index 581955a..a28f1cf 100644 --- a/evmone/test/unittests/evmmax_bn254_pairing_test.cpp +++ b/evmone/test/unittests/evmmax_bn254_pairing_test.cpp @@ -11,14 +11,14 @@ using namespace intx; TEST(evmmax, bn254_pairing) { - const auto P1 = Point{ + const auto P1 = AffinePoint{ 0x1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f59_u256, 0x3034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41_u256, }; // -P1: - const auto nP1 = Point{P1.x, Curve::FIELD_PRIME - P1.y}; + const auto nP1 = -P1; // P1 * 17: - const auto P1_17 = Point{ + const auto P1_17 = AffinePoint{ 0x22980b2e458ec77e258b19ca3a7b46181f63c6536307acae03eea236f6919eeb_u256, 0x4eab993e2ba2cca2b08c216645e3fbcf80ae67515b2c49806c17b90c9d3cad3_u256, }; @@ -34,8 +34,7 @@ TEST(evmmax, bn254_pairing) }, }; // -Q1: - const auto nQ1 = - ExtPoint{Q1.x, {Curve::FIELD_PRIME - Q1.y.first, Curve::FIELD_PRIME - Q1.y.second}}; + const auto nQ1 = -Q1; // -Q1 * 16: const auto nQ1_16 = ExtPoint{ { @@ -61,7 +60,7 @@ TEST(evmmax, bn254_pairing) { // p1*q1 - (-p1*q1) = 0? - const std::vector> pairs{ + const std::vector> pairs{ {P1, Q1}, {nP1, Q1}, }; @@ -70,7 +69,7 @@ TEST(evmmax, bn254_pairing) { // p1*q1 - (p1*-q1) = 0? - const std::vector> pairs{ + const std::vector> pairs{ {P1, Q1}, {P1, nQ1}, }; @@ -79,7 +78,7 @@ TEST(evmmax, bn254_pairing) { // p1*17*q1 - (p1*-q1*16) = 0? - const std::vector> pairs{ + const std::vector> pairs{ {P1_17, Q1}, {P1, nQ1_16}, }; @@ -88,7 +87,7 @@ TEST(evmmax, bn254_pairing) { // p1*17 * q1 - (p1 * -q1*17) = 0? - const std::vector> pairs{ + const std::vector> pairs{ {P1_17, Q1}, {P1, nQ1_17}, }; @@ -103,7 +102,7 @@ TEST(evmmax, bn254_pairing) TEST(evmmax, bn254_pairing_invalid_input) { - const std::vector> valid_input{{ + const std::vector> valid_input{{ { 0x22980b2e458ec77e258b19ca3a7b46181f63c6536307acae03eea236f6919eeb_u256, 0x4eab993e2ba2cca2b08c216645e3fbcf80ae67515b2c49806c17b90c9d3cad3_u256, @@ -122,36 +121,22 @@ TEST(evmmax, bn254_pairing_invalid_input) EXPECT_EQ(pairing_check(valid_input), false); - { - // Coordinate not a field element - auto input = valid_input; - input[0].first.x = Curve::FIELD_PRIME; - EXPECT_EQ(pairing_check(input), std::nullopt); - } - - { - // Coordinate not a field element - auto input = valid_input; - input[0].second.x.second = Curve::FIELD_PRIME; - EXPECT_EQ(pairing_check(input), std::nullopt); - } - { // Point P (G1) not on curve auto input = valid_input; - input[0].first.x += 1; + input[0].first.x += Curve::Fp{1}; EXPECT_EQ(pairing_check(input), std::nullopt); } { // Point Q (G2) not on curve auto input = valid_input; - input[0].second.x.first += 1; + input[0].second.x.coeffs[0] += Fq{1}; EXPECT_EQ(pairing_check(input), std::nullopt); } { - // Q not in proper group. Q id a member of small subgroup on twisted curve over Fq^2 + // Q not in proper group. Q is a member of small subgroup on twisted curve over Fq^2. const ExtPoint Q{ { 0x13d841ba7ff3c6efd6870c3fea13a3ecab0423af5e4db9c5d28a6b46a05cd57b_u256, @@ -173,7 +158,7 @@ TEST(evmmax, bn254_pairing_invalid_input) TEST(evmmax_bn254, evm_codes_example) { // Pair 1 - const auto p1 = Point{ + const auto p1 = AffinePoint{ 0x2cf44499d5d27bb186308b7af7af02ac5bc9eeb6a3d147c186b21fb1b76e18da_u256, 0x2c0f001f52110ccfe69108924926e45f0b0c868df0e7bde1fe16d3242dc715f6_u256, }; @@ -189,7 +174,7 @@ TEST(evmmax_bn254, evm_codes_example) }; // Pair 2 - const auto p2 = Point{ + const auto p2 = AffinePoint{ 0x0000000000000000000000000000000000000000000000000000000000000001_u256, 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd45_u256, }; @@ -204,7 +189,7 @@ TEST(evmmax_bn254, evm_codes_example) }, }; - const std::vector> pairs = {{p1, q1}, {p2, q2}}; + const std::vector> pairs = {{p1, q1}, {p2, q2}}; const auto result = pairing_check(pairs); ASSERT_TRUE(result.has_value()) << "Pairing check should return a value."; @@ -214,7 +199,7 @@ TEST(evmmax_bn254, evm_codes_example) TEST(evmmax_bn254, evm_codes_example_changed_order) { // Pair 1 - const auto p1 = Point{ + const auto p1 = AffinePoint{ 0x2cf44499d5d27bb186308b7af7af02ac5bc9eeb6a3d147c186b21fb1b76e18da_u256, 0x2c0f001f52110ccfe69108924926e45f0b0c868df0e7bde1fe16d3242dc715f6_u256, }; @@ -230,7 +215,7 @@ TEST(evmmax_bn254, evm_codes_example_changed_order) }; // Pair 2 - const auto p2 = Point{ + const auto p2 = AffinePoint{ 0x0000000000000000000000000000000000000000000000000000000000000001_u256, 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd45_u256, }; @@ -245,7 +230,7 @@ TEST(evmmax_bn254, evm_codes_example_changed_order) }, }; - const std::vector> pairs = {{p1, q1}, {p2, q2}}; + const std::vector> pairs = {{p1, q1}, {p2, q2}}; const auto result = pairing_check(pairs); ASSERT_TRUE(!result.has_value()) diff --git a/evmone/test/unittests/evmmax_secp256k1_test.cpp b/evmone/test/unittests/evmmax_secp256k1_test.cpp index 6619570..1339291 100644 --- a/evmone/test/unittests/evmmax_secp256k1_test.cpp +++ b/evmone/test/unittests/evmmax_secp256k1_test.cpp @@ -275,6 +275,10 @@ const TestCase TEST_CASES[]{ // s >= Order {"18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c 000000000000000000000000000000000000000000000000000000000000001c 73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", {}}, + // r is in range but is not the x coordinate of any curve point: 5**3 + 7 is not a quadratic + // residue modulo the field prime, so no point has x == 5. + {"18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c 000000000000000000000000000000000000000000000000000000000000001c 0000000000000000000000000000000000000000000000000000000000000005 3134a4ba8fafe11b351a720538398a5635e235c0b3258dce19942000731079ec", + {}}, // u1 == u2 && R == G {"c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 000000000000000000000000000000000000000000000000000000000000001b 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 3a2db9fe7908dcc36d81824d2338fc3dd5ae2692e4c6790043d7868872b09cd1", "0000000000000000000000002e4db28b1f03ec8acfc2865e0c08308730e7ddf2"}, @@ -293,6 +297,12 @@ const TestCase TEST_CASES[]{ // R == 2G, high s {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 000000000000000000000000000000000000000000000000000000000000001c c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5 fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413b", "000000000000000000000000bbb10a3b5835400b63ca00372c16db781220fb0b"}, + // R == 2G, s == ORDER/2: the highest s a strict (EIP-2) recovery accepts. + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 000000000000000000000000000000000000000000000000000000000000001c c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5 7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0", + "00000000000000000000000090dd1d3d5a9814647c17016ce932360f61639baa"}, + // R == 2G, s == ORDER/2 + 1: the lowest s a strict recovery rejects. + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 000000000000000000000000000000000000000000000000000000000000001c c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5 7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1", + "00000000000000000000000026944cf58be26228fdf1e153c37e2152a21a7a97"}, // R == 3G, low s {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 000000000000000000000000000000000000000000000000000000000000001c f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9 0000000000000000000000000000000000000000000000000000000000000010", "000000000000000000000000620833dce54ca9329f13a22c3831b102f15df27c"}, @@ -309,7 +319,7 @@ const TestCase TEST_CASES[]{ }; } // namespace -TEST(evmmax, ecrecovery) +TEST(evmmax, ecrecovery_malleable) { for (const auto& [input_hex, expected_output_hex] : TEST_CASES) { @@ -339,3 +349,42 @@ TEST(evmmax, ecrecovery) } } } + +TEST(evmmax, ecrecovery_strict) +{ + const auto order_half = "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0"_hex; + ASSERT_EQ(order_half.size(), 32); + + for (const auto& [input_hex, malleable_expected_output_hex] : TEST_CASES) + { + const auto input = from_spaced_hex(input_hex).value(); + ASSERT_EQ(input.size(), 128); + + const std::span input_span{input}; + const auto hash = input_span.subspan<0, 32>(); + const auto v_bytes = input_span.subspan<32, 32>(); + const auto r_bytes = input_span.subspan<64, 32>(); + const auto s_bytes = input_span.subspan<96, 32>(); + + // Both are 32-byte big-endian values, so the byte-wise order is the numeric one. + const auto s_high = std::ranges::lexicographical_compare(order_half, s_bytes); + const auto expected_output_hex = + !s_high ? malleable_expected_output_hex : std::string_view{}; + + const auto v = be::unsafe::load(v_bytes.data()); + ASSERT_TRUE(v == 27 || v == 28); + const bool parity = v == 28; + + const auto result = ecrecover(hash, r_bytes, s_bytes, parity, RecoveryMode::strict); + + if (expected_output_hex.empty()) + { + EXPECT_FALSE(result.has_value()); + } + else + { + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(std::string(24, '0') + hex(*result), expected_output_hex); + } + } +} diff --git a/evmone/test/unittests/evmone_test.cpp b/evmone/test/unittests/evmone_test.cpp index a80dfd3..ad2d35e 100644 --- a/evmone/test/unittests/evmone_test.cpp +++ b/evmone/test/unittests/evmone_test.cpp @@ -15,13 +15,6 @@ TEST(evmone, info) EXPECT_TRUE(vm.is_abi_compatible()); } -TEST(evmone, capabilities) -{ - auto vm = evmc_create_evmone(); - EXPECT_EQ(vm->get_capabilities(vm), evmc_capabilities_flagset{EVMC_CAPABILITY_EVM1}); - vm->destroy(vm); -} - TEST(evmone, set_option_invalid) { auto vm = evmc_create_evmone(); diff --git a/evmone/test/unittests/instructions_test.cpp b/evmone/test/unittests/instructions_test.cpp index 4713b0c..d700605 100644 --- a/evmone/test/unittests/instructions_test.cpp +++ b/evmone/test/unittests/instructions_test.cpp @@ -45,6 +45,8 @@ consteval void validate_traits_of() noexcept // immediate_size if constexpr (Op >= OP_PUSH1 && Op <= OP_PUSH32) static_assert(tr.immediate_size == Op - OP_PUSH1 + 1); + else if constexpr (Op == OP_DUPN || Op == OP_SWAPN || Op == OP_EXCHANGE) + static_assert(tr.immediate_size == 1); else static_assert(tr.immediate_size == 0); diff --git a/evmone/test/unittests/precompiles_expmod_test.cpp b/evmone/test/unittests/precompiles_expmod_test.cpp index 1f48785..b03fe58 100644 --- a/evmone/test/unittests/precompiles_expmod_test.cpp +++ b/evmone/test/unittests/precompiles_expmod_test.cpp @@ -290,6 +290,74 @@ TEST_P(expmod, inputs) {"02", "80", "0300000000000000000000000000000000", "0100000000000000000000000000000000"}, // 2^129 mod (7 * 2^128): carry propagates and is absorbed in nonzero word. {"02", "0081", "0700000000000000000000000000000000", "0200000000000000000000000000000000"}, + + // Sliding-window exponentiation in modexp_odd. One case per window width w=1..5. + // Each exponent is built as: top bit (1) | zero run of w+1 bits | one run of w bits + // | trailing zeros, so its windows exercise both the first (b^1) and last + // (b^(2^w-1)) precomputed odd powers, the zero run in between being wide enough to + // keep them in separate windows, traversed by squarings alone. Modulus is the + // secp256k1 field prime: odd, 4 words, so these also cover the mul_amm<4> + // specialization. + // exp_bits=6, w=1: plain binary square-and-multiply, no table. + {"03", "24", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "00000000000000000000000000000000000000000000000002153e468b91c6d1"}, + // exp_bits=10, w=2: windows hit b^1 and b^3. + {"03", "0230", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "e123f780b153ebd75b17a6e7a7133dba60d90a7dbc0f770f08af0055f8e2c7ed"}, + // exp_bits=30, w=3: windows hit b^1 and b^7. + {"03", "21c00000", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "6cc581d10c7d071216edf63238959949056d7cddf5a90711a7c7cdec6b3e861f"}, + // exp_bits=100, w=4: windows hit b^1 and b^15. + {"03", "083c0000000000000000000000", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "7ff2c68783b688439f7c43de4cbfe265f8875ec726564a442c2cbd1244f6d99e"}, + // exp_bits=254 (mainnet-typical size), w=5: windows hit b^1 and b^31. + {"03", "207c000000000000000000000000000000000000000000000000000000000000", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "40ea9ce0f6a2c94a7bec98114179d8e1a21287312a25c1fdd7bf46e3d723984a"}, + // Same exponent as the w=5 case above, with a 5-word modulus: the cases above only + // ever run through the mul_amm<4> specialization, this covers the generic + // std::dynamic_extent instantiation at w > 1. + {"03", "207c000000000000000000000000000000000000000000000000000000000000", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "aa50260a96f69a722fc965bbfec20c21195eda68068b20e9899976f80ed8f6d4f6816bec10fc4ee6"}, + + // Random exponents straddling the width thresholds of the fixed-window + // implementation this replaced, which no longer coincide with the bands above: + // exp_bits 16..18 (w=2), 48..51 (w=3), 144..148 (w=4). Same modulus as above, + // except for the last case, which repeats exp_bits=148 with a 5-word modulus. + {"03", "8005", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "79c4559d064ab3615f6da729a1f67265b88ee2eaba22838109bea30fb7bee31b"}, + {"03", "01001b", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "a890a61d8d745fae67a345fb031b048c0cf8952b43622263de0fdc4391a6c6a9"}, + {"03", "0200c9", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "600614416289329cf72ef906cdfc1dea20339051ec80ed3ff692eb14ed33be81"}, + {"03", "80013b71b865", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "fd66fdbe1f0c43e6640c121c366b9061c7f13964a572828c8e3968a50dba847f"}, + {"03", "0100d2c92fc182", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "651aace134976d8456fcc35686a57cf12670b2e596dabecd0ddae9984ced96c4"}, + {"03", "0200a6a7ef231d", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "f722a91e1faa3b57f0a19af8d4506b395a0a342e9ee2cbe65cd7a63155d38537"}, + {"03", "04013929f7999c", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "06f41e370c4ef45a2bc5e1ade1504fbe35e5a42a8f8c2b17ad16a6c657900d48"}, + {"03", "8004cb3ff13151bb9f84a488a5d62e79a680", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "97265df41405de7f9b35c1037c349ef367cffd34ed6a86cb933fe14f84bb12d1"}, + {"03", "010014b0a1922289f0b19f56c6c373b0e5cd4a", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "3587c0d41ce1eb59ec2fa686877d8166aa9740f2410f9271592e5f283e3bd738"}, + {"03", "02008d61508c16734bdbe4a9578f4c8185d260", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "f65d573e0ba5bdc7cc0e31072eb946ffe5138d0cd4bc936cc1a714d17cdaf954"}, + {"03", "040160dce60c2531e93ae750b53938d5b04faf", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "0648a7caabfd3d4b972c034830faf933179ed038e1e6a6c4c3ad26f330fe1397"}, + {"03", "0802ae8d294c48793907af3e71b536ed84fa84", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "40c2770e749bcbf7949855252da0258cc5ae80658427a4af8ba3489a81182ee9"}, + {"03", "08f83d563ebc382e09e4b8245edebc817af708", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "8016137e4c542dd66f4ab5f668fc0ac76d43353a675f3d4616a56f23757e463ca1093164385ef006"}, }; for (const auto& [base_hex, exp_hex, mod_hex, expected_result_hex] : test_cases) diff --git a/evmone/test/unittests/precompiles_kzg_test.cpp b/evmone/test/unittests/precompiles_kzg_test.cpp index c14ca45..e189920 100644 --- a/evmone/test/unittests/precompiles_kzg_test.cpp +++ b/evmone/test/unittests/precompiles_kzg_test.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -44,6 +45,38 @@ TEST(kzg, verify_proof_zero) EXPECT_TRUE(r); } +TEST(kzg, verify_g2_gen_lines) +{ + blst_fp6 expected[68]; + blst_precompute_lines(expected, blst_p2_affine_generator()); + const auto precomputed = g2_gen_lines(); + EXPECT_TRUE(std::memcmp(precomputed, expected, sizeof(expected)) == 0); +} + +TEST(kzg, verify_kzg_setup_g2_1_lines) +{ + /// The point [s]₂ at index 1 of the G2 series of the Ethereum mainnet KZG + /// trusted setup. Affine coordinates in Montgomery form. The compressed + /// source (y-parity bit and Fp² x coordinate) is g2_monomial[1] at: + /// https://github.com/ethereum/consensus-specs/blob/master/presets/mainnet/trusted_setups/trusted_setup_4096.json#L8200 + /// + /// Not in the public header file because we don't want to expose blst types. + constexpr blst_p2_affine KZG_SETUP_G2_1{ + {{{0x6120a2099b0379f9, 0xa2df815cb8210e4e, 0xcb57be5577bd3d4f, 0x62da0ea89a0c93f8, + 0x02e0ee16968e150d, 0x171f09aea833acd5}, + {0x11a3670749dfd455, 0x04991d7b3abffadc, 0x85446a8e14437f41, 0x27174e7b4e76e3f2, + 0x7bfa6dd397f60a20, 0x02fcc329ac07080f}}}, + {{{0xaa130838793b2317, 0xe236dd220f891637, 0x6502782925760980, 0xd05c25f60557ec89, + 0x6095767a44064474, 0x185693917080d405}, + {0x549f9e175b03dc0a, 0x32c0c95a77106cfe, 0x64a74eae5705d080, 0x53deeaf56659ed9e, + 0x09a1d368508afb93, 0x12cf3a4525b5e9bd}}}}; + + blst_fp6 expected[68]; + blst_precompute_lines(expected, &KZG_SETUP_G2_1); + const auto precomputed = kzg_setup_g2_1_lines(); + EXPECT_TRUE(std::memcmp(precomputed, expected, sizeof(expected)) == 0); +} + TEST(kzg, verify_proof_constant) { // Commit and prove polynomial f(x) = 1. @@ -61,3 +94,23 @@ TEST(kzg, verify_proof_constant) const auto r = kzg_verify_proof(hash.data(), z, y, c, POINT_AT_INFINITY); EXPECT_TRUE(r); } + +TEST(kzg, verify_proof_final_add_doubling) +{ + // Force the final G1 addition to be a doubling. + // + // Setup: π = O, y = 1, z = 0 ⇒ [z]π − [y]G1 = −G1. + std::byte z[32]{}; + std::byte y[32]{}; + y[31] = std::byte{1}; + + // C = −G1 (compressed): same X as G1 with the compressed flag (0x80) + // and the Y-sign flag (0x20) set, since y(G1) is the lexicographically + // smaller of the two square roots. + std::byte c[48]{}; + intx::be::unsafe::store(reinterpret_cast(c), G1_GENERATOR_X); + c[0] |= std::byte{0xA0}; + + const auto hash = versioned_hash(c); + EXPECT_FALSE(kzg_verify_proof(hash.data(), z, y, c, POINT_AT_INFINITY)); +} diff --git a/evmone/test/unittests/state_authorization_test.cpp b/evmone/test/unittests/state_authorization_test.cpp new file mode 100644 index 0000000..8a50d46 --- /dev/null +++ b/evmone/test/unittests/state_authorization_test.cpp @@ -0,0 +1,40 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2026 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +using namespace evmc::literals; +using namespace intx; +using namespace evmone; + +namespace +{ +/// A valid authorization from the execution-specs. +constexpr state::Authorization SIGNED_AUTHORIZATION{ + .chain_id = 0, + .addr = 0x37f536464af59c8d7358cae965f92cbeadd58dcb_address, + .nonce = 0, + .y_parity = 0, + .r = 0x16ee2526c737c019c381de001f6aa6fb8a5f4090084b2c58f824bc78b00d827f_u256, + .s = 0x49c6df445a9967a8510b8169445d6d0373b94ad7c789edc23cc8dd2a22fd40c2_u256, +}; +} // namespace + +TEST(state_authorization, recover_valid) +{ + EXPECT_EQ(state::recover_authority(SIGNED_AUTHORIZATION), + 0x1ad9bc24818784172ff393bb6f89f094d4d2ca29_address); +} + +TEST(state_authorization, recover_rejects_invalid_y_parity) +{ + // y_parity is 0 or 1: the legacy v encodings are not accepted here, nor is any other value. + auto auth = SIGNED_AUTHORIZATION; + for (const auto y_parity : {uint8_t{2}, uint8_t{0xff}}) + { + auth.y_parity = y_parity; + EXPECT_FALSE(state::recover_authority(auth).has_value()) << int{y_parity}; + } +} diff --git a/evmone/test/unittests/state_rlp_decode_test.cpp b/evmone/test/unittests/state_rlp_decode_test.cpp new file mode 100644 index 0000000..2756689 --- /dev/null +++ b/evmone/test/unittests/state_rlp_decode_test.cpp @@ -0,0 +1,684 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2026 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include +#include +#include + +using namespace evmc::literals; +using namespace intx; +using namespace evmone; +using namespace evmone::test; + +namespace +{ +/// A minimal, decodable pre-EIP-155 legacy transaction; the base for the field-mutation rejection +/// tests below. Not constexpr: Transaction is not a literal type before libstdc++ 12, which lacks +/// the constexpr container destructors. +const state::Transaction MINIMAL_LEGACY_TX{ + .type = state::Transaction::Type::legacy, + .gas_limit = 21000, + .max_gas_price = 1, + .r = 1_u256, + .s = 2_u256, + .v = 27, +}; + +/// Encodes @p tx and decodes it back; the transaction must decode. +state::Transaction round_trip(const state::Transaction& tx) +{ + const auto decoded = state::decode_transaction(rlp::encode(tx)); + EXPECT_TRUE(decoded.has_value()); + // The argument cannot be spelled {}: value_or() deduces its parameter type (libc++ rejects it). + return decoded.value_or(state::Transaction{}); +} + +/// Decodes @p txbytes and recovers the sender of the transaction; it must decode. +std::optional
recover(const bytes& txbytes) +{ + const auto tx = state::decode_transaction(txbytes); + EXPECT_TRUE(tx.has_value()); + return state::recover_sender(tx.value(), txbytes); +} + +/// Compares all decoded fields of two transactions (sender is not recovered by the decoder). +void expect_tx_eq(const state::Transaction& expected, const state::Transaction& actual) +{ + EXPECT_EQ(actual.type, expected.type); + EXPECT_EQ(actual.chain_id, expected.chain_id); + EXPECT_EQ(actual.nonce, expected.nonce); + EXPECT_EQ(actual.max_priority_gas_price, expected.max_priority_gas_price); + EXPECT_EQ(actual.max_gas_price, expected.max_gas_price); + EXPECT_EQ(actual.gas_limit, expected.gas_limit); + EXPECT_EQ(actual.to, expected.to); + EXPECT_EQ(actual.value, expected.value); + EXPECT_EQ(actual.data, expected.data); + EXPECT_EQ(actual.access_list, expected.access_list); + EXPECT_EQ(actual.max_blob_gas_price, expected.max_blob_gas_price); + EXPECT_EQ(actual.blob_hashes, expected.blob_hashes); + EXPECT_EQ(actual.v, expected.v); + EXPECT_EQ(actual.r, expected.r); + EXPECT_EQ(actual.s, expected.s); + + ASSERT_EQ(actual.authorization_list.size(), expected.authorization_list.size()); + for (size_t i = 0; i < expected.authorization_list.size(); ++i) + { + const auto& e = expected.authorization_list[i]; + const auto& a = actual.authorization_list[i]; + EXPECT_EQ(a.chain_id, e.chain_id); + EXPECT_EQ(a.addr, e.addr); + EXPECT_EQ(a.nonce, e.nonce); + EXPECT_EQ(a.y_parity, e.y_parity); + EXPECT_EQ(a.r, e.r); + EXPECT_EQ(a.s, e.s); + } +} +} // namespace + +TEST(state_rlp_decode, tx_round_trip) +{ + // decode(encode(tx)) reproduces each typed transaction. Every tx is in "decoded normal form" + // (typed y_parity in {0, 1}; access-list max_priority mirrors the single gas price), so the + // decoded transaction must equal the input. Legacy is asymmetric and covered separately. + using enum state::Transaction::Type; + const auto to = 0x9232a548dd9e81bac65500b5e0d918f8ba93675c_address; + const state::AccessList example_access_list{ + {to, {0x8e947fe742892ee6fffe7cfc013acac35d33a3892c58597344bed88b21eb1d2f_bytes32}}, + }; + + const std::array cases{ + std::pair{"access_list", // EIP-2930. + state::Transaction{ + .type = access_list, + .data = "0x095ea7b3"_hex, + .gas_limit = 0xc835, + .max_gas_price = 0x64, + .max_priority_gas_price = 0x64, // Mirrors the single wire gas price. + .to = to, + .access_list = example_access_list, + .chain_id = 1, + .nonce = 62, + .r = 0x2c_u256, + .s = 0x41_u256, + .v = 1, + }}, + std::pair{"eip1559", + state::Transaction{ + .type = eip1559, + .data = "0x095ea7b3"_hex, + .gas_limit = 0x9c40, + .max_gas_price = 0x64, + .max_priority_gas_price = 0x0a, + .to = to, + .value = 0x0de0b6b3a7640000_u256, + .access_list = example_access_list, + .chain_id = 1, + .nonce = 42, + .r = 0x2c_u256, + .s = 0x41_u256, + .v = 1, + }}, + std::pair{"blob", + state::Transaction{ + .type = blob, + .gas_limit = 0x7530, + .max_gas_price = 0x64, + .max_blob_gas_price = 4, + .to = 0x535b918f3724001fd6fb52fcc6cbc220592990a3_address, + .value = 7_u256, + .blob_hashes = + { + 0x0111111111111111111111111111111111111111111111111111111111111111_bytes32, + 0x0122222222222222222222222222222222222222222222222222222222222222_bytes32, + }, + .chain_id = 1, + .nonce = 5, + .r = 9_u256, + .s = 0xa_u256, + .v = 1, + }}, + std::pair{"set_code", // EIP-7702; auth y_parity may be any value < 2**8. + state::Transaction{ + .type = set_code, + .gas_limit = 0x186a0, + .max_gas_price = 7, + .to = 0x1111_address, + .chain_id = 1, + .r = 1_u256, + .s = 2_u256, + .v = 0, + .authorization_list = + { + { + .chain_id = 1_u256, + .addr = 0x2222_address, + .nonce = 0, + .y_parity = 2, + .r = 0x1234_u256, + .s = 0x5678_u256, + }, + { + .chain_id = 1_u256, + .addr = 0x3333_address, + .nonce = 7, + .y_parity = 27, + .r = 0x9abc_u256, + .s = 0xdef0_u256, + }, + { + .chain_id = 1_u256, + .addr = 0x4444_address, + .nonce = 42, + .y_parity = 0xff, + .r = 0xaaaa_u256, + .s = 0xbbbb_u256, + }, + }, + }}, + }; + + for (const auto& [name, tx] : cases) + { + SCOPED_TRACE(name); + expect_tx_eq(tx, round_trip(tx)); + } +} + +TEST(state_rlp_decode, tx_round_trip_legacy) +{ + // A legacy transaction has a single wire gas price, so the decoded form differs from the input + // in max_priority_gas_price; v is kept verbatim and the chain id derived from it. + // EIP-155: v = 35 + 2 * chain_id + parity, with v = 35 the lowest accepted value; before it, + // v is 27 or 28 and the transaction is bound to no chain. + for (const auto& [v, chain_id] : + {std::pair{27u, uint64_t{0}}, std::pair{28u, uint64_t{0}}, std::pair{35u, uint64_t{0}}, + std::pair{36u, uint64_t{0}}, std::pair{37u, uint64_t{1}}, std::pair{38u, uint64_t{1}}}) + { + SCOPED_TRACE(v); + const state::Transaction in{ + .type = state::Transaction::Type::legacy, + .data = "0xdeadbeef"_hex, + .gas_limit = 0x5208, + .max_gas_price = 0x0102, + .to = 0x9232a548dd9e81bac65500b5e0d918f8ba93675c_address, + .value = 0xabcdef_u256, + .nonce = 7, + .r = 0x1111_u256, + .s = 0x2222_u256, + .v = v, + }; + + auto expected = in; + expected.max_priority_gas_price = in.max_gas_price; + expected.chain_id = chain_id; + expect_tx_eq(expected, round_trip(in)); + } + + // The largest chain id a legacy transaction can carry: any larger one has a v above uint64. + auto in = MINIMAL_LEGACY_TX; + in.v = std::numeric_limits::max(); + auto expected = in; + expected.max_priority_gas_price = in.max_gas_price; + expected.chain_id = (std::numeric_limits::max() - 35) / 2; + expect_tx_eq(expected, round_trip(in)); +} + +TEST(state_rlp_decode, tx_set_code_auth_y_parity_overflow_rejected) +{ + // EIP-7702 bounds y_parity to < 2**8; a value of 2**8 fails the whole transaction at decode + // time, matching geth (V uint8) and revm/alloy (y_parity: U8). Authorization::y_parity cannot + // hold such a value, so the tuple is encoded by hand. + const auto auth = rlp::encode_tuple( + 1_u256, 0x2222_address, uint64_t{0}, 0x100_u256, 0x1234_u256, 0x5678_u256); + const auto payload = rlp::encode(uint64_t{1}) + // chain_id + rlp::encode(uint64_t{0}) + // nonce + rlp::encode(uint64_t{0}) + // max_priority_gas_price + rlp::encode(uint64_t{7}) + // max_gas_price + rlp::encode(uint64_t{0x186a0}) + // gas_limit + rlp::encode(0x1111_address) + // to + rlp::encode(uint64_t{0}) + // value + rlp::encode(bytes_view{}) + // data + rlp::encode(state::AccessList{}) + // access_list + rlp::internal::wrap_list(auth) + // authorization_list + rlp::encode(uint64_t{0}) + // y_parity + rlp::encode(1_u256) + rlp::encode(2_u256); // r, s + EXPECT_FALSE( + state::decode_transaction("0x04"_hex + rlp::internal::wrap_list(payload)).has_value()); +} + +TEST(state_rlp_decode, tx_rejects_trailing_data) +{ + // Both the legacy and the typed envelope must reject bytes after the transaction. + EXPECT_FALSE(state::decode_transaction(rlp::encode(MINIMAL_LEGACY_TX) + "00"_hex).has_value()); + + auto typed = MINIMAL_LEGACY_TX; + typed.type = state::Transaction::Type::eip1559; + typed.chain_id = 1; + typed.v = 0; // Typed y_parity must be in {0, 1}. + EXPECT_FALSE(state::decode_transaction(rlp::encode(typed) + "00"_hex).has_value()); +} + +TEST(state_rlp_decode, tx_rejects_under_declared_list_length) +{ + // A list-length prefix that declares fewer bytes than the fields that follow must be rejected; + // the decoder must honor the declared list boundary, not read past it. + auto rlp = rlp::encode(MINIMAL_LEGACY_TX); + ASSERT_GE(rlp[0], 0xc0); // Short RLP list. + ASSERT_LT(rlp[0], 0xf8); + rlp[0] = static_cast(rlp[0] - 1); // Declare one byte less than the payload. + EXPECT_FALSE(state::decode_transaction(rlp).has_value()); +} + +TEST(state_rlp_decode, header_boundaries) +{ + // decode_header must reject truncated, overflowing, and non-canonical headers and accept a + // canonical long form. Each rejection below guards a specific past regression. + rlp::Header h; + const auto rejected = [&h](bytes_view in) { + bytes_view v = in; + return !rlp::decode_header(v, h); + }; + + // A declared length must fit the available input. Each input sits exactly one byte over what + // is present, so a bound loosened by one would accept it and hand out a payload running past + // the input. + EXPECT_TRUE(rejected("0x8201"_hex)); // short string declares 2 bytes, 1 present + EXPECT_TRUE(rejected("0xb901"_hex)); // long-string length header truncated + EXPECT_TRUE(rejected("0xc201"_hex)); // short list declares 2 bytes, 1 present + EXPECT_TRUE(rejected("0xf901"_hex)); // long-list length header truncated + EXPECT_TRUE(rejected("0xb838"_hex + bytes(55, 0x11))); // long string declares 56, 55 present + EXPECT_TRUE(rejected("0xf838"_hex + bytes(55, 0x11))); // long list declares 56, 55 present + + // A long-form length near 2**64 must be rejected; the naive bounds check + // `payload_length + length_of_length >= input_len` overflows uint64 and would accept it. + EXPECT_TRUE(rejected(bytes(9, uint8_t{0xff}))); // long list + EXPECT_TRUE(rejected(bytes{uint8_t{0xbf}} + bytes(8, uint8_t{0xff}))); // long string + + // The long-form length must be canonical: no leading zero byte. The two-byte lengths below + // are otherwise valid, so only the leading-zero rule can reject them. + EXPECT_TRUE(rejected("0xb90038"_hex + bytes(56, 0x11))); // long string, length 0x0038 == 56 + EXPECT_TRUE(rejected("0xf90038"_hex + bytes(56, 0x11))); // long list, length 0x0038 == 56 + EXPECT_TRUE(rejected("0xb800"_hex)); // long string, length byte 0 + EXPECT_TRUE(rejected("0xf800"_hex)); // long list, length byte 0 + + // The long form is reserved for payloads longer than the short-form maximum (55); 55 itself + // must still use the short form. + EXPECT_TRUE(rejected("0xb837"_hex + bytes(55, 0x11))); // long string for a 55-byte payload + EXPECT_TRUE(rejected("0xf837"_hex + bytes(55, 0x11))); // long list for a 55-byte payload + + // A byte below 0x80 must be its own encoding, not a 1-byte string (0x81 0x7f). + EXPECT_TRUE(rejected("0x817f"_hex)); + + // A valid long string (payload longer than the short-form maximum) decodes. + auto long_str = "0xb838"_hex; // long string, one length byte 0x38 == 56. + long_str.append(56, uint8_t{0x11}); + bytes_view v = long_str; + ASSERT_TRUE(rlp::decode_header(v, h)); + EXPECT_FALSE(h.is_list); + EXPECT_EQ(h.payload_length, 56u); +} + +TEST(state_rlp_decode, fixed_width_requires_exact_length) +{ + // Regression: a fixed-width field (address/hash/storage key) must be encoded as exactly N + // bytes; a shorter string was silently zero-padded and accepted. + // Cover both fixed-width instantiations -- bytes32 (32 bytes) and address (20 bytes) -- across + // an exact-length accept and every reject path of the span decoder. + const auto check = [](auto& out, size_t n) { + const auto rejects = [&out](const bytes& in) { + bytes_view v = in; + return !rlp::decode(v, out); + }; + { + bytes in{static_cast(0x80 + n)}; // exactly n bytes + in.append(n, uint8_t{0x11}); + bytes_view v = in; + EXPECT_TRUE(rlp::decode(v, out)); + EXPECT_TRUE(v.empty()); + } + { + bytes in{static_cast(0x80 + n - 1)}; // one byte too short + in.append(n - 1, uint8_t{0x11}); + EXPECT_TRUE(rejects(in)); + } + { + bytes in{static_cast(0x80 + n + 1)}; // one byte too long + in.append(n + 1, uint8_t{0x11}); + EXPECT_TRUE(rejects(in)); + } + EXPECT_TRUE(rejects("0xc0"_hex)); // a list where a fixed-width string is expected + EXPECT_TRUE(rejects(bytes{})); // empty input: no header to decode + }; + bytes32 hash; + check(hash, 32); + address addr; + check(addr, 20); +} + +TEST(state_rlp_decode, integer_rejects_malformed) +{ + // A scalar field must be a canonical, width-bounded string, not a leading-zero integer, an + // oversized one, or a list. + // Exercise both scalar-decode instantiations the transaction decoder uses -- uint64_t (nonce, + // gas limit) and uint256 (value, r, s, y_parity, chain id) -- across accept and every reject + // path, so each width's decode is covered independently. + const auto check = [](T) { + T out{}; + const auto decodes = [&out](const bytes& in) { + bytes_view v = in; + return rlp::decode(v, out) && v.empty(); + }; + EXPECT_TRUE(decodes("0x80"_hex)); // canonical zero (empty payload) + EXPECT_EQ(out, T{}); + EXPECT_TRUE(decodes("0x05"_hex)); // canonical small value + EXPECT_EQ(out, T{5}); + EXPECT_FALSE(decodes("0x820005"_hex)); // leading zero byte (non-canonical) + EXPECT_FALSE(decodes("0xc0"_hex)); // a list where a scalar is expected + EXPECT_FALSE(decodes(bytes{})); // empty input: no header + }; + check(uint64_t{}); + check(uint256{}); + + // A payload wider than the destination integer is rejected (the width bound is per type). + { + const auto in = "0x89010000000000000000"_hex; // 9-byte payload: wider than uint64_t. + bytes_view v = in; + uint64_t u = 0; + EXPECT_FALSE(rlp::decode(v, u)); + } + { + bytes in{uint8_t{0xa1}}; // 33-byte payload: wider than uint256. + in.append(33, uint8_t{0x11}); + bytes_view v = in; + uint256 u = 0; + EXPECT_FALSE(rlp::decode(v, u)); + } +} + +TEST(state_rlp_decode, decode_pair_rejects_malformed) +{ + // The access-list entry [address, [storage keys]] pair must be a two-element list. + std::pair> e; + const auto rejected = [&e](const bytes& in) { + bytes_view v = in; + return !rlp::decode(v, e); + }; + EXPECT_TRUE(rejected(bytes{})); // empty input (no header) + EXPECT_TRUE(rejected("0x80"_hex)); // not a list + EXPECT_TRUE(rejected("0xc482aabbc0"_hex)); // first element (address) is not 20 bytes + + auto second_not_list = "0xd694"_hex; // [address, ] + second_not_list.append(20, uint8_t{0x11}); + second_not_list += "0x80"_hex; + EXPECT_TRUE(rejected(second_not_list)); + + auto trailing = "0xd794"_hex; // [address, [], ] + trailing.append(20, uint8_t{0x11}); + trailing += "0xc000"_hex; + EXPECT_TRUE(rejected(trailing)); +} + +TEST(state_rlp_decode, rejects_container_type_mismatch) +{ + { + bytes out; + const auto in = "0xc0"_hex; // a list where a byte string is expected + bytes_view v = in; + EXPECT_FALSE(rlp::decode(v, out)); + } + { + std::vector out; + const auto in = "0x80"_hex; // a string where a list is expected + bytes_view v = in; + EXPECT_FALSE(rlp::decode(v, out)); + } + { + std::vector out; + bytes_view v; // empty input: no list header + EXPECT_FALSE(rlp::decode(v, out)); + } +} + +TEST(state_rlp_decode, vector_unchanged_on_failure) +{ + // Regression: a malformed list element must leave the output vector untouched (no partial + // results). + std::vector out{1, 2, 3}; + const bytes in = "0xc20500"_hex; // list [5, <0x00: non-canonical integer>]. + bytes_view v = in; + EXPECT_FALSE(rlp::decode(v, out)); + EXPECT_EQ(out, (std::vector{1, 2, 3})); +} + +TEST(state_rlp_decode, tx_rejects_gas_limit_over_int64) +{ + // Regression: a wire gas_limit above INT64_MAX must be rejected, not narrowed to a negative + // int64. + auto tx = MINIMAL_LEGACY_TX; + tx.gas_limit = -1; // Encodes as the unsigned wire value 2**64 - 1. + EXPECT_FALSE(state::decode_transaction(rlp::encode(tx)).has_value()); +} + +TEST(state_rlp_decode, tx_rejects_invalid_legacy_v) +{ + // Regression: a legacy signature v that is neither 27/28 nor >= 35 must be rejected, not + // underflowed. + auto tx = MINIMAL_LEGACY_TX; + tx.v = 5; // Invalid: neither pre-155 {27, 28} nor EIP-155 (>= 35). + EXPECT_FALSE(state::decode_transaction(rlp::encode(tx)).has_value()); +} + +TEST(state_rlp_decode, tx_rejects_legacy_v_over_uint64) +{ + // Regression: a legacy v that does not fit uint64 must be rejected, not truncated. This bounds + // the EIP-155 chain id to (2**64 - 36) / 2, the limit the JSON transaction loader also has. + // Hand-crafted legacy tx with v = 2**65 + 35, i.e. chain id 2**64. + const auto rlp = "0xd2808080808080890200000000000000230101"_hex; + EXPECT_FALSE(state::decode_transaction(rlp).has_value()); +} + +TEST(state_rlp_decode, tx_rejects_malformed_envelope) +{ + EXPECT_FALSE(state::decode_transaction({}).has_value()); // Empty input. + EXPECT_FALSE(state::decode_transaction("0x00c0"_hex).has_value()); // Type 0 (t == legacy). + EXPECT_FALSE(state::decode_transaction("0x05c0"_hex).has_value()); // Unknown type 5. + EXPECT_FALSE(state::decode_transaction("0x0280"_hex).has_value()); // Typed body is not a list. + + // The type byte must be rejected on its own, before the body is looked at: the two bodies + // below decode cleanly for the type they are shaped for, so only the type check rejects them. + // Type 0 takes the legacy field order (no chain_id) once past the type check, type 5 the + // EIP-1559 one. + EXPECT_FALSE(state::decode_transaction("0x00ca018080808080801b0102"_hex).has_value()); + EXPECT_FALSE(state::decode_transaction("0x05cc8080808080808080c0800102"_hex).has_value()); + + // The EIP-2718 type byte is a raw byte, not an RLP item; wrapping it as a 1-byte RLP string + // (0x81 0x02, read as type 129, outside the {1..4} range) must be rejected. + auto typed = MINIMAL_LEGACY_TX; + typed.type = state::Transaction::Type::eip1559; + typed.chain_id = 1; + typed.v = 0; + auto wrapped = rlp::encode(typed); + ASSERT_EQ(wrapped[0], 0x02); + wrapped.insert(wrapped.begin(), uint8_t{0x81}); + EXPECT_FALSE(state::decode_transaction(wrapped).has_value()); +} + +TEST(state_rlp_decode, tx_rejects_typed_v_over_1) +{ + // A typed transaction's top-level y_parity must be 0 or 1. + auto tx = MINIMAL_LEGACY_TX; + tx.type = state::Transaction::Type::eip1559; + tx.chain_id = 1; + tx.v = 2; + EXPECT_FALSE(state::decode_transaction(rlp::encode(tx)).has_value()); +} + +TEST(state_rlp_decode, tx_rejects_wrong_length_to) +{ + // A "to" that is neither empty (CREATE) nor exactly 20 bytes must be rejected. + auto rlp = "0xdc80018093"_hex; // Legacy list header, then a 19-byte "to" (prefix 0x93). + rlp.append(19, uint8_t{0x11}); + rlp += "0x80801b0102"_hex; // value, data, v = 27, r, s. + EXPECT_FALSE(state::decode_transaction(rlp).has_value()); +} + +TEST(state_rlp_decode, tx_rejects_truncated_fields) +{ + // A transaction truncated before any required field must be rejected, not read past the RLP + // list. Covers every field-decode-failure return in decode_transaction_body. + using rlp::encode_tuple; + const auto rejected = [](const bytes& tx) { + return !state::decode_transaction(tx).has_value(); + }; + const std::vector l; // Encodes as an empty RLP list (0xc0), used for access_list. + + // Legacy [nonce, gas_price, gas_limit, to, value, data, v, r, s], truncated before each field: + EXPECT_TRUE(rejected("0xc0"_hex)); // nonce + EXPECT_TRUE(rejected(encode_tuple(uint64_t{0}))); // gas_price + EXPECT_TRUE(rejected(encode_tuple(uint64_t{0}, 1_u256))); // gas_limit + EXPECT_TRUE(rejected(encode_tuple(uint64_t{0}, 1_u256, uint64_t{21000}))); // "to" + EXPECT_TRUE( + rejected(encode_tuple(uint64_t{0}, 1_u256, uint64_t{21000}, bytes_view{}))); // value + EXPECT_TRUE(rejected( + encode_tuple(uint64_t{0}, 1_u256, uint64_t{21000}, bytes_view{}, 6_u256))); // data + EXPECT_TRUE(rejected(encode_tuple( + uint64_t{0}, 1_u256, uint64_t{21000}, bytes_view{}, 6_u256, bytes_view{}))); // v + EXPECT_TRUE(rejected("0xf8"_hex)); // malformed legacy list header + + // Typed eip1559 [chain_id, nonce, max_priority, max_fee, gas_limit, to, value, data, + // access_list, y_parity, r, s], truncated before each field: + EXPECT_TRUE(rejected("0x02c0"_hex)); // chain_id + EXPECT_TRUE(rejected("0x02"_hex + encode_tuple(uint64_t{1}))); // nonce + EXPECT_TRUE(rejected("0x02"_hex + encode_tuple(uint64_t{1}, uint64_t{2}))); // max_priority + EXPECT_TRUE( + rejected("0x02"_hex + encode_tuple(uint64_t{1}, uint64_t{2}, 3_u256, 4_u256, uint64_t{5}, + bytes_view{}, 6_u256, bytes_view{}))); // access_list + EXPECT_TRUE( + rejected("0x02"_hex + encode_tuple(uint64_t{1}, uint64_t{2}, 3_u256, 4_u256, uint64_t{5}, + bytes_view{}, 6_u256, bytes_view{}, l))); // y_parity + EXPECT_TRUE(rejected( + "0x02"_hex + encode_tuple(uint64_t{1}, uint64_t{2}, 3_u256, 4_u256, uint64_t{5}, + bytes_view{}, 6_u256, bytes_view{}, l, uint64_t{1}, 7_u256))); // s + EXPECT_TRUE(rejected("0x02"_hex + encode_tuple(uint64_t{1}, uint64_t{2}, 3_u256, 4_u256, + uint64_t{5}, bytes_view{}, 6_u256, bytes_view{}, l, + uint64_t{1}, 7_u256, 8_u256, uint64_t{9}))); // trailing + // element + + // Typed blob, truncated before the blob-gas fields: + EXPECT_TRUE( + rejected("0x03"_hex + encode_tuple(uint64_t{1}, uint64_t{2}, 3_u256, 4_u256, uint64_t{5}, + bytes_view{}, 6_u256, bytes_view{}, l))); // max_fee_per_blob_gas + EXPECT_TRUE(rejected("0x03"_hex + encode_tuple(uint64_t{1}, uint64_t{2}, 3_u256, 4_u256, + uint64_t{5}, bytes_view{}, 6_u256, bytes_view{}, l, + 7_u256))); // blob_versioned_hashes +} + +TEST(state_rlp_decode, tx_rejects_malformed_authorization) +{ + // The EIP-7702 authorization_list and its entries are RLP lists of exactly six fields each. + const auto rejected = [](const bytes& tx) { + return !state::decode_transaction(tx).has_value(); + }; + + // The authorization_list field is a string, not a list. + EXPECT_TRUE(rejected("0x04"_hex + rlp::encode_tuple(uint64_t{1}, uint64_t{2}, 3_u256, 4_u256, + uint64_t{5}, bytes_view{}, 6_u256, bytes_view{}, + std::vector{}, bytes_view{}))); + EXPECT_TRUE(rejected("0x04cb0102030405800680c0c100"_hex)); // an entry is not a list + EXPECT_TRUE(rejected("0x04cc0102030405800680c0c2c101"_hex)); // an entry with too few fields + + // An entry [chain, addr, nonce, y_parity, r] missing its final `s`. + auto missing_s = "0x04e40102030405800680c0dad90194"_hex; + missing_s.append(20, uint8_t{0x22}); // 20-byte authority address. + missing_s += "0x800101"_hex; // nonce, y_parity, r; no s. + EXPECT_TRUE(rejected(missing_s)); + + // An entry [chain, addr, nonce, y_parity, r, s, ] with a trailing element. + auto extra_field = "0x04e60102030405800680c0dcdb0194"_hex; + extra_field.append(20, uint8_t{0x22}); + extra_field += "0x8001010101"_hex; // nonce, y_parity, r, s, extra. + EXPECT_TRUE(rejected(extra_field)); +} + +TEST(state_rlp_decode, decode_authorization_field_positions) +{ + // An authorization tuple [chain_id, addr, nonce, y_parity, r, s] must carry every field: an + // entry truncated before any one of them is rejected. + const auto chain = "0x01"_hex; + const bytes addr = + bytes{uint8_t{0x94}} + bytes(20, uint8_t{0x11}); // RLP of a 20-byte address. + const auto nonce = "0x07"_hex; + const auto y_parity = "0x80"_hex; // 0 + const auto r = "0x03"_hex; + const auto s = "0x04"_hex; + const auto as_list = [](const bytes& payload) { + return bytes{static_cast(rlp::SHORT_LIST_BASE + payload.size())} + payload; + }; + const auto rejected = [](const bytes& entry) { + bytes_view v = entry; + state::Authorization a; + return !state::decode(v, a); + }; + EXPECT_TRUE(rejected(bytes{uint8_t{rlp::SHORT_LIST_BASE}})); // []: chain_id missing + EXPECT_TRUE(rejected(as_list(chain))); // addr missing + EXPECT_TRUE(rejected(as_list(chain + addr))); // nonce missing + EXPECT_TRUE(rejected(as_list(chain + addr + nonce))); // y_parity missing + EXPECT_TRUE(rejected(as_list(chain + addr + nonce + y_parity))); // r missing + EXPECT_TRUE(rejected(as_list(chain + addr + nonce + y_parity + r))); // s missing + EXPECT_TRUE(rejected(as_list(chain + addr + nonce + y_parity + r + s + s))); // trailing field + + const auto entry = as_list(chain + addr + nonce + y_parity + r + s); // complete tuple + bytes_view v = entry; + state::Authorization a; + EXPECT_TRUE(state::decode(v, a)); + EXPECT_TRUE(v.empty()); + EXPECT_EQ(a.nonce, 7u); +} + +TEST(state_rlp_decode, recover_sender_legacy_protected) +{ + // The same fields signed twice: over the pre-EIP-155 preimage and over the EIP-155 one for + // chain 0 (wire v = 35/36). Both decode to chain_id 0, so only the verbatim v says which + // preimage was signed. No EEST fixture signs for chain 0, which is why this is pinned here. + // Signer of both: 0x1d694d5ad94f32132ff5c14c901d3ddbee90a550 (private key 0xa5). evmone only + // recovers, so changing the fields means re-signing each preimage elsewhere, with a low s. + constexpr auto signer = 0x1d694d5ad94f32132ff5c14c901d3ddbee90a550_address; + + const auto protected_tx = + "0xf86807820102825208949232a548dd9e81bac65500b5e0d918f8ba93675c83abcdef84deadbeef23" + "a0d6c3bc8b0fc4456b4687ef74c42a70f0dfd2b2d2575a6f614749685164fe85c2" + "a02264f7f854576e62b8c8415e5f6fdd0ead0876c2b13b8e8d70ddda9239b95f16"_hex; + EXPECT_EQ(recover(protected_tx), signer); + + const auto unprotected_tx = + "0xf86807820102825208949232a548dd9e81bac65500b5e0d918f8ba93675c83abcdef84deadbeef1c" + "a07290c0bb6429493499b400d2912ef585ee39b7c722d086f6de5b175cb495feae" + "a011417a917fff6e40eb2f74cdd22fb15c268c0ee6fd5ad97fc55af15aab9ae27f"_hex; + EXPECT_EQ(recover(unprotected_tx), signer); +} + +TEST(state_rlp_decode, recover_sender_rejects_out_of_range_s) +{ + // s = the curve order is outside [1, secp256k1n) yet a canonical 32-byte integer, so the + // transaction decodes and only the recovery rejects it. + auto tx = MINIMAL_LEGACY_TX; + tx.s = evmmax::secp256k1::Curve::ORDER; + EXPECT_FALSE(recover(rlp::encode(tx)).has_value()); +} + +TEST(state_rlp_decode, recover_sender_rejects_high_s) +{ + // EIP-2 bounds s to the lower half of the curve order, on top of the [1, secp256k1n) range. + // One above the bound differs from the largest accepted s in nothing else. + auto tx = MINIMAL_LEGACY_TX; + tx.s = evmmax::secp256k1::Curve::ORDER / 2; + EXPECT_TRUE(recover(rlp::encode(tx)).has_value()); + + tx.s += 1; + EXPECT_FALSE(recover(rlp::encode(tx)).has_value()); +} diff --git a/evmone/test/unittests/state_system_call_test.cpp b/evmone/test/unittests/state_system_call_test.cpp index 5a02c93..5d597a3 100644 --- a/evmone/test/unittests/state_system_call_test.cpp +++ b/evmone/test/unittests/state_system_call_test.cpp @@ -81,8 +81,8 @@ TEST_F(state_system_call, withdrawal) state[CONSOLIDATION_REQUEST_ADDRESS].code = bytecode{OP_STOP}; const auto r = system_call_block_end(state, block, block_hashes, EVMC_PRAGUE, vm); - ASSERT_TRUE(r.has_value()); - const auto& requests = *r; + ASSERT_TRUE(std::holds_alternative>(r)); + const auto& requests = std::get>(r); EXPECT_FALSE(state.contains(SYSTEM_ADDRESS)); const auto& c = state.at(WITHDRAWAL_REQUEST_ADDRESS); @@ -109,8 +109,8 @@ TEST_F(state_system_call, consolidation) state[WITHDRAWAL_REQUEST_ADDRESS].code = bytecode{OP_STOP}; const auto r = system_call_block_end(state, block, block_hashes, EVMC_PRAGUE, vm); - ASSERT_TRUE(r.has_value()); - const auto& requests = *r; + ASSERT_TRUE(std::holds_alternative>(r)); + const auto& requests = std::get>(r); EXPECT_FALSE(state.contains(SYSTEM_ADDRESS)); const auto& c = state.at(CONSOLIDATION_REQUEST_ADDRESS); diff --git a/evmone/test/unittests/state_transition.cpp b/evmone/test/unittests/state_transition.cpp index 11b5040..e801449 100644 --- a/evmone/test/unittests/state_transition.cpp +++ b/evmone/test/unittests/state_transition.cpp @@ -3,8 +3,10 @@ // SPDX-License-Identifier: Apache-2.0 #include "state_transition.hpp" +#include #include #include +#include #include #include @@ -84,6 +86,21 @@ void state_transition::TearDown() { EXPECT_EQ(receipt.gas_used, *expect.gas_used); } + if (expect.gas_refund.has_value()) + { + EXPECT_EQ(receipt.gas_refund, *expect.gas_refund); + } + if (expect.logs.has_value()) + { + ASSERT_EQ(receipt.logs.size(), expect.logs->size()) << "unexpected number of logs"; + for (size_t i = 0; i < expect.logs->size(); ++i) + { + EXPECT_EQ(receipt.logs[i].addr, (*expect.logs)[i].addr) << "log " << i << " addr"; + EXPECT_EQ(receipt.logs[i].data, (*expect.logs)[i].data) << "log " << i << " data"; + EXPECT_EQ(receipt.logs[i].topics, (*expect.logs)[i].topics) + << "log " << i << " topics"; + } + } // Update default expectations - valid transaction means coinbase exists unless explicitly // requested otherwise if (!expect.post.contains(Coinbase)) @@ -157,4 +174,14 @@ void state_transition::export_state_test( const auto j = to_state_test(export_test_name, block, tx, pre, rev, res, post); std::ofstream{export_file_path} << std::setw(2) << j; } + +Log state_transition::transfer_log( + const address& sender, const address& recipient, const intx::uint256& amount) +{ + static constexpr std::string_view EVENT = "Transfer(address,address,uint256)"; + static const auto topic = + keccak256({reinterpret_cast(EVENT.data()), EVENT.size()}); + return {SYSTEM_ADDRESS, bytes{intx::be::store(amount)}, + {topic, to_bytes32(sender), to_bytes32(recipient)}}; +} } // namespace evmone::test diff --git a/evmone/test/unittests/state_transition.hpp b/evmone/test/unittests/state_transition.hpp index 39b553e..90a263f 100644 --- a/evmone/test/unittests/state_transition.hpp +++ b/evmone/test/unittests/state_transition.hpp @@ -32,6 +32,11 @@ class state_transition : public ExportableFixture /// The default destination address of the test transaction. static constexpr auto To = 0xc0de_address; + /// A second signing account, for tests needing a signature that is not the Sender's + /// (e.g. an EIP-7702 authority). + /// Private key: 0xa5. + static constexpr auto AUTHORITY = 0x1d694d5ad94f32132ff5c14c901d3ddbee90a550_address; + static constexpr auto Coinbase = 0xc014bace_address; static inline evmc::VM vm{evmc_create_evmone()}; @@ -58,6 +63,14 @@ class state_transition : public ExportableFixture /// The expected amount of gas used by the transaction. std::optional gas_used; + /// The expected EIP-7778 block-side gas refund stored on the receipt + /// (`gas_used + gas_refund` equals `max(pre-refund gas, EIP-7623 floor)`). + std::optional gas_refund; + + /// The expected logs emitted by the transaction. When set, the receipt's logs must match + /// exactly: count, address, data, topics, and order. + std::optional> logs; + /// The expected post-execution state. std::unordered_map post; @@ -85,6 +98,7 @@ class state_transition : public ExportableFixture .max_gas_price = block.base_fee + 1, .max_priority_gas_price = block.base_fee + 1, .sender = Sender, + .chain_id = 1, .nonce = 1, }; TestState pre; @@ -95,6 +109,11 @@ class state_transition : public ExportableFixture /// The test runner. void TearDown() override; + /// Build the expected EIP-7708 Transfer log: {SYSTEM_ADDRESS, amount (32-byte big-endian), + /// topics = [Transfer event topic, sender, recipient]}. + static Log transfer_log( + const address& sender, const address& recipient, const intx::uint256& amount); + /// Exports the test in the JSON State Test format to ExportableFixture::export_out. void export_state_test( const std::variant& res, const TestState& post); diff --git a/evmone/test/unittests/state_transition_create_test.cpp b/evmone/test/unittests/state_transition_create_test.cpp index cd0d956..e004e5a 100644 --- a/evmone/test/unittests/state_transition_create_test.cpp +++ b/evmone/test/unittests/state_transition_create_test.cpp @@ -258,6 +258,38 @@ TEST_F(state_transition, create_revert) expect.post[CREATED].exists = false; } +TEST_F(state_transition, create2_prefunded_revert_storage_no_leak) +{ + // Prefunded CREATE2 (create-over-existing path): the init writes storage, the create is + // reverted, then a second CREATE2 at the same address must read slot 0 back as zero. + // TODO: migrate to EEST -- extend test_create2_succeeds_after_reverted_create2 to read storage. + static constexpr auto Creator = 0xcc_address; + static constexpr auto Reverter = 0xbb_address; + + // Init: copy slot 0 to slot 1 (leak detector), write slot 0, deploy 1-byte runtime. + const auto initcode = sstore(1, sload(0)) + sstore(0, 0x99) + ret(0, 1); + const auto creator_code = + mstore(0, push(initcode)) + create2().input(32 - initcode.size(), initcode.size()); + + tx.to = To; + // First attempt reverts (via the reverter sub-call), the second deploys directly. + pre[To] = {.code = call(Reverter).gas(0xffffff) + call(Creator).gas(0xffffff)}; + pre[Reverter] = {.code = call(Creator).gas(0xffffff) + revert(0, 0)}; + pre[Creator] = {.code = creator_code}; + + const auto created = compute_create2_address(Creator, {}, initcode); + pre[created] = {.balance = 1}; // prefunded -> create-over-existing path + + expect.post[To].exists = true; + expect.post[Reverter].exists = true; + expect.post[Creator].exists = true; + expect.post[created].balance = 1; // prefunded balance preserved + expect.post[created].nonce = 1; // post-SD created contract + expect.post[created].code = bytes{0x00}; // second CREATE2 deployed + expect.post[created].storage[0x00_bytes32] = 0x99_bytes32; // second attempt's own write + expect.post[created].storage[0x01_bytes32] = 0x00_bytes32; // slot 0 read back fresh: no leak +} + TEST_F(state_transition, create_revert_sd) { rev = EVMC_SPURIOUS_DRAGON; @@ -362,3 +394,63 @@ TEST_F(state_transition, created_code_hash) expect.post[created].code = runtime_code; expect.post[To].storage[0x00_bytes32] = keccak256(runtime_code); } + +TEST_F(state_transition, create2_rollback_preserves_access_list_slot_warmth) +{ + // Rolling back the first CREATE2 must not cool the slots its address had warmed via the tx + // access list, so the SSTORE in the second attempt still pays the warm price. + rev = EVMC_CANCUN; + + // Initcode reverts on zero CALLVALUE, otherwise stores and deploys nothing. + const auto revert_path = revert(0, 0); + const auto dest = 4 + revert_path.size(); // CALLVALUE + PUSH1 dest + JUMPI + const auto initcode = bytecode{OP_CALLVALUE} + push(dest) + OP_JUMPI + revert_path + + OP_JUMPDEST + sstore(1, 1) + ret(0, 0); + + const auto off = 32 - initcode.size(); + tx.to = To; + pre[To] = {.nonce = 1, + .balance = 1, + .code = mstore(0, push(initcode)) + create2().input(off, initcode.size()) + OP_POP + + create2().value(1).input(off, initcode.size()) + OP_POP}; + + const auto created = compute_create2_address(To, {}, initcode); + tx.access_list = {{created, {0x01_bytes32}}}; + + expect.post[To].nonce = pre[To].nonce + 2; + expect.post[To].balance = 0; // the endowment left the creator + expect.post[created].nonce = 1; + expect.post[created].balance = 1; + expect.post[created].storage[0x01_bytes32] = 0x01_bytes32; + expect.gas_used = 109405; // Cooling the slot would add the 2100 cold surcharge. +} + +TEST_F(state_transition, eip7954_create_tx_at_max_code_size) +{ + // Amsterdam raises the deployed code size limit from 0x6000 to 0x10000 (EIP-7954). + // A create transaction deploying code of exactly the new limit succeeds. + rev = EVMC_AMSTERDAM; + static constexpr auto code_size = 0x10000; // MAX_CODE_SIZE_AMSTERDAM. + tx.gas_limit = 16'000'000; // Covers the ~13.1M code-deposit gas (200/byte). + block.gas_limit = tx.gas_limit; + pre[Sender].balance = tx.gas_limit * tx.max_gas_price; + tx.data = ret(0, code_size); // Init code returns `code_size` zero bytes as the deployed code. + + const auto create_address = compute_create_address(Sender, pre[Sender].nonce); + expect.post[create_address].code = bytes(code_size, 0x00); +} + +TEST_F(state_transition, eip7954_create_tx_above_max_code_size) +{ + // Code one byte above the new 0x10000 limit is still rejected on Amsterdam (EIP-7954). + rev = EVMC_AMSTERDAM; + static constexpr auto code_size = 0x10000 + 1; + tx.gas_limit = 16'000'000; // Enough to deposit the code, so only the limit can reject it. + block.gas_limit = tx.gas_limit; + pre[Sender].balance = tx.gas_limit * tx.max_gas_price; + tx.data = ret(0, code_size); // Init code returns code one byte over the limit. + + const auto create_address = compute_create_address(Sender, pre[Sender].nonce); + expect.status = EVMC_FAILURE; + expect.post[create_address].exists = false; +} diff --git a/evmone/test/unittests/state_transition_eip7702_test.cpp b/evmone/test/unittests/state_transition_eip7702_test.cpp index 0d52e0d..b291f92 100644 --- a/evmone/test/unittests/state_transition_eip7702_test.cpp +++ b/evmone/test/unittests/state_transition_eip7702_test.cpp @@ -4,26 +4,79 @@ #include "../utils/bytecode.hpp" #include "state_transition.hpp" +#include using namespace evmc::literals; +using namespace intx; using namespace evmone::test; +namespace +{ +// Authorization tuples signed by AUTHORITY and Sender with the keys the fixture documents. +// evmone has no ECDSA signer, so the signatures are literals: changing chain_id, addr or nonce +// means re-signing keccak256(0x05 || rlp([chain_id, addr, nonce])). + +constexpr Authorization AUTHORITY_DELEGATION{ + .addr = 0xde1e_address, + .nonce = 0, + .y_parity = 0, + .r = 0x7cb2b4929dfbe4d0fb2aa6f22a5aa484e1f1bf465908045f913a2e6aa0850826_u256, + .s = 0x7741194e3500b9c5bf376a0ca7bfb813555289d035bc13c7aa188052328c0521_u256, +}; + +constexpr Authorization AUTHORITY_DELEGATION_NONCE1{ + .addr = 0xde1e_address, + .nonce = 1, + .y_parity = 1, + .r = 0xa565c4e16dd98f02b76f754c1bad064cf1399948dd1a7ba6bcd861a4c92caf03_u256, + .s = 0x2b6dd08f993e76340c0e9bb41e67fda24eefdbf815ebb38f99876a573a674834_u256, +}; + +constexpr Authorization SENDER_DELEGATION_NONCE2{ + .addr = 0xde1e_address, + .nonce = 2, + .y_parity = 1, + .r = 0xa7a87fd5ac72c2a06ce5de51b42246cc39ee17fd1b9b7fa1600af4b9f200e401_u256, + .s = 0x5e690658827f7efd3415920c55057a50a97db979e53fcc5764e264af2c40ce03_u256, +}; + +// Signed over MAX_NONCE - 1, the last nonce a sender can still authorize from. +constexpr Authorization SENDER_DELEGATION_NONCE_MAX{ + .addr = 0xde1e_address, + .nonce = MAX_NONCE - 1, + .y_parity = 1, + .r = 0x091b7ea090552d9b423479ff48bab00ddc4c87e7d610c6fa0dfe7a57019dcde2_u256, + .s = 0x7a5c56d5724cdc6b49f8d39b1dda26bf9a660745e2eeba6bd05c27d56d030fca_u256, +}; + +// The CREATE2 address eip7702_set_code_transaction_with_selfdestruct deploys; that test asserts +// the deployment still lands here, because the authorization below is signed over it. +constexpr auto SELFDESTRUCTING_DELEGATE = 0x917e75ff40e354f8d10ed4456ae7e365e7ee7912_address; + +constexpr Authorization AUTHORITY_DELEGATION_TO_SELFDESTRUCTING{ + .addr = SELFDESTRUCTING_DELEGATE, + .nonce = 0, + .y_parity = 0, + .r = 0xe26a6ca88e50d3559040a6962e36eccf4577388da303f5f85664c0c103b7d3fa_u256, + .s = 0x7ed6094cbae06609e21276851cf382befb3ee9d91ae402ef5dc9169343cab8c3_u256, +}; +} // namespace + TEST_F(state_transition, eip7702_set_code_transaction) { rev = EVMC_PRAGUE; - constexpr auto authority = 0xca11ee_address; constexpr auto delegate = 0xde1e_address; pre[delegate] = {.code = bytecode{OP_STOP}}; tx.to = To; tx.type = Transaction::Type::set_code; - tx.authorization_list = {{.addr = delegate, .nonce = 0, .signer = authority}}; + tx.authorization_list = {AUTHORITY_DELEGATION}; pre[To] = {.code = ret(0)}; expect.post[To].exists = true; expect.post[delegate].exists = true; - expect.post[authority].nonce = 1; - expect.post[authority].code = bytes{0xef, 0x01, 0x00} + hex(delegate); + expect.post[AUTHORITY].nonce = 1; + expect.post[AUTHORITY].code = bytes{0xef, 0x01, 0x00} + hex(delegate); } TEST_F(state_transition, eip7702_set_code_transaction_authority_is_sender) @@ -35,7 +88,7 @@ TEST_F(state_transition, eip7702_set_code_transaction_authority_is_sender) tx.to = To; tx.type = Transaction::Type::set_code; // Sender nonce is 1 in prestate, it is bumped once for tx and then another time for delegation - tx.authorization_list = {{.addr = delegate, .nonce = 2, .signer = Sender}}; + tx.authorization_list = {SENDER_DELEGATION_NONCE2}; pre[To] = {.code = ret(0)}; expect.post[Sender].nonce = 3; @@ -44,36 +97,79 @@ TEST_F(state_transition, eip7702_set_code_transaction_authority_is_sender) expect.post[delegate].exists = true; } +TEST_F(state_transition, eip7702_set_code_self_authorization_reaching_nonce_max) +{ + // A self-authorization that bumps the sender nonce to MAX_NONCE (2^64-1) is valid: only a + // tx nonce == MAX_NONCE is rejected by EIP-2681, not reaching it during execution. + rev = EVMC_PRAGUE; + + constexpr auto delegate = 0xde1e_address; + + // The tx bumps the sender nonce 2^64-3 -> 2^64-2, then the self-authorization -> 2^64-1. + pre[Sender].nonce = MAX_NONCE - 2; + tx.nonce = MAX_NONCE - 2; + tx.to = To; + tx.type = Transaction::Type::set_code; + tx.authorization_list = {SENDER_DELEGATION_NONCE_MAX}; + pre[To] = {.code = sstore(0, 1)}; + + expect.status = EVMC_SUCCESS; + expect.post[Sender].nonce = MAX_NONCE; + expect.post[Sender].code = bytes{0xef, 0x01, 0x00} + hex(delegate); + expect.post[To].storage[0x00_bytes32] = 0x01_bytes32; // Proves the top-level call executed. +} + TEST_F(state_transition, eip7702_set_code_transaction_authority_is_to) { rev = EVMC_PRAGUE; constexpr auto delegate = 0xde1e_address; pre[delegate] = {.code = bytecode{OP_STOP}}; - tx.to = To; + tx.to = AUTHORITY; // The authority is also the transaction destination. tx.type = Transaction::Type::set_code; - tx.authorization_list = {{.addr = delegate, .nonce = 0, .signer = To}}; + tx.authorization_list = {AUTHORITY_DELEGATION}; expect.post[delegate].exists = true; - expect.post[To].nonce = pre[To].nonce + 1; - expect.post[To].code = bytes{0xef, 0x01, 0x00} + hex(delegate); + expect.post[AUTHORITY].nonce = pre[AUTHORITY].nonce + 1; + expect.post[AUTHORITY].code = bytes{0xef, 0x01, 0x00} + hex(delegate); } TEST_F(state_transition, eip7702_set_code_transaction_invalid_y_parity) { rev = EVMC_PRAGUE; - constexpr auto authority = 0xca11ee_address; constexpr auto delegate = 0xde1e_address; - pre[authority] = {.nonce = 1}; + pre[AUTHORITY] = {.nonce = 1}; + tx.to = To; + tx.type = Transaction::Type::set_code; + auto auth = AUTHORITY_DELEGATION_NONCE1; + auth.y_parity = 2; // Corrupt the y_parity of an otherwise valid signature. + tx.authorization_list = {auth}; + pre[To] = {.code = ret(0)}; + + expect.post[AUTHORITY].nonce = 1; + expect.post[AUTHORITY].code = bytes{}; + expect.post[To].exists = true; + expect.post[delegate].exists = false; +} + +TEST_F(state_transition, eip7702_set_code_transaction_unrecoverable_signature) +{ + // An authorization with no recoverable authority must be skipped, leaving no account behind: + // https://github.com/ipsilon/evmone/issues/1483. + rev = EVMC_PRAGUE; + + constexpr auto delegate = 0xde1e_address; tx.to = To; tx.type = Transaction::Type::set_code; - tx.authorization_list = {{.addr = delegate, .nonce = 1, .signer = authority, .v = 2}}; + auto auth = AUTHORITY_DELEGATION; + auth.r = 0; + auth.s = 0; + tx.authorization_list = {auth}; pre[To] = {.code = ret(0)}; - expect.post[authority].nonce = 1; - expect.post[authority].code = bytes{}; expect.post[To].exists = true; + expect.post[AUTHORITY].exists = false; expect.post[delegate].exists = false; } @@ -283,13 +379,14 @@ TEST_F(state_transition, eip7702_selfdestruct) TEST_F(state_transition, eip7702_set_code_transaction_with_selfdestruct) { rev = EVMC_PRAGUE; - constexpr auto callee = 0xca11ee_address; + const auto callee = AUTHORITY; // The delegation is installed on the called account. constexpr bytes32 salt{0xff}; const auto deploy_code = bytecode{selfdestruct(0x00_address)}; const auto initcode = mstore(0, push(deploy_code)) + ret(32 - deploy_code.size(), deploy_code.size()); const auto deployed_address = compute_create2_address(To, salt, initcode); + ASSERT_EQ(deployed_address, SELFDESTRUCTING_DELEGATE); pre[To].code = mstore(0, push(initcode)) + sstore(0, create2().input(32 - initcode.size(), initcode.size()).salt(salt)) + @@ -297,7 +394,7 @@ TEST_F(state_transition, eip7702_set_code_transaction_with_selfdestruct) tx.to = To; tx.type = Transaction::Type::set_code; - tx.authorization_list = {{.addr = deployed_address, .nonce = 0, .signer = callee}}; + tx.authorization_list = {AUTHORITY_DELEGATION_TO_SELFDESTRUCTING}; expect.post[deployed_address].code = deploy_code; expect.post[To].storage[0x00_bytes32] = to_bytes32(deployed_address); diff --git a/evmone/test/unittests/state_transition_eip7778_block_gas_test.cpp b/evmone/test/unittests/state_transition_eip7778_block_gas_test.cpp new file mode 100644 index 0000000..4ff8c54 --- /dev/null +++ b/evmone/test/unittests/state_transition_eip7778_block_gas_test.cpp @@ -0,0 +1,26 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2026 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "state_transition.hpp" +#include + +using namespace evmc::literals; +using namespace evmone::test; + +TEST_F(state_transition, eip7778_sstore_clear_refund_amsterdam) +{ + // EIP-7778: a clearing SSTORE produces a 4800 refund. The receipt exposes + // it as `gas_refund` so the block accumulates `gas_used + gas_refund` + // (the pre-refund gas), independent of what the user pays. + rev = EVMC_AMSTERDAM; + tx.to = To; + pre[To] = {.storage = {{0x01_bytes32, 0x42_bytes32}}, .code = sstore(1, 0)}; + + // Pre-refund: 21000 intrinsic + 5000 (cold SSTORE reset) + 6 (two PUSHes) = 26006. + // London-schedule clear refund 4800 ≤ cap 26006/5, so the full refund applies. + expect.gas_used = 26006 - 4800; + expect.gas_refund = 4800; + expect.post[To].exists = true; + expect.post[To].storage[0x01_bytes32] = 0x00_bytes32; +} diff --git a/evmone/test/unittests/state_transition_selfdestruct_test.cpp b/evmone/test/unittests/state_transition_selfdestruct_test.cpp index 5baf4ab..83c54f8 100644 --- a/evmone/test/unittests/state_transition_selfdestruct_test.cpp +++ b/evmone/test/unittests/state_transition_selfdestruct_test.cpp @@ -108,7 +108,94 @@ TEST_F(state_transition, selfdestruct_double_revert) TEST_F(state_transition, selfdestruct_initcode) { + rev = EVMC_SHANGHAI; + tx.data = selfdestruct(0xbe_address); + + expect.post[compute_create_address(tx.sender, tx.nonce)].exists = false; + expect.post[0xbe_address].exists = false; +} + +TEST_F(state_transition, selfdestruct_initcode_amsterdam) +{ + // A same-tx-created account that self-destructs ending with a zero balance must not be in the + // final state (EIP-8246). In this test we use initcode. + rev = EVMC_AMSTERDAM; tx.data = selfdestruct(0xbe_address); + + expect.post[compute_create_address(tx.sender, tx.nonce)].exists = false; + expect.post[0xbe_address].exists = false; +} + +TEST_F(state_transition, selfdestruct_prefunded) +{ + // Although burn is removed in EIP-8246, the deletion of a pre-funded account still happens. + rev = EVMC_CANCUN; + const auto created = compute_create_address(tx.sender, tx.nonce); + pre[created] = {.balance = 1}; + tx.data = selfdestruct(0xbe_address); // Transfer to distinct beneficiary. + + expect.post[created].exists = false; // Removed, despite pre-existing in the state. + expect.post[0xbe_address].balance = 1; // Pre-funded balance delivered to the beneficiary. +} + +TEST_F(state_transition, selfdestruct_prefunded_amsterdam) +{ + // Although burn is removed in EIP-8246, the deletion of a pre-funded account still happens. + rev = EVMC_AMSTERDAM; + const auto created = compute_create_address(tx.sender, tx.nonce); + pre[created] = {.balance = 1}; + tx.data = selfdestruct(0xbe_address); // Transfer to distinct beneficiary. + + expect.post[created].exists = false; // Removed, despite pre-existing in the state. + expect.post[0xbe_address].balance = 1; // Pre-funded balance delivered to the beneficiary. +} + +TEST_F(state_transition, selfdestruct_prefunded_burn) +{ + // Burn pre-funded ETH by self-destruct to self. + rev = EVMC_CANCUN; + const auto created = compute_create_address(tx.sender, tx.nonce); + pre[created] = {.balance = 1}; + tx.data = selfdestruct(created); + + expect.post[created].exists = false; // Removed, despite pre-existing in the state. +} + +TEST_F(state_transition, selfdestruct_prefunded_burn_amsterdam) +{ + // Burn is removed with EIP-8246, the balance must be preserved. + rev = EVMC_AMSTERDAM; + const auto created = compute_create_address(tx.sender, tx.nonce); + pre[created] = {.balance = 1}; + tx.data = selfdestruct(created); + + expect.post[created].balance = 1; // Balance preserved. + expect.post[created].nonce = 0; + expect.post[created].code = {}; +} + +TEST_F(state_transition, selfdestruct_sibling_create_then_destruct_amsterdam) +{ + // A contract created in one sub-call and self-destructed in a sibling sub-call of the same + // transaction must still be removed (especially its code). + rev = EVMC_AMSTERDAM; + static constexpr auto F = 0xfac0_address; // Factory address. + + const auto runtime = selfdestruct(0xbe_address); + const auto initcode = mstore(0, push(runtime)) + ret(32 - runtime.size(), runtime.size()); + const auto created = compute_create_address(F, 1); + + pre[F] = {.nonce = 1, + .balance = 1, + .code = mstore(0, push(initcode)) + + create().input(32 - initcode.size(), initcode.size()).value(1)}; + pre[To] = {.code = call(F).gas(0xffffff) + call(created).gas(0xffffff)}; + tx.to = To; + + expect.post[created].exists = false; // Created and destructed in the same tx -> removed. + expect.post[0xbe_address].balance = 1; // Funds delivered to the beneficiary. + expect.post[F] = {.nonce = 2, .balance = 0}; // F created one contract and sent it the balance. + expect.post[To] = {}; } TEST_F(state_transition, massdestruct_shanghai) @@ -169,3 +256,33 @@ TEST_F(state_transition, massdestruct_cancun) expect.post[SINK].balance = N; } + +TEST_F(state_transition, eip7708_transfer_log_selfdestruct_existing) +{ + // A pre-existing contract self-destructing to a distinct beneficiary emits an ETH transfer log + // for the moved balance (EIP-7708). + rev = EVMC_AMSTERDAM; + static constexpr auto Beneficiary = 0xbe_address; + tx.to = To; + pre[To] = {.balance = 0x99, .code = selfdestruct(Beneficiary)}; + + expect.post[To] = {}; // EIP-6780: survives, balance moved out. + expect.post[Beneficiary].balance = 0x99; + expect.logs = {transfer_log(To, Beneficiary, 0x99)}; +} + +TEST_F(state_transition, eip7708_transfer_log_create_tx_then_selfdestruct) +{ + // A CREATE-transaction endowment emits an ETH transfer log, then the same-tx-created account + // self-destructing in its initcode emits a second ETH transfer log (EIP-7708). + rev = EVMC_AMSTERDAM; + static constexpr auto Beneficiary = 0xbe_address; + tx.value = 0x99; + tx.data = selfdestruct(Beneficiary); + pre[Sender].balance += 0x99; + const auto created = compute_create_address(Sender, tx.nonce); + + expect.post[created].exists = false; + expect.post[Beneficiary].balance = 0x99; + expect.logs = {transfer_log(Sender, created, 0x99), transfer_log(created, Beneficiary, 0x99)}; +} diff --git a/evmone/test/unittests/state_transition_touch_test.cpp b/evmone/test/unittests/state_transition_touch_test.cpp index 953fad5..0bac989 100644 --- a/evmone/test/unittests/state_transition_touch_test.cpp +++ b/evmone/test/unittests/state_transition_touch_test.cpp @@ -225,3 +225,85 @@ TEST_F(state_transition, touch_revert_selfdestruct_to_nonexistient_tw) expect.post[DESTRUCTOR].exists = true; expect.post[BENEFICIARY].exists = false; } + +TEST_F(state_transition, touch_revert_ripemd_frontier) +{ + // Before Spurious Dragon the 0x03 quirk is off: the failed call's touch of 0x03 is reverted and + // 0x03 does not linger. Guards the >= EVMC_SPURIOUS_DRAGON lower bound. + rev = EVMC_FRONTIER; + block.base_fee = 0; + tx.type = Transaction::Type::legacy; + tx.to = To; + pre[*tx.to] = {.code = call(0x03_address)}; // gas = 0 -> RIPEMD out-of-gas -> failed call + + expect.post[*tx.to].exists = true; + expect.post[0x03_address].exists = false; +} + +TEST_F(state_transition, touch_revert_ripemd_london) +{ + // In range the quirk keeps the touch, so a pre-existing empty 0x03 leaf is swept by EIP-161 + // even though the touching call reverted. Guards that the quirk stays active up to the Merge. + rev = EVMC_LONDON; + block.base_fee = 0; + tx.type = Transaction::Type::legacy; + tx.to = To; + pre[*tx.to] = {.code = call(0x03_address)}; // gas = 0 -> RIPEMD out-of-gas -> failed call + pre[0x03_address] = {}; // pre-existing empty leaf + + expect.post[*tx.to].exists = true; + expect.post[0x03_address].exists = false; // deleted by the retained touch +} + +// A storage-only account (nonce 0, balance 0, no code) is empty per EIP-158, so it is eligible for +// the end-of-tx sweep, but only a genuine touch may trigger it. Constructing one in the pre-state +// needs a fork before EIP-7523. + +TEST_F(state_transition, touch_access_list_storage_only) +{ + // Warming via the access list is not a touch. + rev = EVMC_LONDON; + static constexpr auto STORAGE_ONLY = 0x5a_address; + + tx.to = To; + tx.access_list = {{STORAGE_ONLY, {}}}; + pre[*tx.to] = {.code = bytecode{OP_STOP}}; + pre[STORAGE_ONLY] = {.storage = {{0x01_bytes32, 0x01_bytes32}}}; + + expect.post[*tx.to].exists = true; + expect.post[STORAGE_ONLY].exists = true; + expect.post[STORAGE_ONLY].storage[0x01_bytes32] = 0x01_bytes32; +} + +TEST_F(state_transition, touch_balance_storage_only) +{ + // BALANCE loads the account, where the access list above only warms it. + rev = EVMC_LONDON; + static constexpr auto STORAGE_ONLY = 0x5a_address; + + tx.to = To; + pre[*tx.to] = {.code = push(STORAGE_ONLY) + OP_BALANCE + OP_POP}; + pre[STORAGE_ONLY] = {.storage = {{0x01_bytes32, 0x01_bytes32}}}; + + expect.post[*tx.to].exists = true; + expect.post[STORAGE_ONLY].exists = true; + expect.post[STORAGE_ONLY].storage[0x01_bytes32] = 0x01_bytes32; +} + +TEST_F(state_transition, touch_revert_storage_only) +{ + // The rollback must undo the touched flag, or the account is swept despite the revert. + rev = EVMC_ISTANBUL; // Berlin's account access would journal the flag on its own. + block.base_fee = 0; + static constexpr auto STORAGE_ONLY = 0x5a_address; + + tx.type = Transaction::Type::legacy; + tx.to = To; + pre[*tx.to] = {.code = call(STORAGE_ONLY) + revert(0, 0)}; + pre[STORAGE_ONLY] = {.storage = {{0x01_bytes32, 0x01_bytes32}}}; + + expect.status = EVMC_REVERT; + expect.post[*tx.to].exists = true; + expect.post[STORAGE_ONLY].exists = true; + expect.post[STORAGE_ONLY].storage[0x01_bytes32] = 0x01_bytes32; +} diff --git a/evmone/test/unittests/state_transition_tx_test.cpp b/evmone/test/unittests/state_transition_tx_test.cpp index a8c551d..b0d9d6a 100644 --- a/evmone/test/unittests/state_transition_tx_test.cpp +++ b/evmone/test/unittests/state_transition_tx_test.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "state_transition.hpp" +#include #include using namespace evmc::literals; @@ -45,10 +46,48 @@ TEST_F(state_transition, invalid_tx_non_existing_sender) tx.nonce = 0; pre.erase(Sender); - expect.tx_error = INSUFFICIENT_FUNDS; + expect.tx_error = INSUFFICIENT_ACCOUNT_FUNDS; expect.post[Sender].exists = false; } +TEST_F(state_transition, invalid_tx_wrong_chain_id) +{ + tx.to = To; + tx.chain_id = 2; // Mismatches the block chain id (1). + expect.tx_error = INVALID_CHAIN_ID; +} + +TEST_F(state_transition, invalid_tx_wrong_chain_id_legacy) +{ + tx.type = Transaction::Type::legacy; + tx.to = To; + tx.chain_id = 2; // Mismatches the block chain id (1). + tx.v = 35 + 2 * tx.chain_id; + expect.tx_error = INVALID_CHAIN_ID; +} + +TEST_F(state_transition, invalid_tx_legacy_protected_chain_id_0) +{ + // A legacy transaction signed for chain 0 (EIP-155) is bound to it like any other, unlike an + // unprotected one. No EEST fixture signs for chain 0, which is why this is pinned here. + tx.type = Transaction::Type::legacy; + tx.to = To; + tx.chain_id = 0; + tx.v = 35; + expect.tx_error = INVALID_CHAIN_ID; +} + +TEST_F(state_transition, tx_legacy_unprotected_chain_id) +{ + rev = EVMC_ISTANBUL; + block.base_fee = 0; // should be 0 before London + tx.type = Transaction::Type::legacy; + tx.to = To; + tx.chain_id = 0; // Unprotected legacy tx is valid on any chain (pre-EIP-155). + + expect.post.at(Sender).nonce = pre[Sender].nonce + 1; +} + TEST_F(state_transition, tx_blob_gas_price) { rev = EVMC_CANCUN; @@ -150,3 +189,180 @@ TEST_F(state_transition, tx_data_min_cost_exec_51) expect.gas_used = 21000 + MIN_GAS + 1; expect.post[To].exists = true; } + +TEST_F(state_transition, tx_data_floor_amsterdam_exec_0) +{ + // EIP-7976: the floor is 64 gas per calldata byte. Execution gas is 0. + rev = EVMC_AMSTERDAM; + tx.to = To; + tx.data = "0001"_hex; + static constexpr auto MIN_GAS = 64 * 2; + + expect.gas_used = 21000 + MIN_GAS; +} + +TEST_F(state_transition, tx_data_floor_amsterdam_exec_below_floor) +{ + // EIP-7976: standard cost (intrinsic data + execution) is 1 below the floor. + rev = EVMC_AMSTERDAM; + tx.to = To; + tx.data = "0001"_hex; + static constexpr auto DATA_GAS = 16 + 4; + static constexpr auto MIN_GAS = 64 * 2; + + pre[To] = {.code = (MIN_GAS - DATA_GAS - 1) * OP_JUMPDEST}; + expect.gas_used = 21000 + MIN_GAS; + expect.post[To].exists = true; +} + +TEST_F(state_transition, tx_data_floor_amsterdam_exec_at_floor) +{ + // EIP-7976: standard cost (intrinsic data + execution) equals the floor. + rev = EVMC_AMSTERDAM; + tx.to = To; + tx.data = "0001"_hex; + static constexpr auto DATA_GAS = 16 + 4; + static constexpr auto MIN_GAS = 64 * 2; + + pre[To] = {.code = (MIN_GAS - DATA_GAS) * OP_JUMPDEST}; + expect.gas_used = 21000 + MIN_GAS; + expect.post[To].exists = true; +} + +TEST_F(state_transition, tx_data_floor_amsterdam_exec_above_floor) +{ + // EIP-7976: standard cost (intrinsic data + execution) is 1 above the floor. + rev = EVMC_AMSTERDAM; + tx.to = To; + tx.data = "0001"_hex; + static constexpr auto DATA_GAS = 16 + 4; + static constexpr auto MIN_GAS = 64 * 2; + + pre[To] = {.code = (MIN_GAS - DATA_GAS + 1) * OP_JUMPDEST}; + expect.gas_used = 21000 + MIN_GAS + 1; + expect.post[To].exists = true; +} + +TEST_F(state_transition, tx_data_floor_amsterdam_zero_bytes) +{ + // EIP-7976: zero bytes pay the same 64-gas floor as nonzero bytes. + rev = EVMC_AMSTERDAM; + tx.to = To; + tx.data = "0000"_hex; + static constexpr auto MIN_GAS = 64 * 2; + + expect.gas_used = 21000 + MIN_GAS; +} + +TEST_F(state_transition, tx_data_floor_osaka_uses_eip7623) +{ + // EIP-7976 is not yet active in Osaka; the EIP-7623 floor (10 gas per token) still applies. + rev = EVMC_OSAKA; + tx.to = To; + tx.data = "0001"_hex; // tokens = 4 (nonzero) + 1 (zero) = 5 + static constexpr auto MIN_GAS = 10 * 5; + + expect.gas_used = 21000 + MIN_GAS; +} + +TEST_F(state_transition, access_list_cost_amsterdam) +{ + // EIP-7981: 1280 gas (64*20) per address, 2048 gas (64*32) per storage key. + rev = EVMC_AMSTERDAM; + tx.to = To; + tx.access_list = {{To, {0x01_bytes32}}}; + // intrinsic = 21000 + 2400 + 1900 + 1280 + 2048 = 28628 + expect.gas_used = 28628; +} + +TEST_F(state_transition, access_list_cost_osaka_unchanged) +{ + // EIP-7981 is inactive before Amsterdam. + rev = EVMC_OSAKA; + tx.to = To; + tx.access_list = {{To, {0x01_bytes32}}}; + // intrinsic = 21000 + 2400 + 1900 = 25300 + expect.gas_used = 25300; +} + +TEST_F(state_transition, access_list_precompile_with_storage_keys) +{ + // An access list may name a precompile with storage keys (EIP-2930). Intrinsic gas is charged + // for both, though access_account() creates no state entry and the key warming is skipped. + rev = EVMC_OSAKA; + tx.to = To; + tx.access_list = {{0x01_address, {0x01_bytes32}}}; + // intrinsic = 21000 + 2400 + 1900 = 25300 + expect.gas_used = 25300; +} + +TEST_F(state_transition, access_list_floor_amsterdam) +{ + // EIP-7981: access-list bytes count toward the floor. + rev = EVMC_AMSTERDAM; + tx.to = To; + tx.data = bytes(100, 0x00); + tx.access_list = {{To, {}}}; + // intrinsic = 21000 + 100*4 + 2400 + 1280 = 25080 + // floor = 21000 + 64*(100 + 20) = 28680 (dominates) + expect.gas_used = 28680; +} + +TEST_F(state_transition, invalid_access_list_amsterdam_gas_limit_below_floor) +{ + // EIP-7981: gas limit must cover the floor (28680) — pre-7981 intrinsic (23800) is not enough. + rev = EVMC_AMSTERDAM; + tx.to = To; + tx.data = bytes(100, 0x00); + tx.access_list = {{To, {}}}; + tx.gas_limit = 28679; + expect.tx_error = INTRINSIC_GAS_TOO_LOW; +} + +TEST_F(state_transition, tx_at_sender_nonce_max_minus_1_call) +{ + // Regression: a top-level CALL tx must execute normally when the sender nonce is MAX_NONCE - 1 + // (2^64-2). Only nonce == MAX_NONCE (2^64-1) is invalid per EIP-2681. + tx.to = To; + pre[Sender].nonce = MAX_NONCE - 1; + tx.nonce = MAX_NONCE - 1; + + expect.status = EVMC_SUCCESS; + expect.post.at(Sender).nonce = MAX_NONCE; +} + +TEST_F(state_transition, tx_at_sender_nonce_max_minus_1_create) +{ + // Regression: a top-level CREATE tx must execute normally when the sender nonce is + // MAX_NONCE - 1 (2^64-2). Only nonce == MAX_NONCE (2^64-1) is invalid per EIP-2681. + pre[Sender].nonce = MAX_NONCE - 1; + tx.nonce = MAX_NONCE - 1; + + expect.status = EVMC_SUCCESS; + expect.post.at(Sender).nonce = MAX_NONCE; + expect.post[compute_create_address(Sender, MAX_NONCE - 1)] = {.nonce = 1, .code = bytes{}}; +} + +TEST_F(state_transition, tx_emits_log) +{ + // Smoke test for the `expect.logs` mechanism: assert a log with data and a topic from LOG1. + static constexpr auto TOPIC = 0xaa_bytes32; + tx.to = To; + // Store 0xaabbccdd at mem[28..31], then LOG1(offset=28, size=4, TOPIC) over those bytes. + pre[To] = {.code = mstore(0, 0xaabbccdd) + push(TOPIC) + push(4) + push(28) + OP_LOG1}; + + expect.post[To] = {}; // the contract survives (has code). + expect.logs = {Log{To, bytes{0xaa, 0xbb, 0xcc, 0xdd}, {TOPIC}}}; +} + +TEST_F(state_transition, eip7708_transfer_log_tx_value) +{ + // Top level transaction with value emits log (EIP-7708). + rev = EVMC_AMSTERDAM; + tx.to = To; + tx.value = 0x12345; + pre[Sender].balance += 0x12345; + + expect.post[To].balance = 0x12345; + expect.logs = {transfer_log(Sender, To, 0x12345)}; +} diff --git a/evmone/test/unittests/state_tx_test.cpp b/evmone/test/unittests/state_tx_test.cpp index b0da8ac..1db2b90 100644 --- a/evmone/test/unittests/state_tx_test.cpp +++ b/evmone/test/unittests/state_tx_test.cpp @@ -32,13 +32,13 @@ TEST(state_tx, validate_nonce) EXPECT_EQ(std::get( validate_transaction(state, block, tx, EVMC_BERLIN, block.gas_limit, 0)) .message(), - "nonce too low"); + "TransactionException.NONCE_MISMATCH_TOO_LOW"); tx.nonce = 2; EXPECT_EQ(std::get( validate_transaction(state, block, tx, EVMC_BERLIN, block.gas_limit, 0)) .message(), - "nonce too high"); + "TransactionException.NONCE_MISMATCH_TOO_HIGH"); } TEST(state_tx, validate_sender) @@ -61,14 +61,14 @@ TEST(state_tx, validate_sender) EXPECT_EQ(std::get( validate_transaction(state, block, tx, EVMC_LONDON, block.gas_limit, 0)) .message(), - "max fee per gas less than block base fee"); + "TransactionException.INSUFFICIENT_MAX_FEE_PER_GAS"); tx.max_gas_price = block.base_fee; EXPECT_EQ(std::get( validate_transaction(state, block, tx, EVMC_LONDON, block.gas_limit, 0)) .message(), - "insufficient funds for gas * price + value"); + "TransactionException.INSUFFICIENT_ACCOUNT_FUNDS"); } TEST(state_tx, validate_blob_tx) @@ -85,6 +85,7 @@ TEST(state_tx, validate_blob_tx) .gas_limit = 60000, .max_gas_price = block.base_fee, .sender = 0x02_address, + .chain_id = 1, }; const TestState state{{tx.sender, {.balance = 1'000'000}}}; @@ -92,7 +93,7 @@ TEST(state_tx, validate_blob_tx) static_cast(max_blob_gas_per_block(get_blob_params(EVMC_CANCUN))); EXPECT_EQ(std::get(validate_transaction( state, block, tx, EVMC_SHANGHAI, block.gas_limit, blob_gas_limit)), - make_error_code(ErrorCode::TX_TYPE_NOT_SUPPORTED)); + make_error_code(ErrorCode::TYPE_NOT_SUPPORTED)); EXPECT_EQ(std::get(validate_transaction(state, block, tx, EVMC_CANCUN, block.gas_limit, blob_gas_limit)) @@ -116,8 +117,8 @@ TEST(state_tx, validate_blob_tx) validate_transaction(state, block, tx, EVMC_CANCUN, block.gas_limit, g)); }; - EXPECT_EQ( - expect_error(blob_gas_limit), make_error_code(ErrorCode::BLOB_FEE_CAP_LESS_THAN_BLOCKS)); + EXPECT_EQ(expect_error(blob_gas_limit), + make_error_code(ErrorCode::INSUFFICIENT_MAX_FEE_PER_BLOB_GAS)); tx.max_blob_gas_price = 1; tx.blob_hashes.push_back( @@ -139,24 +140,26 @@ TEST(state_tx, validate_blob_tx) TEST(state_tx, validate_eof_create_transaction) { + // Check if a create-tx with EOF initcode is valid. + const BlockInfo block{ .gas_limit = 1'000'000, }; const Transaction tx{ .data = "EF00 01 010004 0200010001 030004 00 00000000 00 AABBCCDD"_hex, - .gas_limit = 60000, + .gas_limit = block.gas_limit, .sender = 0x02_address, .to = {}, .nonce = 1, }; const TestState state{{tx.sender, {.nonce = 1, .balance = 1'000'000}}}; - EXPECT_FALSE(holds_alternative( - validate_transaction(state, block, tx, EVMC_CANCUN, 60000, 0))); - EXPECT_FALSE(holds_alternative( - validate_transaction(state, block, tx, EVMC_PRAGUE, 60000, 0))); - EXPECT_FALSE(holds_alternative( - validate_transaction(state, block, tx, EVMC_EXPERIMENTAL, 60000, 0))); + for (int r = EVMC_CANCUN; r <= EVMC_MAX_REVISION; ++r) + { + const auto rev = static_cast(r); + const auto res = validate_transaction(state, block, tx, rev, block.gas_limit, 0); + EXPECT_FALSE(holds_alternative(res)); + } } TEST(state_tx, validate_tx_data_cost) @@ -214,6 +217,7 @@ TEST(state_tx, max_blob_count) .max_blob_gas_price = 1, .sender = 0x02_address, .to = 0x01_address, + .chain_id = 1, }; const TestState state{{tx.sender, {.balance = 1'000'000}}}; const auto blob_gas_limit = @@ -248,5 +252,5 @@ TEST(state_tx, max_gas_limit_exceeded) EXPECT_EQ(std::get( validate_transaction(state, block, tx, EVMC_OSAKA, block.gas_limit, 0)), - make_error_code(ErrorCode::MAX_GAS_LIMIT_EXCEEDED)); + make_error_code(ErrorCode::GAS_LIMIT_EXCEEDS_MAXIMUM)); } diff --git a/evmone/test/unittests/statetest_loader_block_info_test.cpp b/evmone/test/unittests/statetest_loader_block_info_test.cpp index c5588c5..d5efc47 100644 --- a/evmone/test/unittests/statetest_loader_block_info_test.cpp +++ b/evmone/test/unittests/statetest_loader_block_info_test.cpp @@ -299,6 +299,32 @@ TEST(statetest_loader, block_info_current_blob_gas) EXPECT_EQ(bi.excess_blob_gas, 2); } +TEST(statetest_loader, block_info_blob_gas_used) +{ + constexpr std::string_view absent_input = R"({ + "currentNumber": "0", + "currentTimestamp": "0", + "currentGasLimit": "0", + "currentCoinbase": "" + })"; + constexpr std::string_view zero_input = R"({ + "currentNumber": "0", + "currentTimestamp": "0", + "currentGasLimit": "0", + "currentCoinbase": "", + "blobGasUsed": "0" + })"; + + const auto blob_params = test::get_blob_params(EVMC_CANCUN); + + // An absent "blobGasUsed" must leave the optional disengaged, not engage it with zero. + EXPECT_FALSE(test::from_json_with_rev(json::json::parse(absent_input), EVMC_CANCUN, blob_params) + .blob_gas_used.has_value()); + EXPECT_EQ(test::from_json_with_rev(json::json::parse(zero_input), EVMC_CANCUN, blob_params) + .blob_gas_used, + 0); +} + TEST(statetest_loader, block_info_parent_beacon_block_root) { constexpr std::string_view input = R"({ diff --git a/evmone/test/unittests/statetest_loader_tx_test.cpp b/evmone/test/unittests/statetest_loader_tx_test.cpp index efabfd3..3576f0a 100644 --- a/evmone/test/unittests/statetest_loader_tx_test.cpp +++ b/evmone/test/unittests/statetest_loader_tx_test.cpp @@ -74,6 +74,53 @@ TEST(statetest_loader, tx_create_legacy) EXPECT_EQ(tx.v, 1); } +TEST(statetest_loader, tx_max_chain_id) +{ + // The maximum representable `chainId` (uint64 max = 0xffffffffffffffff) must be + // loaded without overflow or truncation. + constexpr std::string_view input = R"({ + "input": "b0b1", + "gas": "0x9091", + "chainId": "0xffffffffffffffff", + "value": "0xe0e1", + "sender": "a0a1", + "gasPrice": "0x7071", + "nonce": "0", + "r": "0x1111111111111111111111111111111111111111111111111111111111111111", + "s": "0x2222222222222222222222222222222222222222222222222222222222222222", + "v": "1" + })"; + + const auto tx = test::from_json(json::json::parse(input)); + EXPECT_EQ(tx.chain_id, std::numeric_limits::max()); +} + +TEST(statetest_loader, tx_max_legacy_chain_id) +{ + // A legacy EIP-155 transaction encodes the chain ID inside `v` as + // v = chain_id*2 + 35 + parity (parity in {0, 1}). Because `v` is loaded as uint64, the + // largest chain ID a legacy transaction can carry is the one whose `v` reaches uint64 max: + // chain_id = 0x7fffffffffffffee with parity 0 gives v = 0xffffffffffffffff. A larger chain ID + // would overflow `v`. + constexpr std::string_view input = R"({ + "input": "b0b1", + "gas": "0x9091", + "chainId": "0x7fffffffffffffee", + "value": "0xe0e1", + "sender": "a0a1", + "gasPrice": "0x7071", + "nonce": "0", + "r": "0x1111111111111111111111111111111111111111111111111111111111111111", + "s": "0x2222222222222222222222222222222222222222222222222222222222222222", + "v": "0xffffffffffffffff" + })"; + + const auto tx = test::from_json(json::json::parse(input)); + EXPECT_EQ(tx.chain_id, 0x7fffffffffffffee); + EXPECT_EQ(tx.v, std::numeric_limits::max()); + EXPECT_EQ(tx.v, tx.chain_id * 2 + 35); // EIP-155 with y-parity 0. +} + TEST(statetest_loader, tx_eip1559) { constexpr std::string_view input = R"({ diff --git a/evmone/test/unittests/tooling_run_test.cpp b/evmone/test/unittests/tooling_run_test.cpp new file mode 100644 index 0000000..9cdd70a --- /dev/null +++ b/evmone/test/unittests/tooling_run_test.cpp @@ -0,0 +1,40 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2026 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include + +using namespace evmone; +using namespace evmone::test; +using namespace evmone::tooling; +using namespace testing; + +TEST(tooling_run, execute) +{ + evmc::VM vm{evmc_create_evmone()}; + const auto code = push(1); + std::ostringstream out; + const auto rc = run(vm, EVMC_OSAKA, 100, code, {}, false, false, out); + EXPECT_EQ(rc, 0); + EXPECT_THAT(out.str(), HasSubstr("Executing")); + EXPECT_THAT(out.str(), HasSubstr("Osaka")); + EXPECT_THAT(out.str(), HasSubstr("Result: success")); + EXPECT_THAT(out.str(), HasSubstr("Gas used: 3")); +} + +TEST(tooling_run, create) +{ + evmc::VM vm{evmc_create_evmone()}; + const auto code = mstore(0, 0x5f) + ret(31, 1); + std::ostringstream out; + const auto rc = run(vm, EVMC_OSAKA, 100, code, {}, true, false, out); + EXPECT_EQ(rc, 0); + EXPECT_THAT(out.str(), HasSubstr("Creating")); + EXPECT_THAT(out.str(), HasSubstr("Osaka")); + EXPECT_THAT(out.str(), HasSubstr("Result: success")); + EXPECT_THAT(out.str(), HasSubstr("Gas used: 2")); +} diff --git a/evmone/test/unittests/tooling_t8n_test.cpp b/evmone/test/unittests/tooling_t8n_test.cpp new file mode 100644 index 0000000..c43b151 --- /dev/null +++ b/evmone/test/unittests/tooling_t8n_test.cpp @@ -0,0 +1,289 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2026 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include + +using namespace evmone; +using namespace testing; + +namespace +{ +// Minimal block env used by the trace test below. +// currentDifficulty is set so t8n() takes the "difficulty supplied" branch; +// tests with no env still exercise the calculate_difficulty fallback. +// currentRandom is also set so that the difficulty value isn't reinterpreted +// as a bytes32 prev_randao by from_json_with_rev. +constexpr auto ENV_JSON = R"({ + "currentCoinbase": "0x8888f1f195afa192cfee860698584c030f4c9db1", + "currentNumber": "0x01", + "currentTimestamp": "0x54c99069", + "currentGasLimit": "0x2fefd8", + "currentDifficulty": "0x20000", + "currentRandom": "0x0000000000000000000000000000000000000000000000000000000000000000" +})"; + +// Account funding the transaction used in the trace test. +constexpr auto ALLOC_JSON = R"({ + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "code": "", + "nonce": "0x00", + "balance": "0x02540be400" + } +})"; + +// Single legacy CREATE transaction; init code is `PUSH1 0x01 PUSH0 RETURN`, +// which deploys a one-byte runtime `0x01`. Three opcodes => three trace lines. +// Matches test/integration/t8n/cancun_create_tx/txs.json[0]; the tx hash is +// well-known and used below. +constexpr auto TX_JSON = R"([{ + "to": null, + "input": "0x60015ff3", + "gas": "0x186a0", + "nonce": "0x0", + "value": "0x0", + "gasPrice": "0x32", + "chainId": "0x1", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "v": "0x1b", + "r": "0x468a915f087692bb9be503831a3dfef2cf9c8dee26deb40ff2ec99e8d22665ae", + "s": "0x5cedae0810c3851ecd1004bfdbfe6ddc7753c2d665993bb01ce75af7857b13dc" +}])"; +} // namespace + +TEST(tooling_t8n, no_inputs_no_outputs) +{ + // Smoke: t8n() with everything left at defaults must not throw or crash. + evmc::VM vm{evmc_create_evmone()}; + + tooling::T8NArgs args; + args.rev = EVMC_OSAKA; + + tooling::t8n(vm, args); +} + +TEST(tooling_t8n, result_written_to_out_streams) +{ + evmc::VM vm{evmc_create_evmone()}; + + tooling::T8NArgs args; + args.rev = EVMC_OSAKA; + std::ostringstream out_result; + std::ostringstream out_alloc; + args.out_result = &out_result; + args.out_alloc = &out_alloc; + + tooling::t8n(vm, args); + + EXPECT_THAT(out_result.str(), HasSubstr("\"gasUsed\"")); + EXPECT_THAT(out_result.str(), HasSubstr("\"txRoot\"")); + EXPECT_THAT(out_result.str(), HasSubstr("\"receiptsRoot\"")); + EXPECT_THAT(out_result.str(), HasSubstr("\"logsBloom\"")); + EXPECT_THAT(out_alloc.str(), Eq("{}")); +} + +TEST(tooling_t8n, open_trace_called_per_tx) +{ + evmc::VM vm{evmc_create_evmone()}; + + std::istringstream env{ENV_JSON}; + std::istringstream alloc{ALLOC_JSON}; + std::istringstream txs{TX_JSON}; + std::ostringstream out_result; + std::ostringstream out_alloc; + std::ostringstream trace_buf; + std::vector> trace_calls; + + tooling::T8NArgs args; + args.rev = EVMC_SHANGHAI; // No system contracts => clean trace_buf. + args.chain_id = 1; + args.alloc = &alloc; + args.env = &env; + args.txs = &txs; + args.out_result = &out_result; + args.out_alloc = &out_alloc; + args.open_trace = [&](size_t i, const evmc::bytes32& hash) -> std::ostream& { + trace_calls.emplace_back(i, hash); + return trace_buf; + }; + + tooling::t8n(vm, args); + + ASSERT_EQ(trace_calls.size(), 1U); + EXPECT_EQ(trace_calls[0].first, 0U); + EXPECT_THAT(trace_buf.str(), HasSubstr("\"opName\":\"PUSH1\"")); + EXPECT_THAT(trace_buf.str(), HasSubstr("\"opName\":\"PUSH0\"")); + EXPECT_THAT(trace_buf.str(), HasSubstr("\"opName\":\"RETURN\"")); +} + +TEST(tooling_t8n, out_body_is_hex_rlp_of_transactions) +{ + evmc::VM vm{evmc_create_evmone()}; + + std::istringstream env{ENV_JSON}; + std::istringstream alloc{ALLOC_JSON}; + std::istringstream txs{TX_JSON}; + std::ostringstream out_result; + std::ostringstream out_alloc; + std::ostringstream out_body; + + tooling::T8NArgs args; + args.rev = EVMC_SHANGHAI; + args.chain_id = 1; + args.alloc = &alloc; + args.env = &env; + args.txs = &txs; + args.out_result = &out_result; + args.out_alloc = &out_alloc; + args.out_body = &out_body; + + tooling::t8n(vm, args); + + // RLP-encoded list of one legacy transaction, hex-prefixed. + EXPECT_THAT(out_body.str(), StartsWith("0x")); + EXPECT_GT(out_body.str().size(), std::size_t{2}); +} + +TEST(tooling_t8n, pre_byzantium_sets_receipt_post_state) +{ + evmc::VM vm{evmc_create_evmone()}; + + // Pre-Byzantium receipts include the post-state root via receipt.post_state. + // The TX_JSON fixture uses PUSH0 in its init code, so the inner CREATE fails + // at Homestead, but the outer tx still produces a TransactionReceipt that + // exercises the `rev < EVMC_BYZANTIUM` branch in t8n(). + std::istringstream env{ENV_JSON}; + std::istringstream alloc{ALLOC_JSON}; + std::istringstream txs{TX_JSON}; + std::ostringstream out_result; + + tooling::T8NArgs args; + args.rev = EVMC_HOMESTEAD; + args.chain_id = 1; + args.alloc = &alloc; + args.env = &env; + args.txs = &txs; + args.out_result = &out_result; + + tooling::t8n(vm, args); + + // The "receipts" array is initialized empty on every txs-present run; the + // distinguishing signal that a receipt was actually produced (i.e., the + // tx wasn't classified as rejected) is the presence of transactionHash. + EXPECT_THAT(out_result.str(), HasSubstr("\"transactionHash\"")); +} + +TEST(tooling_t8n, mismatched_tx_hash_throws) +{ + evmc::VM vm{evmc_create_evmone()}; + + // TX_JSON's tx with a deliberately wrong "hash" field. t8n() must detect + // the mismatch against the recomputed hash and throw std::logic_error. + static constexpr auto TX_WITH_BAD_HASH = R"([{ + "to": null, + "input": "0x60015ff3", + "gas": "0x186a0", + "nonce": "0x0", + "value": "0x0", + "gasPrice": "0x32", + "chainId": "0x1", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "v": "0x1b", + "r": "0x468a915f087692bb9be503831a3dfef2cf9c8dee26deb40ff2ec99e8d22665ae", + "s": "0x5cedae0810c3851ecd1004bfdbfe6ddc7753c2d665993bb01ce75af7857b13dc", + "hash": "0xdeadbeef00000000000000000000000000000000000000000000000000000000" + }])"; + + std::istringstream env{ENV_JSON}; + std::istringstream alloc{ALLOC_JSON}; + std::istringstream txs{TX_WITH_BAD_HASH}; + + tooling::T8NArgs args; + args.rev = EVMC_SHANGHAI; + args.chain_id = 1; + args.alloc = &alloc; + args.env = &env; + args.txs = &txs; + + EXPECT_THROW(tooling::t8n(vm, args), std::logic_error); +} + +TEST(tooling_t8n, max_chain_id) +{ + evmc::VM vm{evmc_create_evmone()}; + + // The maximum `chainId` (uint64 max = 0xffffffffffffffff) must be parsed and + // executed without overflow; regression test for `chainId` being loaded as + // `uint8_t`, which threw `from_json: value > 0xFF`. + static constexpr auto TX_MAX_CHAIN_ID = R"([{ + "to": null, + "input": "0x60015ff3", + "gas": "0x186a0", + "nonce": "0x0", + "value": "0x0", + "gasPrice": "0x32", + "chainId": "0xffffffffffffffff", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "v": "0x1b", + "r": "0x468a915f087692bb9be503831a3dfef2cf9c8dee26deb40ff2ec99e8d22665ae", + "s": "0x5cedae0810c3851ecd1004bfdbfe6ddc7753c2d665993bb01ce75af7857b13dc" + }])"; + + std::istringstream env{ENV_JSON}; + std::istringstream alloc{ALLOC_JSON}; + std::istringstream txs{TX_MAX_CHAIN_ID}; + std::ostringstream out_result; + + tooling::T8NArgs args; + args.rev = EVMC_SHANGHAI; + args.chain_id = std::numeric_limits::max(); + args.alloc = &alloc; + args.env = &env; + args.txs = &txs; + args.out_result = &out_result; + + EXPECT_NO_THROW(tooling::t8n(vm, args)); + EXPECT_THAT(out_result.str(), HasSubstr("\"transactionHash\"")); +} + +TEST(tooling_t8n, max_v) +{ + evmc::VM vm{evmc_create_evmone()}; + + // Legacy EIP-155 `v` is chainId*2 + 35 + parity, exceeding 0xff for chainId > 110. + // The maximum `v` (uint64 max = 0xffffffffffffffff) must be parsed and executed without + // overflow; regression test for `v` being loaded as `uint8_t`, which threw + // `from_json: value > 0xFF`. + static constexpr auto TX_MAX_V = R"([{ + "to": null, + "input": "0x60015ff3", + "gas": "0x186a0", + "nonce": "0x0", + "value": "0x0", + "gasPrice": "0x32", + "chainId": "0x1", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "v": "0xffffffffffffffff", + "r": "0x468a915f087692bb9be503831a3dfef2cf9c8dee26deb40ff2ec99e8d22665ae", + "s": "0x5cedae0810c3851ecd1004bfdbfe6ddc7753c2d665993bb01ce75af7857b13dc" + }])"; + + std::istringstream env{ENV_JSON}; + std::istringstream alloc{ALLOC_JSON}; + std::istringstream txs{TX_MAX_V}; + std::ostringstream out_result; + + tooling::T8NArgs args; + args.rev = EVMC_SHANGHAI; + args.chain_id = 1; + args.alloc = &alloc; + args.env = &env; + args.txs = &txs; + args.out_result = &out_result; + + EXPECT_NO_THROW(tooling::t8n(vm, args)); + EXPECT_THAT(out_result.str(), HasSubstr("\"transactionHash\"")); +} diff --git a/evmone/test/unittests/tracing_test.cpp b/evmone/test/unittests/tracing_test.cpp index c511c7f..5fd891c 100644 --- a/evmone/test/unittests/tracing_test.cpp +++ b/evmone/test/unittests/tracing_test.cpp @@ -311,7 +311,6 @@ TEST_F(tracing, trace_create_instruction) const auto code = push(10) + push(0) + push(0) + OP_CREATE + ret_top(); const auto result_data = "0x60016000526001601ff3"_hex; - host.call_result.create_address = 0x1122334455667788991011223344556677889910_address; host.call_result.output_data = result_data.data(); host.call_result.output_size = result_data.size(); @@ -320,8 +319,8 @@ TEST_F(tracing, trace_create_instruction) {"pc":2,"op":96,"gas":"0xf423d","gasCost":"0x3","memSize":0,"stack":["0xa"],"depth":1,"refund":0,"opName":"PUSH1"} {"pc":4,"op":96,"gas":"0xf423a","gasCost":"0x3","memSize":0,"stack":["0xa","0x0"],"depth":1,"refund":0,"opName":"PUSH1"} {"pc":6,"op":240,"gas":"0xf4237","gasCost":"0x7d00","memSize":0,"stack":["0xa","0x0","0x0"],"depth":1,"refund":0,"opName":"CREATE"} -{"pc":7,"op":96,"gas":"0x3b14","gasCost":"0x3","memSize":32,"stack":["0x1122334455667788991011223344556677889910"],"returnData":"0x60016000526001601ff3","depth":1,"refund":0,"opName":"PUSH1"} -{"pc":9,"op":82,"gas":"0x3b11","gasCost":"0x3","memSize":32,"stack":["0x1122334455667788991011223344556677889910","0x0"],"returnData":"0x60016000526001601ff3","depth":1,"refund":0,"opName":"MSTORE"} +{"pc":7,"op":96,"gas":"0x3b14","gasCost":"0x3","memSize":32,"stack":["0xbd770416a3345f91e4b34576cb804a576fa48eb1"],"returnData":"0x60016000526001601ff3","depth":1,"refund":0,"opName":"PUSH1"} +{"pc":9,"op":82,"gas":"0x3b11","gasCost":"0x3","memSize":32,"stack":["0xbd770416a3345f91e4b34576cb804a576fa48eb1","0x0"],"returnData":"0x60016000526001601ff3","depth":1,"refund":0,"opName":"MSTORE"} {"pc":10,"op":96,"gas":"0x3b0e","gasCost":"0x3","memSize":32,"stack":[],"returnData":"0x60016000526001601ff3","depth":1,"refund":0,"opName":"PUSH1"} {"pc":12,"op":96,"gas":"0x3b0b","gasCost":"0x3","memSize":32,"stack":["0x20"],"returnData":"0x60016000526001601ff3","depth":1,"refund":0,"opName":"PUSH1"} {"pc":14,"op":243,"gas":"0x3b08","gasCost":"0x0","memSize":32,"stack":["0x20","0x0"],"returnData":"0x60016000526001601ff3","depth":1,"refund":0,"opName":"RETURN"} diff --git a/evmone/test/utils/CMakeLists.txt b/evmone/test/utils/CMakeLists.txt index 5a03f09..3ed2a3c 100644 --- a/evmone/test/utils/CMakeLists.txt +++ b/evmone/test/utils/CMakeLists.txt @@ -4,7 +4,11 @@ add_library(evmone.testutils STATIC) add_library(evmone::testutils ALIAS evmone.testutils) -target_link_libraries(evmone.testutils PUBLIC evmone::state evmc::evmc_cpp nlohmann_json::nlohmann_json) +target_link_libraries( + evmone.testutils + PUBLIC evmone::state evmc::evmc_cpp nlohmann_json::nlohmann_json + PRIVATE evmc::mocked_host +) target_sources( evmone.testutils @@ -12,9 +16,13 @@ target_sources( stdx/utility.hpp blob_schedule.hpp blob_schedule.cpp + block_transition.hpp + block_transition.cpp blockchaintest.hpp blockchaintest_loader.cpp bytecode.hpp + error_matching.hpp + error_matching.cpp mpt.hpp mpt.cpp mpt_hash.hpp @@ -22,10 +30,14 @@ target_sources( rlp.hpp rlp_encode.hpp rlp_encode.cpp + run.hpp + run.cpp statetest.hpp statetest_export.cpp statetest_loader.cpp statetest_logs_hash.cpp + t8n.hpp + t8n.cpp test_state.hpp test_state.cpp utils.hpp diff --git a/evmone/test/utils/blob_schedule.cpp b/evmone/test/utils/blob_schedule.cpp index 7258331..beca932 100644 --- a/evmone/test/utils/blob_schedule.cpp +++ b/evmone/test/utils/blob_schedule.cpp @@ -4,15 +4,15 @@ namespace evmone::test { -state::BlobParams get_blob_params(evmc_revision rev) +state::BlobParams get_blob_params(evmc_revision rev) noexcept { - if (rev == EVMC_PRAGUE || rev == EVMC_EXPERIMENTAL) + if (rev >= EVMC_AMSTERDAM) + return {14, 21, 11684671}; + if (rev >= EVMC_PRAGUE) return {6, 9, 5007716}; - else if (rev > EVMC_PRAGUE) - throw std::invalid_argument{ - "no hardcoded blob params for " + std::string{evmc::to_string(rev)}}; - else + if (rev == EVMC_CANCUN) return {3, 6, 3338477}; + return {0, 0, 1}; } state::BlobParams get_blob_params(evmc_revision rev, const BlobSchedule& blob_schedule) @@ -34,6 +34,8 @@ state::BlobParams get_blob_params( fork = timestamp >= 15'000 ? "BPO3" : "BPO2"; else if (network == "BPO3ToBPO4AtTime15k") fork = timestamp >= 15'000 ? "BPO4" : "BPO3"; + else if (network == "BPO2ToAmsterdamAtTime15k") + fork = timestamp >= 15'000 ? "Amsterdam" : "BPO2"; else fork = network; if (const auto it = blob_schedule.find(fork); it != blob_schedule.end()) diff --git a/evmone/test/utils/blob_schedule.hpp b/evmone/test/utils/blob_schedule.hpp index c7ff6f1..f7f9f4e 100644 --- a/evmone/test/utils/blob_schedule.hpp +++ b/evmone/test/utils/blob_schedule.hpp @@ -10,8 +10,9 @@ namespace evmone::test using BlobSchedule = std::unordered_map; /// Returns the hardcoded blob params for the given EVM revision. -/// After Prague, the blob params must be derived from config. -state::BlobParams get_blob_params(evmc_revision rev); +/// After Prague, the blob params should be taken from a BPO config. However, for unit tests this +/// function still can be used to get a reasonable default. +state::BlobParams get_blob_params(evmc_revision rev) noexcept; /// Returns the blob params for the given EVM revision and a blob schedule. state::BlobParams get_blob_params(evmc_revision rev, const BlobSchedule& blob_schedule); diff --git a/evmone/test/utils/block_transition.cpp b/evmone/test/utils/block_transition.cpp new file mode 100644 index 0000000..c8a36a7 --- /dev/null +++ b/evmone/test/utils/block_transition.cpp @@ -0,0 +1,120 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2025 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "block_transition.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace evmone::test +{ +namespace +{ +/// Redirects an ostream's streambuf for the scope's lifetime. +class StreamRedirect +{ + std::ostream& stream_; + std::streambuf* prev_; + +public: + StreamRedirect(std::ostream& stream, std::streambuf* new_buf) noexcept + : stream_{stream}, prev_{stream.rdbuf(new_buf)} + {} + + StreamRedirect(const StreamRedirect&) = delete; + StreamRedirect& operator=(const StreamRedirect&) = delete; + + ~StreamRedirect() { stream_.rdbuf(prev_); } +}; +} // namespace + +TransitionResult apply_block(const TestState& state, evmc::VM& vm, const state::BlockInfo& block, + const state::BlockHashes& block_hashes, const std::vector& txs, + evmc_revision rev, int64_t blob_gas_limit, const BlockTransitionOptions& opts) +{ + const bool trace_enabled = static_cast(opts.open_trace); + if (trace_enabled) + vm.set_option("trace", "1"); // This actually appends a new tracer on each set_option(). + + TestState block_state(state); + if (!opts.skip_system_calls) + system_call_block_start(block_state, block, block_hashes, rev, vm); + + std::vector rejected_txs; + std::vector receipts; + + int64_t block_gas_left = block.gas_limit; + int64_t cumulative_gas_used = 0; + int64_t block_gas_used = 0; + auto blob_gas_left = blob_gas_limit; + + for (size_t i = 0; i < txs.size(); ++i) + { + const auto& tx = txs[i]; + const auto computed_tx_hash = keccak256(rlp::encode(tx)); + + std::optional trace_guard; + if (trace_enabled) + trace_guard.emplace(std::clog, opts.open_trace(i, computed_tx_hash).rdbuf()); + + auto res = transition( + block_state, block, block_hashes, tx, rev, vm, block_gas_left, blob_gas_left); + + if (holds_alternative(res)) + { + rejected_txs.push_back({computed_tx_hash, i, std::get(res)}); + } + else + { + auto& receipt = get(res); + + cumulative_gas_used += receipt.gas_used; + receipt.cumulative_gas_used = cumulative_gas_used; + if (rev < EVMC_BYZANTIUM) + receipt.post_state = state::mpt_hash(block_state); + + // Block gas accounting, refunds excluded (EIP-7778). + const auto block_tx_gas = + (rev >= EVMC_AMSTERDAM) ? receipt.gas_used + receipt.gas_refund : receipt.gas_used; + block_gas_used += block_tx_gas; + block_gas_left -= block_tx_gas; + blob_gas_left -= static_cast(tx.blob_gas_used()); + receipts.emplace_back(std::move(receipt)); + } + } + + std::vector requests; + std::error_code requests_error; + if (!opts.skip_system_calls) + { + if (rev >= EVMC_PRAGUE) + { + if (auto opt_deposits = collect_deposit_requests(receipts); opt_deposits.has_value()) + requests.emplace_back(std::move(*opt_deposits)); + else + requests_error = make_error_code(state::INVALID_DEPOSIT_EVENT_LAYOUT); + } + if (!requests_error) + { + auto block_end = system_call_block_end(block_state, block, block_hashes, rev, vm); + if (const auto* ec = std::get_if(&block_end)) + requests_error = *ec; + else + std::ranges::move(std::get>(block_end), + std::back_inserter(requests)); + } + } + + finalize(block_state, rev, block.coinbase, opts.block_reward, block.ommers, block.withdrawals); + + const auto bloom = compute_bloom_filter(receipts); + + return {std::move(receipts), std::move(rejected_txs), std::move(requests), requests_error, + block_gas_used, bloom, blob_gas_left, std::move(block_state)}; +} +} // namespace evmone::test diff --git a/evmone/test/utils/block_transition.hpp b/evmone/test/utils/block_transition.hpp new file mode 100644 index 0000000..c95628a --- /dev/null +++ b/evmone/test/utils/block_transition.hpp @@ -0,0 +1,74 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2025 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace evmone::state +{ +struct BlockInfo; +} + +namespace evmone::test +{ +/// A transaction rejected during block application. +struct RejectedTransaction +{ + hash256 hash; ///< keccak256 of the transaction's RLP encoding. + size_t index; ///< Position in the input transaction list. + std::error_code error; ///< Why the transaction was rejected. +}; + +/// Options for apply_block(). Defaults match block-validation (full) behavior. +struct BlockTransitionOptions +{ + /// Mining reward paid to the coinbase on finalization (nullopt = PoS, no reward). + std::optional block_reward; + + /// Skip the block-start/block-end system calls and request collection + /// (t8n pre-state-only mode). The transaction loop and finalization still run. + bool skip_system_calls = false; + + /// Called once per transaction (just before execution) to obtain a per-tx + /// trace sink; std::clog is redirected to the returned stream for the + /// duration of that transaction. Unset = tracing disabled. + std::function open_trace; +}; + +/// Result of applying a block; see apply_block(). +struct TransitionResult +{ + std::vector receipts; ///< Accepted transactions, in block order. + std::vector rejected; ///< Rejected transactions, in input order. + std::vector requests; ///< Collected requests (EIP-7685). + std::error_code requests_error; ///< Set if requests collection failed (block is invalid). + int64_t gas_used = 0; ///< Block gas used; includes refunds for Amsterdam+ (EIP-7778). + state::BloomFilter bloom; ///< Logs bloom over all accepted transactions. + int64_t blob_gas_left = 0; ///< Blob gas remaining out of the budget passed in. + TestState block_state; ///< State after applying the block. +}; + +/// Applies a block of transactions to a copy of @p state: block-start system call, the +/// transactions, request collection, block-end system call, and finalization. The system calls +/// and request collection are skipped when `opts.skip_system_calls` is set. +/// +/// Shared block-transition core for the blockchain test runner and the t8n tool. It performs +/// no validation/assertions and produces no output; callers interpret the returned result. +/// Block-level validity is assumed, but individual transactions may be rejected. +/// +/// @param blob_gas_limit The per-block blob-gas budget set by the protocol maximum. +[[nodiscard]] TransitionResult apply_block(const TestState& state, evmc::VM& vm, + const state::BlockInfo& block, const state::BlockHashes& block_hashes, + const std::vector& txs, evmc_revision rev, int64_t blob_gas_limit, + const BlockTransitionOptions& opts = {}); +} // namespace evmone::test diff --git a/evmone/test/utils/blockchaintest.hpp b/evmone/test/utils/blockchaintest.hpp index ce0e1e6..88f3356 100644 --- a/evmone/test/utils/blockchaintest.hpp +++ b/evmone/test/utils/blockchaintest.hpp @@ -42,15 +42,16 @@ struct BlockHeader std::optional blob_gas_used; std::optional excess_blob_gas; hash256 requests_hash; + std::optional slot_number; ///< EIP-7843 — absent before Amsterdam. }; struct TestBlock { state::BlockInfo block_info; std::vector transactions; - size_t rlp_size = 0; + bytes rlp; ///< The block's complete serialization. bool withdrawals_parse_success = true; - bool valid = true; + std::string expected_exception; ///< Empty for valid blocks. BlockHeader expected_block_header; }; diff --git a/evmone/test/utils/blockchaintest_loader.cpp b/evmone/test/utils/blockchaintest_loader.cpp index 3828733..aed3203 100644 --- a/evmone/test/utils/blockchaintest_loader.cpp +++ b/evmone/test/utils/blockchaintest_loader.cpp @@ -3,30 +3,14 @@ // SPDX-License-Identifier: Apache-2.0 #include "blockchaintest.hpp" +#include "error_matching.hpp" #include "statetest.hpp" #include "utils.hpp" +#include namespace evmone::test { -namespace -{ -template -T load_if_exists(const json::json& j, std::string_view key) -{ - if (const auto it = j.find(key); it != j.end()) - return from_json(*it); - return {}; -} -template -std::optional load_optional(const json::json& j, std::string_view key) -{ - if (const auto it = j.find(key); it != j.end()) - return from_json(*it); - return std::nullopt; -} -} // namespace - template <> BlockHeader from_json(const json::json& j) { @@ -36,21 +20,22 @@ BlockHeader from_json(const json::json& j) .state_root = from_json(j.at("stateRoot")), .receipts_root = from_json(j.at("receiptTrie")), .logs_bloom = state::bloom_filter_from_bytes(from_json(j.at("bloom"))), - .difficulty = load_if_exists(j, "difficulty"), - .prev_randao = load_if_exists(j, "mixHash"), + .difficulty = load_or(j, "difficulty", 0), + .prev_randao = load_or(j, "mixHash", {}), .block_number = from_json(j.at("number")), .gas_limit = from_json(j.at("gasLimit")), .gas_used = from_json(j.at("gasUsed")), .timestamp = from_json(j.at("timestamp")), .extra_data = from_json(j.at("extraData")), - .base_fee_per_gas = load_if_exists(j, "baseFeePerGas"), + .base_fee_per_gas = load_or(j, "baseFeePerGas", 0), .hash = from_json(j.at("hash")), .transactions_root = from_json(j.at("transactionsTrie")), - .withdrawal_root = load_if_exists(j, "withdrawalsRoot"), - .parent_beacon_block_root = load_if_exists(j, "parentBeaconBlockRoot"), + .withdrawal_root = load_or(j, "withdrawalsRoot", {}), + .parent_beacon_block_root = load_or(j, "parentBeaconBlockRoot", {}), .blob_gas_used = load_optional(j, "blobGasUsed"), .excess_blob_gas = load_optional(j, "excessBlobGas"), - .requests_hash = load_if_exists(j, "requestsHash"), + .requests_hash = load_or(j, "requestsHash", {}), + .slot_number = load_optional(j, "slotNumber"), }; } @@ -81,6 +66,7 @@ static TestBlock load_test_block( tb.block_info.parent_beacon_block_root = tb.expected_block_header.parent_beacon_block_root; tb.block_info.blob_gas_used = tb.expected_block_header.blob_gas_used; tb.block_info.excess_blob_gas = tb.expected_block_header.excess_blob_gas; + tb.block_info.slot_number = tb.expected_block_header.slot_number; tb.block_info.blob_base_fee = tb.block_info.excess_blob_gas.has_value() ? std::optional(state::compute_blob_gas_price( @@ -144,10 +130,11 @@ BlockchainTest load_blockchain_test_case(const std::string& name, const json::js bt.pre_state = from_json(j.at("pre")); bt.network = j.at("network").get(); bt.rev = to_rev_schedule(bt.network); + uint64_t chain_id = 1; if (const auto config_it = j.find("config"); config_it != j.end()) { - if (const auto bs_it = config_it->find("blobSchedule"); bs_it != config_it->end()) - bt.blob_schedule = from_json(*bs_it); + bt.blob_schedule = load_or(*config_it, "blobSchedule", {}); + chain_id = load_or(*config_it, "chainid", chain_id); } for (const auto& el : j.at("blocks")) { @@ -163,24 +150,28 @@ BlockchainTest load_blockchain_test_case(const std::string& name, const json::js "tests with invalidly rlp-encoded blocks are not supported"); auto test_block = load_test_block(el.at("rlp_decoded"), bt.network, bt.blob_schedule); - test_block.valid = false; - test_block.rlp_size = from_json(el.at("rlp")).size(); + test_block.expected_exception = map_legacy_exception(it->get()); + test_block.rlp = from_json(el.at("rlp")); bt.test_blocks.emplace_back(test_block); } else { auto test_block = load_test_block(el, bt.network, bt.blob_schedule); - test_block.rlp_size = from_json(el.at("rlp")).size(); + test_block.rlp = from_json(el.at("rlp")); bt.test_blocks.emplace_back(test_block); } } + for (auto& tb : bt.test_blocks) + tb.block_info.chain_id = chain_id; + bt.expectation.last_block_hash = from_json(j.at("lastblockhash")); - if (const auto it = j.find("postState"); it != j.end()) - bt.expectation.post_state = from_json(*it); - else if (const auto it_hash = j.find("postStateHash"); it_hash != j.end()) - bt.expectation.post_state = from_json(*it_hash); + // A test states its expected post state either in full or by its hash, never neither. + if (auto post_state = load_optional(j, "postState")) + bt.expectation.post_state = std::move(*post_state); + else + bt.expectation.post_state = from_json(j.at("postStateHash")); return bt; } diff --git a/evmone/test/utils/error_matching.cpp b/evmone/test/utils/error_matching.cpp new file mode 100644 index 0000000..b239ad0 --- /dev/null +++ b/evmone/test/utils/error_matching.cpp @@ -0,0 +1,169 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2026 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "error_matching.hpp" +#include +#include +#include + +namespace evmone::test +{ +namespace +{ +/// The exceptions a fixture may name for a rejection evmone reports with this error, on top of +/// the canonical name its error message carries. +struct AlternativeExceptions +{ + state::ErrorCode errc; ///< The code evmone rejects with. + std::string_view names; ///< The other names the fixtures use for it, `|`-separated. +}; + +/// Where the execution specs draw more distinctions than evmone does, or draw one of them in a +/// different place. Both refuse the same transactions, so every name listed here is accepted. +constexpr AlternativeExceptions ALTERNATIVE_TX_EXCEPTIONS[]{ + // The specs make the floor cost (EIP-7623) a rule of its own; evmone folds it into the + // intrinsic gas. + {state::INTRINSIC_GAS_TOO_LOW, "TransactionException.INTRINSIC_GAS_BELOW_FLOOR_GAS_COST"}, + + // The specs name the transaction type that arrived before its fork; evmone has one rule. + {state::TYPE_NOT_SUPPORTED, + "TransactionException.TYPE_1_TX_PRE_FORK|" + "TransactionException.TYPE_2_TX_PRE_FORK|" + "TransactionException.TYPE_3_TX_PRE_FORK|" + "TransactionException.TYPE_4_TX_PRE_FORK"}, + + // The specs separate the transaction's own blob count from the block's blob gas allowance. + {state::BLOB_GAS_LIMIT_EXCEEDED, + "TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED"}, + + // evmone bounds the signature v while decoding the transaction, because the domain of v is + // what tells a legacy transaction from a typed one and carries the chain id (EIP-155). The + // execution specs read v as a plain integer and bound it with the rest of the signature. + // decode_transaction() reports one code for every malformed encoding, so this accepts more + // than the v rule; narrowing it needs the decoder to report the v domain separately. + {state::INVALID_ENCODING, "TransactionException.INVALID_SIGNATURE_VRS"}, +}; + +/// The same, for the rules evmone checks on the block rather than the transaction. +constexpr AlternativeExceptions ALTERNATIVE_BLOCK_EXCEPTIONS[]{ + // A parent that is absent and one whose hash is zero are the same lookup miss to evmone. + {state::UNKNOWN_PARENT, "BlockException.UNKNOWN_PARENT_ZERO"}, + + // evmone reports one malformed-header error where the specs name the individual rule. Nine + // validate_block() branches share that code, so these names are interchangeable to it; + // separating them needs a distinct code per branch. + {state::INCORRECT_BLOCK_FORMAT, + "BlockException.GAS_USED_OVERFLOW|" + "BlockException.IMPORT_IMPOSSIBLE_UNCLES_OVER_PARIS|" + "BlockException.RLP_STRUCTURES_ENCODING|" + "BlockException.RLP_INVALID_FIELD_OVERFLOW_64"}, +}; + +/// A retesteth `expectException` value and the evmone rejection(s) it stands for. Some legacy +/// names cover two rules at once, hence the second code. +struct LegacyException +{ + std::string_view name; + state::ErrorCode errc; + state::ErrorCode alt = state::SUCCESS; +}; + +constexpr LegacyException LEGACY_EXCEPTIONS[]{ + // Transaction-level, ethereum/tests and ethereum/legacytests. + {"TR_IntrinsicGas", state::INTRINSIC_GAS_TOO_LOW}, + {"IntrinsicGas", state::INTRINSIC_GAS_TOO_LOW}, + {"TR_TypeNotSupported", state::TYPE_NOT_SUPPORTED}, + {"TR_NoFunds", state::INSUFFICIENT_ACCOUNT_FUNDS}, + {"TR_NoFundsX", state::INSUFFICIENT_ACCOUNT_FUNDS}, + {"TR_NoFundsOrGas", state::INSUFFICIENT_ACCOUNT_FUNDS, state::INTRINSIC_GAS_TOO_LOW}, + {"SenderNotEOA", state::SENDER_NOT_EOA}, + {"SenderNotEOAorNoCASH", state::SENDER_NOT_EOA, state::INSUFFICIENT_ACCOUNT_FUNDS}, + {"TR_GasLimitReached", state::GAS_ALLOWANCE_EXCEEDED}, + {"TR_TipGtFeeCap", state::PRIORITY_GREATER_THAN_MAX_FEE_PER_GAS}, + {"TR_FeeCapLessThanBlocks", state::INSUFFICIENT_MAX_FEE_PER_GAS}, + {"TR_FeeCapLessThanBlocksORNoFunds", state::INSUFFICIENT_MAX_FEE_PER_GAS, + state::INSUFFICIENT_ACCOUNT_FUNDS}, + {"TR_FeeCapLessThanBlocksORGasLimitReached", state::INSUFFICIENT_MAX_FEE_PER_GAS, + state::GAS_ALLOWANCE_EXCEEDED}, + {"TR_NonceHasMaxValue", state::NONCE_IS_MAX}, + {"TR_NonceTooLow", state::NONCE_TOO_LOW}, + {"TR_NonceTooHigh", state::NONCE_TOO_HIGH}, + {"TR_RLP_WRONGVALUE", state::INVALID_ENCODING}, + {"TR_InitCodeLimitExceeded", state::INITCODE_SIZE_EXCEEDED}, + {"TR_BLOBCREATE", state::CREATE_BLOB_TX}, + {"TR_EMPTYBLOB", state::EMPTY_BLOB_HASHES_LIST}, + {"TR_BLOBVERSION_INVALID", state::INVALID_BLOB_HASH_VERSION}, + {"TR_BLOBLIST_OVERSIZE", state::BLOB_GAS_LIMIT_EXCEEDED}, + + // Block-level. + {"PostParisUncleHashIsNotEmpty", state::INCORRECT_BLOCK_FORMAT}, + {"3675PreParis1559BlockRejected", state::INCORRECT_BLOCK_FORMAT}, + {"InvalidNumber", state::INCORRECT_BLOCK_FORMAT}, + {"InvalidTimestampOlderParent", state::INVALID_BLOCK_TIMESTAMP_OLDER_THAN_PARENT}, + {"TooMuchGasUsed", state::INCORRECT_BLOCK_FORMAT}, + {"UncleParentIsNotAncestor", state::INCORRECT_BLOCK_FORMAT}, + {"InvalidGasLimit2", state::INVALID_GASLIMIT}, + {"1559BlockImportImpossible_BaseFeeWrong", state::INVALID_BASEFEE_PER_GAS}, +}; + +/// Takes the next `|`-separated name off @p list, which is left pointing past it. +std::string_view take_name(std::string_view& list) noexcept +{ + const auto end = std::min(list.find('|'), list.size()); + const auto name = list.substr(0, end); + list.remove_prefix(std::min(end + 1, list.size())); + return name; +} +} // namespace + +std::string map_legacy_exception(std::string_view expected) +{ + const auto it = std::ranges::find(LEGACY_EXCEPTIONS, expected, &LegacyException::name); + if (it == std::end(LEGACY_EXCEPTIONS)) + return std::string{expected}; + + auto names = make_error_code(it->errc).message(); + if (it->alt != state::SUCCESS) + names += '|' + make_error_code(it->alt).message(); + return names; +} + +bool contains_any(std::string_view expected, std::string_view names) noexcept +{ + while (!names.empty()) + { + const auto name = take_name(names); + for (auto rest = expected; !rest.empty();) + { + if (take_name(rest) == name) + return true; + } + } + return false; +} + +namespace +{ +bool is_expected_exception(std::span alternatives, + const std::error_code& ec, std::string_view expected) noexcept +{ + if (contains_any(expected, ec.message())) // The message is the canonical exception name. + return true; + + return std::ranges::any_of(alternatives, [&](const AlternativeExceptions& a) { + return make_error_code(a.errc) == ec && contains_any(expected, a.names); + }); +} +} // namespace + +bool is_expected_tx_exception(const std::error_code& ec, std::string_view expected) noexcept +{ + return is_expected_exception(ALTERNATIVE_TX_EXCEPTIONS, ec, expected); +} + +bool is_expected_block_exception(const std::error_code& ec, std::string_view expected) noexcept +{ + return is_expected_exception(ALTERNATIVE_BLOCK_EXCEPTIONS, ec, expected); +} +} // namespace evmone::test diff --git a/evmone/test/utils/error_matching.hpp b/evmone/test/utils/error_matching.hpp new file mode 100644 index 0000000..921c9c6 --- /dev/null +++ b/evmone/test/utils/error_matching.hpp @@ -0,0 +1,36 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2026 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include +#include + +namespace evmone::test +{ +/// Rewrites a fixture's `expectException` value to the execution-spec-tests names evmone reports, +/// so both test runners compare one vocabulary. Covers the retesteth vocabulary of ethereum/tests +/// (TR_NoFunds, InvalidGasLimit2, ...) and the few block-level spec names evmone does not tell +/// apart. Anything else is returned unchanged. +[[nodiscard]] std::string map_legacy_exception(std::string_view expected); + +/// Whether any of the `|`-separated @p names is one of the `|`-separated @p expected, a fixture's +/// `expectException` value listing the exceptions it accepts. Names are compared whole: several +/// are a prefix of another (BlockException.UNKNOWN_PARENT and BlockException.UNKNOWN_PARENT_ZERO), +/// so a substring search would accept a rejection for a different rule. +/// +/// TODO(C++23): both sides become std::views::split ranges. In C++20 that view is the lazy one: +/// it yields forward ranges, not the contiguous ones std::string_view can be built from. +[[nodiscard]] bool contains_any(std::string_view expected, std::string_view names) noexcept; + +/// Whether the transaction validation error @p ec is one of the exceptions @p expected, the +/// fixture's `expectException` value. The canonical name is the error's own message; where the +/// specs name more exceptions for the same rule, those are accepted too. +[[nodiscard]] bool is_expected_tx_exception( + const std::error_code& ec, std::string_view expected) noexcept; + +/// The same for a block validation error. +[[nodiscard]] bool is_expected_block_exception( + const std::error_code& ec, std::string_view expected) noexcept; +} // namespace evmone::test diff --git a/evmone/test/utils/rlp.hpp b/evmone/test/utils/rlp.hpp index 353697b..149da4f 100644 --- a/evmone/test/utils/rlp.hpp +++ b/evmone/test/utils/rlp.hpp @@ -6,7 +6,9 @@ #include #include +#include #include +#include #include #include #include @@ -19,26 +21,27 @@ using evmc::bytes_view; namespace internal { -template +template inline bytes encode_length(size_t l) { - static constexpr uint8_t short_cutoff = 55; - static_assert(ShortBase + short_cutoff <= 0xff); + static_assert(ShortBase + SHORT_LENGTH_LIMIT <= std::numeric_limits::max(), + "long base must fit uint8_t"); + static constexpr uint8_t LONG_BASE = ShortBase + SHORT_LENGTH_LIMIT; assert(l <= 0xffffff); - if (l <= short_cutoff) + if (l <= SHORT_LENGTH_LIMIT) return {static_cast(ShortBase + l)}; else if (const auto l0 = static_cast(l); l <= 0xff) - return {LongBase + 1, l0}; + return {LONG_BASE + 1, l0}; else if (const auto l1 = static_cast(l >> 8); l <= 0xffff) - return {LongBase + 2, l1, l0}; + return {LONG_BASE + 2, l1, l0}; else - return {LongBase + 3, static_cast(l >> 16), l1, l0}; + return {LONG_BASE + 3, static_cast(l >> 16), l1, l0}; } inline bytes wrap_list(const bytes& content) { - return internal::encode_length<192, 247>(content.size()) + content; + return internal::encode_length(content.size()) + content; } template @@ -59,11 +62,10 @@ inline decltype(rlp_encode(std::declval())) encode(const T& v) inline bytes encode(bytes_view data) { - static constexpr uint8_t short_base = 128; - if (data.size() == 1 && data[0] < short_base) + if (data.size() == 1 && data[0] < SHORT_STRING_BASE) return {data[0]}; - return internal::encode_length(data.size()) += data; // Op + not available. + return internal::encode_length(data.size()) += data; // Op + not available. } inline bytes encode(uint64_t x) diff --git a/evmone/test/utils/rlp_encode.cpp b/evmone/test/utils/rlp_encode.cpp index 8c9e25c..8275157 100644 --- a/evmone/test/utils/rlp_encode.cpp +++ b/evmone/test/utils/rlp_encode.cpp @@ -96,7 +96,7 @@ namespace evmone::state [[nodiscard]] bytes rlp_encode(const Authorization& authorization) { return rlp::encode_tuple(authorization.chain_id, authorization.addr, authorization.nonce, - authorization.v, authorization.r, authorization.s); + authorization.y_parity, authorization.r, authorization.s); } [[nodiscard]] bytes rlp_encode(const Withdrawal& withdrawal) diff --git a/evmone/evmc/lib/tooling/run.cpp b/evmone/test/utils/run.cpp similarity index 78% rename from evmone/evmc/lib/tooling/run.cpp rename to evmone/test/utils/run.cpp index 8db3961..12b9bb3 100644 --- a/evmone/evmc/lib/tooling/run.cpp +++ b/evmone/test/utils/run.cpp @@ -1,16 +1,17 @@ -// EVMC: Ethereum Client-VM Connector API. -// Copyright 2019-2020 The EVMC Authors. -// Licensed under the Apache License, Version 2.0. +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2019 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 -#include +#include "run.hpp" #include #include -#include #include #include -namespace evmc::tooling +namespace evmone::tooling { +using namespace evmc; + namespace { /// The address where a new contract is created with --create option. @@ -19,16 +20,8 @@ constexpr auto create_address = 0xc9ea7ed000000000000000000000000000000001_addre /// The gas limit for contract creation. constexpr auto create_gas = 10'000'000; -/// MAGIC bytes denoting an EOF container. -constexpr uint8_t MAGIC[] = {0xef, 0x00}; - -auto bench(MockedHost& host, - evmc::VM& vm, - evmc_revision rev, - const evmc_message& msg, - bytes_view code, - const evmc::Result& expected_result, - std::ostream& out) +auto bench(MockedHost& host, evmc::VM& vm, evmc_revision rev, const evmc_message& msg, + bytes_view code, const evmc::Result& expected_result, std::ostream& out) { { using clock = std::chrono::steady_clock; @@ -60,21 +53,10 @@ auto bench(MockedHost& host, << " (avg of " << num_iterations << " iterations)\n"; } } - -bool is_eof_container(bytes_view code) -{ - return code.size() >= 2 && code[0] == MAGIC[0] && code[1] == MAGIC[1]; -} } // namespace -int run(VM& vm, - evmc_revision rev, - int64_t gas, - bytes_view code, - bytes_view input, - bool create, - bool bench, - std::ostream& out) +int run(VM& vm, evmc_revision rev, int64_t gas, bytes_view code, bytes_view input, bool create, + bool bench, std::ostream& out) { out << (create ? "Creating and executing on " : "Executing on ") << rev << " with " << gas << " gas limit\n"; @@ -90,7 +72,7 @@ int run(VM& vm, if (create) { evmc_message create_msg{}; - create_msg.kind = is_eof_container(code) ? EVMC_EOFCREATE : EVMC_CREATE; + create_msg.kind = EVMC_CREATE; create_msg.recipient = create_address; create_msg.gas = create_gas; @@ -122,4 +104,4 @@ int run(VM& vm, return 0; } -} // namespace evmc::tooling +} // namespace evmone::tooling diff --git a/evmone/test/utils/run.hpp b/evmone/test/utils/run.hpp new file mode 100644 index 0000000..dfba193 --- /dev/null +++ b/evmone/test/utils/run.hpp @@ -0,0 +1,13 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2019 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +namespace evmone::tooling +{ +int run(evmc::VM& vm, evmc_revision rev, int64_t gas, evmc::bytes_view code, evmc::bytes_view input, + bool create, bool bench, std::ostream& out); +} // namespace evmone::tooling diff --git a/evmone/test/utils/statetest.hpp b/evmone/test/utils/statetest.hpp index 3923f37..c9d071e 100644 --- a/evmone/test/utils/statetest.hpp +++ b/evmone/test/utils/statetest.hpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace json = nlohmann; @@ -50,7 +51,14 @@ struct StateTransitionTest TestMultiTransaction::Indexes indexes; hash256 state_hash; hash256 logs_hash = EmptyListHash; - bool exception = false; + + /// The exception the transaction is expected to be rejected with, empty if it is + /// expected to be valid. Lists `|`-separated alternatives, see + /// is_expected_tx_exception() in error_matching.hpp. + std::string exception; + + /// The full encoded transaction for this case. Not always available. + std::optional txbytes; }; evmc_revision rev; @@ -112,6 +120,27 @@ state::BlobParams from_json(const json::json& j); template <> BlobSchedule from_json(const json::json& j); +/// Loads the value of the JSON object's @p key, std::nullopt if the object has no such key. +template +std::optional load_optional(const json::json& j, std::string_view key) +{ + if (const auto it = j.find(key); it != j.end()) + return from_json(*it); + return std::nullopt; +} + +/// Loads the value of the JSON object's @p key, @p default_value if the object has no such key. +/// The default is spelled at the call site, {} for the zero value. +/// +/// TODO: Inline as load_optional().value_or({}) once the minimum standard library declares +/// value_or()'s parameter with a defaulted template argument. Deduced, as it is in C++20, +/// it does not accept a braced initializer. +template +T load_or(const json::json& j, std::string_view key, T default_value) +{ + return load_optional(j, key).value_or(std::move(default_value)); +} + /// Exports the State (accounts) to JSON format (aka pre/post/alloc state). json::json to_json(const TestState& state); @@ -120,10 +149,6 @@ json::json to_state_test(std::string_view test_name, const state::BlockInfo& blo state::Transaction& tx, const TestState& pre, evmc_revision rev, const std::variant& res, const TestState& post); -/// Returns the standardized error message for the transaction validation error. -[[nodiscard]] std::string get_invalid_tx_message(state::ErrorCode errc) noexcept; - - std::vector load_state_tests(std::istream& input); /// Validates the invariants of the Ethereum state (e.g. no zero-value storage entries). @@ -148,7 +173,7 @@ inline std::string hex0x(const intx::uint256& v) } /// Encodes bytes as hex with 0x prefix. -inline std::string hex0x(const bytes_view& v) +inline std::string hex0x(bytes_view v) { return "0x" + evmc::hex(v); } diff --git a/evmone/test/utils/statetest_export.cpp b/evmone/test/utils/statetest_export.cpp index ce88b0c..2ab1041 100644 --- a/evmone/test/utils/statetest_export.cpp +++ b/evmone/test/utils/statetest_export.cpp @@ -24,52 +24,6 @@ std::string_view to_test_fork_name(evmc_revision rev) noexcept } } // namespace -[[nodiscard]] std::string get_invalid_tx_message(state::ErrorCode errc) noexcept -{ - using namespace state; - switch (errc) - { - case SUCCESS: - return ""; - case INTRINSIC_GAS_TOO_LOW: - return "TR_IntrinsicGas"; - case TX_TYPE_NOT_SUPPORTED: - return "TR_TypeNotSupported"; - case INSUFFICIENT_FUNDS: - return "TR_NoFunds"; - case NONCE_HAS_MAX_VALUE: - return "TR_NonceHasMaxValue:"; - case NONCE_TOO_HIGH: - return "TR_NonceTooHigh"; - case NONCE_TOO_LOW: - return "TR_NonceTooLow"; - case TIP_GT_FEE_CAP: - return "TR_TipGtFeeCap"; - case FEE_CAP_LESS_THAN_BLOCKS: - return "TR_FeeCapLessThanBlocks"; - case GAS_LIMIT_REACHED: - return "TR_GasLimitReached"; - case SENDER_NOT_EOA: - return "SenderNotEOA"; - case INIT_CODE_SIZE_LIMIT_EXCEEDED: - return "TR_InitCodeLimitExceeded"; - case CREATE_BLOB_TX: - return "TR_BLOBCREATE"; - case EMPTY_BLOB_HASHES_LIST: - return "TR_EMPTYBLOB"; - case INVALID_BLOB_HASH_VERSION: - return "TR_BLOBVERSION_INVALID"; - case BLOB_GAS_LIMIT_EXCEEDED: - return "TR_BLOBLIST_OVERSIZE"; - case UNKNOWN_ERROR: - return "Unknown error"; - default: - assert(false); - return "Wrong error code"; - } -} - - json::json to_json(const TestState& state) { json::json j = json::json::object(); @@ -118,7 +72,9 @@ json::json to_state_test(std::string_view test_name, const state::BlockInfo& blo jtx["to"] = hex0x(*tx.to); jtx["sender"] = hex0x(tx.sender); jtx["secretKey"] = hex0x(SenderSecretKey); + jtx["chainId"] = hex0x(tx.chain_id); jtx["nonce"] = hex0x(tx.nonce); + jtx["v"] = hex0x(tx.v); if (tx.type >= Transaction::Type::eip1559) { jtx["maxFeePerGas"] = hex0x(tx.max_gas_price); @@ -165,17 +121,15 @@ json::json to_state_test(std::string_view test_name, const state::BlockInfo& blo if (!tx.authorization_list.empty()) { auto& ja = jtx["authorizationList"]; - for (const auto& [chain_id, addr, nonce, signer, r, s, y_parity] : tx.authorization_list) + for (const auto& auth : tx.authorization_list) { json::json je; - je["chainId"] = hex0x(chain_id); - je["address"] = hex0x(addr); - je["nonce"] = hex0x(nonce); - je["v"] = hex0x(y_parity); - je["r"] = hex0x(r); - je["s"] = hex0x(s); - if (signer.has_value()) - je["signer"] = hex0x(*signer); + je["chainId"] = hex0x(auth.chain_id); + je["address"] = hex0x(auth.addr); + je["nonce"] = hex0x(auth.nonce); + je["v"] = hex0x(auth.y_parity); + je["r"] = hex0x(auth.r); + je["s"] = hex0x(auth.s); ja.emplace_back(std::move(je)); } } @@ -187,8 +141,8 @@ json::json to_state_test(std::string_view test_name, const state::BlockInfo& blo if (holds_alternative(res)) { - jpost["expectException"] = get_invalid_tx_message( - static_cast(std::get(res).value())); + // The error message is the execution-spec-tests exception name. + jpost["expectException"] = std::get(res).message(); jpost["logs"] = hex0x(logs_hash(std::vector())); } else diff --git a/evmone/test/utils/statetest_loader.cpp b/evmone/test/utils/statetest_loader.cpp index 4682e0c..118fc9f 100644 --- a/evmone/test/utils/statetest_loader.cpp +++ b/evmone/test/utils/statetest_loader.cpp @@ -2,6 +2,7 @@ // Copyright 2022 The evmone Authors. // SPDX-License-Identifier: Apache-2.0 +#include "error_matching.hpp" #include "statetest.hpp" #include "stdx/utility.hpp" #include "utils.hpp" @@ -14,17 +15,6 @@ namespace evmone::test namespace json = nlohmann; using evmc::from_hex; -namespace -{ -template -T load_if_exists(const json::json& j, std::string_view key) -{ - if (const auto it = j.find(key); it != j.end()) - return from_json(*it); - return {}; -} -} // namespace - template <> uint8_t from_json(const json::json& j) { @@ -155,11 +145,9 @@ state::AuthorizationList from_json(const json::json& j authorization.chain_id = from_json(a.at("chainId")); authorization.addr = from_json
(a.at("address")); authorization.nonce = from_json(a.at("nonce")); - if (a.contains("signer")) - authorization.signer = from_json
(a["signer"]); authorization.r = from_json(a.at("r")); authorization.s = from_json(a.at("s")); - authorization.v = from_json(a.at("v")); + authorization.y_parity = from_json(a.at("v")); o.emplace_back(authorization); } return o; @@ -237,39 +225,24 @@ state::Withdrawal from_json(const json::json& j) state::BlockInfo from_json_with_rev( const json::json& j, evmc_revision rev, state::BlobParams blob_params) { - evmc::bytes32 prev_randao; - int64_t current_difficulty = 0; - int64_t parent_difficulty = 0; - const auto prev_randao_it = j.find("currentRandom"); - const auto current_difficulty_it = j.find("currentDifficulty"); - const auto parent_difficulty_it = j.find("parentDifficulty"); - - if (current_difficulty_it != j.end()) - current_difficulty = from_json(*current_difficulty_it); - if (parent_difficulty_it != j.end()) - parent_difficulty = from_json(*parent_difficulty_it); - - // When it's not defined init it with difficulty value. - if (prev_randao_it != j.end()) - prev_randao = from_json(*prev_randao_it); - else if (current_difficulty_it != j.end()) - prev_randao = from_json(*current_difficulty_it); - else if (parent_difficulty_it != j.end()) - prev_randao = from_json(*parent_difficulty_it); - - hash256 parent_uncle_hash; - const auto parent_uncle_hash_it = j.find("parentUncleHash"); - if (parent_uncle_hash_it != j.end()) - parent_uncle_hash = from_json(*parent_uncle_hash_it); + // When prev_randao is not defined init it with the difficulty value. + bytes32 prev_randao; + for (const auto key : {"currentRandom", "currentDifficulty", "parentDifficulty"}) + { + if (const auto v = load_optional(j, key)) + { + prev_randao = *v; + break; + } + } uint64_t base_fee = 0; - if (j.contains("currentBaseFee")) - base_fee = from_json(j.at("currentBaseFee")); - else if (j.contains("parentBaseFee")) + if (const auto current_base_fee = load_optional(j, "currentBaseFee")) + base_fee = *current_base_fee; + else if (const auto parent_base_fee = load_optional(j, "parentBaseFee")) { base_fee = calculate_current_base_fee_eip1559(from_json(j.at("parentGasUsed")), - from_json(j.at("parentGasLimit")), - from_json(j.at("parentBaseFee"))); + from_json(j.at("parentGasLimit")), *parent_base_fee); } std::vector withdrawals; @@ -289,42 +262,35 @@ state::BlockInfo from_json_with_rev( } } - int64_t parent_timestamp = 0; - auto parent_timestamp_it = j.find("parentTimestamp"); - if (parent_timestamp_it != j.end()) - parent_timestamp = from_json(*parent_timestamp_it); - uint64_t excess_blob_gas = 0; - if (const auto it = j.find("parentExcessBlobGas"); it != j.end()) + if (const auto parent_excess_blob_gas = load_optional(j, "parentExcessBlobGas")) { - const auto parent_excess_blob_gas = from_json(*it); const auto parent_blob_gas_used = from_json(j.at("parentBlobGasUsed")); const auto parent_base_fee = from_json(j.at("parentBaseFee")); const auto parent_blob_base_fee = - state::compute_blob_gas_price(blob_params, parent_excess_blob_gas); + state::compute_blob_gas_price(blob_params, *parent_excess_blob_gas); excess_blob_gas = state::calc_excess_blob_gas(rev, blob_params, parent_blob_gas_used, - parent_excess_blob_gas, parent_base_fee, parent_blob_base_fee); - } - else if (const auto it2 = j.find("currentExcessBlobGas"); it2 != j.end()) - { - excess_blob_gas = from_json(*it2); + *parent_excess_blob_gas, parent_base_fee, parent_blob_base_fee); } + else + excess_blob_gas = load_or(j, "currentExcessBlobGas", 0); return state::BlockInfo{ .number = from_json(j.at("currentNumber")), .timestamp = from_json(j.at("currentTimestamp")), - .parent_timestamp = parent_timestamp, + .parent_timestamp = load_or(j, "parentTimestamp", 0), .gas_limit = from_json(j.at("currentGasLimit")), .coinbase = from_json(j.at("currentCoinbase")), - .difficulty = current_difficulty, - .parent_difficulty = parent_difficulty, - .parent_ommers_hash = parent_uncle_hash, + .difficulty = load_or(j, "currentDifficulty", 0), + .parent_difficulty = load_or(j, "parentDifficulty", 0), + .parent_ommers_hash = load_or(j, "parentUncleHash", {}), .prev_randao = prev_randao, - .parent_beacon_block_root = load_if_exists(j, "parentBeaconBlockRoot"), + .parent_beacon_block_root = load_or(j, "parentBeaconBlockRoot", {}), .base_fee = base_fee, - .blob_gas_used = load_if_exists(j, "blobGasUsed"), + .blob_gas_used = load_optional(j, "blobGasUsed"), .excess_blob_gas = excess_blob_gas, .blob_base_fee = state::compute_blob_gas_price(blob_params, excess_blob_gas), + .slot_number = load_optional(j, "slotNumber"), .ommers = std::move(ommers), .withdrawals = std::move(withdrawals), }; @@ -370,13 +336,9 @@ TestState from_json(const json::json& j) static void from_json_tx_common(const json::json& j, state::Transaction& o) { // `sender` is not provided for transactions in invalid blocks. - o.sender = load_if_exists(j, "sender"); + o.sender = load_or
(j, "sender", {}); o.nonce = from_json(j.at("nonce")); - - if (const auto chain_id_it = j.find("chainId"); chain_id_it != j.end()) - o.chain_id = from_json(*chain_id_it); - else - o.chain_id = 1; + o.chain_id = load_or(j, "chainId", 1); if (const auto to_it = j.find("to"); to_it != j.end()) { @@ -452,10 +414,9 @@ state::Transaction from_json(const json::json& j) ", expected: " + std::to_string(inferred_type)); } - o.nonce = from_json(j.at("nonce")); o.r = from_json(j.at("r")); o.s = from_json(j.at("s")); - o.v = from_json(j.at("v")); + o.v = from_json(j.at("v")); return o; } @@ -480,6 +441,8 @@ static void from_json(const json::json& j, TestMultiTransaction& o) for (const auto& j_value : j.at("value")) o.values.emplace_back(from_json(j_value)); + + o.v = load_or(j, "v", 0); } static void from_json(const json::json& j, TestMultiTransaction::Indexes& o) @@ -494,7 +457,9 @@ static void from_json(const json::json& j, StateTransitionTest::Case::Expectatio o.indexes = j.at("indexes").get(); o.state_hash = from_json(j.at("hash")); o.logs_hash = from_json(j.at("logs")); - o.exception = j.contains("expectException"); + if (const auto it = j.find("expectException"); it != j.end()) + o.exception = map_legacy_exception(it->get()); + o.txbytes = load_optional(j, "txbytes"); } static void from_json(const json::json& j_t, StateTransitionTest& o) @@ -519,18 +484,22 @@ static void from_json(const json::json& j_t, StateTransitionTest& o) // LCOV_EXCL_STOP } + uint64_t chain_id = 1; if (const auto config_it = j_t.find("config"); config_it != j_t.end()) { - if (const auto bs_it = config_it->find("blobSchedule"); bs_it != config_it->end()) - o.blob_schedule = from_json(*bs_it); + o.blob_schedule = load_or(*config_it, "blobSchedule", {}); + chain_id = load_or(*config_it, "chainid", chain_id); } for (const auto& [rev_name, expectations] : j_t.at("post").items()) { - const auto blob_params = get_blob_params(to_rev(rev_name), o.blob_schedule); - o.cases.emplace_back(to_rev(rev_name), + const auto rev = to_rev(rev_name); + const auto blob_params = get_blob_params(rev, o.blob_schedule); + auto block = from_json_with_rev(j_t.at("env"), rev, blob_params); + block.chain_id = chain_id; + o.cases.emplace_back(rev, expectations.get>(), - from_json_with_rev(j_t.at("env"), to_rev(rev_name), blob_params)); + std::move(block)); } } diff --git a/evmone/test/utils/t8n.cpp b/evmone/test/utils/t8n.cpp new file mode 100644 index 0000000..dbf8a08 --- /dev/null +++ b/evmone/test/utils/t8n.cpp @@ -0,0 +1,216 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2023 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "t8n.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace evmone::tooling +{ +using JSON = nlohmann::json; +using namespace evmone::test; + +void t8n(evmc::VM& vm, const T8NArgs& args) +{ + const auto rev = args.rev; + + const auto blob_params = + (args.blob_params != nullptr) ? + from_json(JSON::parse(*args.blob_params, nullptr, false)) : + get_blob_params(rev); + + TestState state; + if (args.alloc != nullptr) + { + const auto j = JSON::parse(*args.alloc, nullptr, false); + state = from_json(j); + validate_state(state, rev); + } + + state::BlockInfo block; + TestBlockHashes block_hashes; + if (args.env != nullptr) + { + const auto j = JSON::parse(*args.env); + block = from_json_with_rev(j, rev, blob_params); + block_hashes = from_json(j); + } + block.chain_id = args.chain_id; + + JSON j_result; + + // Difficulty was received from upstream. No need to calc + // TODO: Check if it's needed by the blockchain test. If not remove if statement true branch + if (block.difficulty != 0) + { + j_result["currentDifficulty"] = hex0x(block.difficulty); + } + else + { + const auto current_difficulty = state::calculate_difficulty(block.parent_difficulty, + block.parent_ommers_hash != EmptyListHash, block.parent_timestamp, block.timestamp, + block.number, rev); + + j_result["currentDifficulty"] = hex0x(current_difficulty); + block.difficulty = current_difficulty; + + if (rev < EVMC_PARIS) // Override prev_randao with difficulty pre-Merge + block.prev_randao = intx::be::store(intx::uint256{current_difficulty}); + } + + if (rev >= EVMC_LONDON) + j_result["currentBaseFee"] = hex0x(block.base_fee); + + const auto blob_gas_limit = static_cast(state::max_blob_gas_per_block(blob_params)); + int64_t gas_used = 0; + int64_t blob_gas_left = blob_gas_limit; + std::vector transactions; + std::vector receipts; + std::vector requests; + state::BloomFilter bloom{}; + TestState post_state; + + // Parse and execute transactions + if (args.txs != nullptr) + { + const auto j_txs = JSON::parse(*args.txs); + + if (!args.opcode_count_file.empty()) + vm.set_option("opcode.count", args.opcode_count_file.c_str()); + + j_result["receipts"] = JSON::array(); + j_result["rejected"] = JSON::array(); + + // Parse the transactions, assign the chain ID and validate any provided hash. A non-array + // `txs` value yields zero transactions but still produces a full, finalized block result. + std::vector txs; + if (j_txs.is_array()) + { + txs.reserve(j_txs.size()); + for (const auto& j_tx : j_txs) + { + auto tx = from_json(j_tx); + tx.chain_id = args.chain_id; + + if (const auto loaded_tx_hash = load_optional(j_tx, "hash")) + { + const auto computed_tx_hash = keccak256(rlp::encode(tx)); + if (*loaded_tx_hash != computed_tx_hash) + throw std::logic_error("transaction hash mismatched: computed " + + hex0x(computed_tx_hash) + ", expected " + + hex0x(*loaded_tx_hash)); + } + + txs.emplace_back(std::move(tx)); + } + } + + auto res = apply_block(state, vm, block, block_hashes, txs, rev, blob_gas_limit, + {.block_reward = args.block_reward, + .skip_system_calls = args.pre_state_only, + .open_trace = args.open_trace}); + + // Build the receipts/rejected JSON lists from the partitioned result. `rejected` + // is in input order, so walk it alongside the input transactions; the rest map to + // `receipts` in block order. + std::vector txs_logs; + auto rejected_it = res.rejected.begin(); + size_t receipt_index = 0; + for (size_t i = 0; i < txs.size(); ++i) + { + if (rejected_it != res.rejected.end() && rejected_it->index == i) + { + JSON j_rejected_tx; + j_rejected_tx["hash"] = hex0x(rejected_it->hash); + j_rejected_tx["index"] = i; + j_rejected_tx["error"] = rejected_it->error.message(); + j_result["rejected"].push_back(j_rejected_tx); + ++rejected_it; + } + else + { + const auto& receipt = res.receipts[receipt_index++]; + txs_logs.insert(txs_logs.end(), receipt.logs.begin(), receipt.logs.end()); + + auto& j_receipt = j_result["receipts"][j_result["receipts"].size()]; + j_receipt["transactionHash"] = hex0x(keccak256(rlp::encode(txs[i]))); + j_receipt["gasUsed"] = hex0x(static_cast(receipt.gas_used)); + j_receipt["cumulativeGasUsed"] = hex0x(receipt.cumulative_gas_used); + j_receipt["blockHash"] = hex0x(bytes32{}); + j_receipt["contractAddress"] = hex0x(address{}); + j_receipt["logsBloom"] = hex0x(receipt.logs_bloom_filter); + j_receipt["logs"] = JSON::array(); // FIXME: Add to_json + j_receipt["root"] = ""; + j_receipt["status"] = "0x1"; + j_receipt["transactionIndex"] = hex0x(i); + transactions.emplace_back(std::move(txs[i])); + } + } + + if (res.requests_error) + // Report invalid block in the JSON result when request collection fails. + j_result["blockException"] = res.requests_error.message(); + else + requests = std::move(res.requests); + + receipts = std::move(res.receipts); + // Block gas used reported as the cumulative transaction gas (refunds excluded), + // preserving the prior t8n output. + gas_used = receipts.empty() ? 0 : receipts.back().cumulative_gas_used; + bloom = res.bloom; + blob_gas_left = res.blob_gas_left; + post_state = std::move(res.block_state); + + j_result["logsHash"] = hex0x(logs_hash(txs_logs)); + j_result["stateRoot"] = hex0x(state::mpt_hash(post_state)); + } + else + post_state = state; + + j_result["logsBloom"] = hex0x(bloom); + j_result["receiptsRoot"] = hex0x(state::mpt_hash(receipts)); + if (rev >= EVMC_SHANGHAI) + j_result["withdrawalsRoot"] = hex0x(state::mpt_hash(block.withdrawals)); + + j_result["txRoot"] = hex0x(state::mpt_hash(transactions)); + j_result["gasUsed"] = hex0x(gas_used); + if (rev >= EVMC_CANCUN) + { + j_result["blobGasUsed"] = hex0x(blob_gas_limit - blob_gas_left); + if (block.excess_blob_gas.has_value()) + j_result["currentExcessBlobGas"] = hex0x(*block.excess_blob_gas); + } + if (rev >= EVMC_PRAGUE) + { + // EIP-7685: General purpose execution layer requests + j_result["requests"] = JSON::array(); + for (const auto& r : requests) + { + if (!r.data().empty()) + // Only report non-empty requests. Include the leading type byte. + j_result["requests"].emplace_back(hex0x(r.raw_data)); + } + + auto requests_hash = calculate_requests_hash(requests); + + j_result["requestsHash"] = hex0x(requests_hash); + } + + if (args.out_result != nullptr) + *args.out_result << std::setw(2) << j_result; + if (args.out_alloc != nullptr) + *args.out_alloc << std::setw(2) << to_json(TestState{post_state}); + if (args.out_body != nullptr) + *args.out_body << hex0x(rlp::encode(transactions)); +} +} // namespace evmone::tooling diff --git a/evmone/test/utils/t8n.hpp b/evmone/test/utils/t8n.hpp new file mode 100644 index 0000000..34e95dc --- /dev/null +++ b/evmone/test/utils/t8n.hpp @@ -0,0 +1,52 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2023 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include +#include +#include +#include + +namespace evmone::tooling +{ +/// Arguments for t8n(). Streams are non-owning; the caller manages lifetime. +struct T8NArgs +{ + evmc_revision rev = {}; + uint64_t chain_id = 1; + std::optional block_reward; + bool pre_state_only = false; + + // TODO: Refactor to be filesystem-free; the VM currently opens this file itself. + std::string opcode_count_file; + + // TODO(C++26): switch the optional stream members to std::optional / + // std::optional. + std::istream* alloc = nullptr; ///< pre-state alloc JSON + std::istream* env = nullptr; ///< block env JSON + std::istream* txs = nullptr; ///< transactions JSON + std::istream* blob_params = nullptr; ///< blob schedule JSON (null = rev default) + + // All outputs are optional. t8n() skips writing to any output that is null. + std::ostream* out_result = nullptr; + std::ostream* out_alloc = nullptr; + std::ostream* out_body = nullptr; + + /// Called once per executed transaction (just before execution) to obtain + /// a per-tx trace sink; t8n() redirects std::clog to the returned stream + /// for the duration of that transaction. Unset = tracing disabled. + std::function open_trace; +}; + +/// Runs the state transition (t8n), used for JSON tests "filling". +/// +/// This command takes some JSON inputs, including a list of transactions, and produces the result +/// post-state as some JSON outputs. The specifics of the JSON formats and options are dictated +/// by execution specs, see https://steel.ethereum.foundation/docs/execution-specs. +/// +/// @param vm The VM instance. The command may modify/overwrite its config (depends on args). +/// @param args The command arguments. +void t8n(evmc::VM& vm, const T8NArgs& args); +} // namespace evmone::tooling diff --git a/evmone/test/utils/test_state.cpp b/evmone/test/utils/test_state.cpp index c8de8d0..0d3ac7a 100644 --- a/evmone/test/utils/test_state.cpp +++ b/evmone/test/utils/test_state.cpp @@ -101,14 +101,15 @@ void system_call_block_start(TestState& state, const state::BlockInfo& block, state.apply(diff); } -std::optional> system_call_block_end(TestState& state, +std::variant, std::error_code> system_call_block_end(TestState& state, const state::BlockInfo& block, const state::BlockHashes& block_hashes, evmc_revision rev, evmc::VM& vm) { auto result = state::system_call_block_end(state, block, block_hashes, rev, vm); - if (!result.has_value()) - return std::nullopt; - state.apply(result->state_diff); - return std::move(result->requests); + if (const auto* error = std::get_if(&result)) + return *error; + auto& requests_result = std::get(result); + state.apply(requests_result.state_diff); + return std::move(requests_result.requests); } } // namespace evmone::test diff --git a/evmone/test/utils/test_state.hpp b/evmone/test/utils/test_state.hpp index 93c7378..54330c7 100644 --- a/evmone/test/utils/test_state.hpp +++ b/evmone/test/utils/test_state.hpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace evmone { @@ -83,7 +84,8 @@ void system_call_block_start(TestState& state, const state::BlockInfo& block, const state::BlockHashes& block_hashes, evmc_revision rev, evmc::VM& vm); /// Wrapping of state::system_call_block_end() which operates on TestState. -std::optional> system_call_block_end(TestState& state, +/// Returns the collected requests, or the error code if the collection failed. +std::variant, std::error_code> system_call_block_end(TestState& state, const state::BlockInfo& block, const state::BlockHashes& block_hashes, evmc_revision rev, evmc::VM& vm); } // namespace test diff --git a/evmone/test/utils/utils.cpp b/evmone/test/utils/utils.cpp index 82a5cd6..1aa999b 100644 --- a/evmone/test/utils/utils.cpp +++ b/evmone/test/utils/utils.cpp @@ -13,14 +13,12 @@ evmc_revision to_rev(std::string_view s) return EVMC_FRONTIER; if (s == "Homestead") return EVMC_HOMESTEAD; - if (s == "Tangerine Whistle" || s == "EIP150") + if (s == "TangerineWhistle" || s == "EIP150") return EVMC_TANGERINE_WHISTLE; - if (s == "Spurious Dragon" || s == "EIP158") + if (s == "SpuriousDragon" || s == "EIP158") return EVMC_SPURIOUS_DRAGON; if (s == "Byzantium") return EVMC_BYZANTIUM; - if (s == "Constantinople") - return EVMC_CONSTANTINOPLE; if (s == "Petersburg" || s == "ConstantinopleFix") return EVMC_PETERSBURG; if (s == "Istanbul") @@ -39,6 +37,8 @@ evmc_revision to_rev(std::string_view s) return EVMC_PRAGUE; if (s == "Osaka") return EVMC_OSAKA; + if (s == "Amsterdam") + return EVMC_AMSTERDAM; if (s == "OsakaToBPO1AtTime15k") return EVMC_OSAKA; if (s == "BPO1ToBPO2AtTime15k") @@ -47,6 +47,8 @@ evmc_revision to_rev(std::string_view s) return EVMC_OSAKA; if (s == "BPO3ToBPO4AtTime15k") return EVMC_OSAKA; + if (s == "BPO2ToAmsterdamAtTime15k") + return EVMC_OSAKA; if (s == "Experimental") return EVMC_EXPERIMENTAL; throw std::invalid_argument{"unknown revision: " + std::string{s}}; @@ -64,6 +66,8 @@ RevisionSchedule to_rev_schedule(std::string_view s) return {EVMC_CANCUN, EVMC_PRAGUE, 15'000}; if (s == "PragueToOsakaAtTime15k") return {EVMC_PRAGUE, EVMC_OSAKA, 15'000}; + if (s == "BPO2ToAmsterdamAtTime15k") + return {EVMC_OSAKA, EVMC_AMSTERDAM, 15'000}; const auto single_rev = to_rev(s); return {single_rev, single_rev, 0}; diff --git a/evmone/tools/evmone/CMakeLists.txt b/evmone/tools/evmone/CMakeLists.txt index 238318d..21a7dd3 100644 --- a/evmone/tools/evmone/CMakeLists.txt +++ b/evmone/tools/evmone/CMakeLists.txt @@ -7,4 +7,4 @@ find_package(CLI11 CONFIG REQUIRED) add_executable(evmone-cli main.cpp) set_target_properties(evmone-cli PROPERTIES OUTPUT_NAME evmone) -target_link_libraries(evmone-cli PRIVATE evmone evmc::tooling CLI11::CLI11) +target_link_libraries(evmone-cli PRIVATE evmone evmone::testutils CLI11::CLI11) diff --git a/evmone/tools/evmone/main.cpp b/evmone/tools/evmone/main.cpp index 44bd3db..82e4c1f 100644 --- a/evmone/tools/evmone/main.cpp +++ b/evmone/tools/evmone/main.cpp @@ -4,9 +4,17 @@ #include #include -#include #include +#include +#include +#include +#include #include +#include +#include +#include + +namespace fs = std::filesystem; namespace { @@ -42,6 +50,107 @@ struct HexOrFileValidator : CLI::Validator }; } }; + +/// CLI bindings for the `t8n` subcommand. +struct T8nOptions +{ + std::string state_fork; + uint64_t state_chainid = 1; + std::optional state_reward; + fs::path alloc_file; + fs::path env_file; + fs::path txs_file; + fs::path blob_params_file; + fs::path output_dir; + fs::path output_result_file; + fs::path output_alloc_file; + fs::path output_body_file; + fs::path opcode_count_filename; + bool trace = false; +}; + +const CLI::App& setup_t8n_cmd(CLI::App& app, T8nOptions& opts) +{ + auto& cmd = *app.add_subcommand("t8n", "Run Ethereum state transition (EELS t8n protocol)"); + cmd.add_option("--state.fork", opts.state_fork, "Active EVM revision")->required(); + cmd.add_option("--state.chainid", opts.state_chainid, "Chain ID (decimal or 0x-prefixed hex)"); + cmd.add_option( + "--state.reward", opts.state_reward, "Block reward in wei (-1 to output pre-state only)") + ->check(CLI::Range(int64_t{-1}, std::numeric_limits::max())); + cmd.add_option("--input.alloc", opts.alloc_file, "Pre-state alloc JSON") + ->check(CLI::ExistingFile); + cmd.add_option("--input.env", opts.env_file, "Block env JSON")->check(CLI::ExistingFile); + cmd.add_option("--input.txs", opts.txs_file, "Transactions JSON")->check(CLI::ExistingFile); + cmd.add_option("--input.blobParams", opts.blob_params_file, "Blob schedule JSON") + ->check(CLI::ExistingFile); + cmd.add_option("--output.basedir", opts.output_dir, "Output base directory"); + cmd.add_option("--output.result", opts.output_result_file, "Result JSON filename"); + cmd.add_option("--output.alloc", opts.output_alloc_file, "Post-state alloc JSON filename"); + cmd.add_option("--output.body", opts.output_body_file, "RLP-encoded transactions filename"); + cmd.add_flag("--trace", opts.trace, "Write per-tx execution traces under --output.basedir"); + cmd.add_option( + "--opcode.count", opts.opcode_count_filename, "Per-opcode count output filename"); + return cmd; +} + +int exec_t8n_cmd(evmc::VM& vm, const T8nOptions& opts) +{ + evmone::tooling::T8NArgs args; + args.rev = evmone::test::to_rev(opts.state_fork); + args.chain_id = opts.state_chainid; + if (opts.state_reward) + { + if (*opts.state_reward == -1) + args.pre_state_only = true; + else + args.block_reward = static_cast(*opts.state_reward); + } + + if (!opts.output_dir.empty()) + fs::create_directories(opts.output_dir); + if (!opts.opcode_count_filename.empty()) + args.opcode_count_file = (opts.output_dir / opts.opcode_count_filename).string(); + + std::ifstream in_alloc; + std::ifstream in_env; + std::ifstream in_txs; + std::ifstream in_blob_params; + std::ofstream out_result; + std::ofstream out_alloc; + std::ofstream out_body; + std::ofstream trace_file; + + const auto bind_stream = [](auto& s, const fs::path& p) -> decltype(&s) { + if (p.empty()) + return nullptr; + s.open(p); + return &s; + }; + const auto output_path = [&](const fs::path& name) -> fs::path { + return name.empty() ? fs::path{} : opts.output_dir / name; + }; + + args.alloc = bind_stream(in_alloc, opts.alloc_file); + args.env = bind_stream(in_env, opts.env_file); + args.txs = bind_stream(in_txs, opts.txs_file); + args.blob_params = bind_stream(in_blob_params, opts.blob_params_file); + args.out_result = bind_stream(out_result, output_path(opts.output_result_file)); + args.out_alloc = bind_stream(out_alloc, output_path(opts.output_alloc_file)); + args.out_body = bind_stream(out_body, output_path(opts.output_body_file)); + + if (opts.trace) + { + args.open_trace = [&](size_t tx_index, const evmc::bytes32& tx_hash) -> std::ostream& { + trace_file = + std::ofstream{opts.output_dir / ("trace-" + std::to_string(tx_index) + "-0x" + + evmc::hex(tx_hash) + ".jsonl")}; + return trace_file; + }; + } + + evmone::tooling::t8n(vm, args); + return 0; +} } // namespace int main(int argc, const char* const* argv) noexcept @@ -61,8 +170,11 @@ int main(int argc, const char* const* argv) noexcept auto trace = false; auto histogram = false; + VM vm{evmc_create_evmone()}; + CLI::App app{"evmone EVM tool"}; - const auto& version_flag = *app.add_flag("--version", "Print version information and exit"); + app.set_version_flag( + "--version", [&vm] { return std::string{vm.name()} + " " + vm.version(); }); app.add_flag("--trace", trace, "Enable execution trace"); app.add_flag("--histogram", histogram, "Enable opcode histogram"); @@ -71,39 +183,40 @@ int main(int argc, const char* const* argv) noexcept run_cmd.add_option("--gas", gas, "Execution gas limit") ->capture_default_str() ->check(CLI::Range(0, 1000000000)); - run_cmd.add_option("--rev", rev, "EVM revision")->capture_default_str(); + run_cmd + .add_option_function( + "--rev", [&rev](const std::string& name) { rev = evmone::test::to_rev(name); }, + "EVM revision name") + ->default_str(evmc::to_string(rev)); run_cmd.add_option("--input", input_arg, "Input bytes")->check(HexOrFile); run_cmd.add_flag("--create", create, "Create new contract out of the code and then execute this contract with the input"); run_cmd.add_flag("--bench", bench, "Benchmark execution time (state modification may result in unexpected behaviour)"); + T8nOptions t8n_opts; + const auto& t8n_cmd = setup_t8n_cmd(app, t8n_opts); + try { app.parse(argc, argv); - VM vm{evmc_create_evmone()}; - if (trace) vm.set_option("trace", ""); if (histogram) vm.set_option("histogram", ""); - // Handle the --version flag first and exit when present. - if (version_flag) - { - std::cout << vm.name() << " " << vm.version() << "\n"; - return 0; - } - if (run_cmd) { // If code_arg or input_arg contains invalid hex string, an exception is thrown. const auto code = load_from_hex(code_arg); const auto input = load_from_hex(input_arg); - return tooling::run(vm, rev, gas, code, input, create, bench, std::cout); + return evmone::tooling::run(vm, rev, gas, code, input, create, bench, std::cout); } + if (t8n_cmd) + return exec_t8n_cmd(vm, t8n_opts); + return 0; } catch (const CLI::ParseError& e)