diff --git a/circle.yml b/circle.yml index 84ac26d28d..d206f50575 100644 --- a/circle.yml +++ b/circle.yml @@ -470,13 +470,13 @@ jobs: working_directory: ~/build command: > bin/evmone-statetest ~/spec-tests/fixtures/state_tests/ - --gtest_filter='-cancun/eip4844_blobs/blob_txs.invalid_tx_blob_count:frontier/precompiles/precompiles.precompiles:osaka/eip7594_peerdas/max_blob_per_tx.invalid_max_blobs_per_tx:osaka/eip7825_transaction_gas_limit_cap/tx_gas_limit.transaction_gas_limit_cap:osaka/eip7939_count_leading_zeros/*.*:osaka/eip7951_p256verify_precompiles/*.*:prague/eip7702_set_code_tx/set_code_txs.set_code_to_precompile' + --gtest_filter='-cancun/eip4844_blobs/blob_txs.invalid_tx_blob_count:frontier/precompiles/precompiles.precompiles:osaka/eip7594_peerdas/max_blob_per_tx.invalid_max_blobs_per_tx:osaka/eip7825_transaction_gas_limit_cap/tx_gas_limit.transaction_gas_limit_cap:osaka/eip7951_p256verify_precompiles/*.*:prague/eip7702_set_code_tx/set_code_txs.set_code_to_precompile' - run: name: "Fusaka pre-release execution spec tests (blockchain_tests)" working_directory: ~/build command: > bin/evmone-blockchaintest ~/spec-tests/fixtures/blockchain_tests/ - --gtest_filter='-cancun/eip4844_blobs/blob_txs.valid_blob_tx_combinations:frontier/precompiles/precompiles.precompiles:osaka/eip7594_peerdas/max_blob_per_tx.max_blobs_per_tx_fork_transition:osaka/eip7825_transaction_gas_limit_cap/tx_gas_limit.transaction_gas_limit_cap_at_transition:osaka/eip7918_blob_reserve_price/*.*:osaka/eip7934_block_rlp_limit/max_block_rlp_size.block_at_rlp_size_limit_boundary:osaka/eip7939_count_leading_zeros/*.*:osaka/eip7951_p256verify_precompiles/*.*:prague/eip7702_set_code_tx/set_code_txs.set_code_to_precompile' + --gtest_filter='-cancun/eip4844_blobs/blob_txs.valid_blob_tx_combinations:frontier/precompiles/precompiles.precompiles:osaka/eip7594_peerdas/max_blob_per_tx.max_blobs_per_tx_fork_transition:osaka/eip7825_transaction_gas_limit_cap/tx_gas_limit.transaction_gas_limit_cap_at_transition:osaka/eip7918_blob_reserve_price/*.*:osaka/eip7934_block_rlp_limit/max_block_rlp_size.block_at_rlp_size_limit_boundary:osaka/eip7951_p256verify_precompiles/*.*:prague/eip7702_set_code_tx/set_code_txs.set_code_to_precompile' - collect_coverage_clang - upload_coverage: flags: eest-fusaka diff --git a/lib/evmone/instructions.hpp b/lib/evmone/instructions.hpp index e3e6bb24b8..abda0115ca 100644 --- a/lib/evmone/instructions.hpp +++ b/lib/evmone/instructions.hpp @@ -352,6 +352,11 @@ inline void sar(StackTop stack) noexcept x = (x >> y) | (sign_mask << mask_shift); } +inline void clz(StackTop stack) noexcept +{ + stack.top() = clz(stack.top()); +} + inline Result keccak256(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept { const auto& index = stack.pop(); diff --git a/lib/evmone/instructions_opcodes.hpp b/lib/evmone/instructions_opcodes.hpp index 1c5db3e48b..4265427c55 100644 --- a/lib/evmone/instructions_opcodes.hpp +++ b/lib/evmone/instructions_opcodes.hpp @@ -41,6 +41,7 @@ enum Opcode : uint8_t OP_SHL = 0x1b, OP_SHR = 0x1c, OP_SAR = 0x1d, + OP_CLZ = 0x1e, OP_KECCAK256 = 0x20, diff --git a/lib/evmone/instructions_traits.hpp b/lib/evmone/instructions_traits.hpp index b1ffe59c61..de842ac961 100644 --- a/lib/evmone/instructions_traits.hpp +++ b/lib/evmone/instructions_traits.hpp @@ -173,6 +173,7 @@ constexpr inline GasCostTable gas_costs = []() noexcept { table[EVMC_PRAGUE] = table[EVMC_CANCUN]; table[EVMC_OSAKA] = table[EVMC_PRAGUE]; + table[EVMC_OSAKA][OP_CLZ] = 5; table[EVMC_EXPERIMENTAL] = table[EVMC_OSAKA]; @@ -281,6 +282,7 @@ constexpr inline std::array traits = []() noexcept { table[OP_SHL] = {"SHL", 0, false, 2, -1, EVMC_CONSTANTINOPLE, REV_EOF1}; table[OP_SHR] = {"SHR", 0, false, 2, -1, EVMC_CONSTANTINOPLE, REV_EOF1}; table[OP_SAR] = {"SAR", 0, false, 2, -1, EVMC_CONSTANTINOPLE, REV_EOF1}; + table[OP_CLZ] = {"CLZ", 0, false, 1, 0, EVMC_OSAKA, REV_EOF1}; table[OP_KECCAK256] = {"KECCAK256", 0, false, 2, -1, EVMC_FRONTIER, REV_EOF1}; diff --git a/lib/evmone/instructions_xmacro.hpp b/lib/evmone/instructions_xmacro.hpp index 92b92c9439..c7ff292011 100644 --- a/lib/evmone/instructions_xmacro.hpp +++ b/lib/evmone/instructions_xmacro.hpp @@ -63,7 +63,7 @@ ON_OPCODE_IDENTIFIER(OP_SHL, shl) \ ON_OPCODE_IDENTIFIER(OP_SHR, shr) \ ON_OPCODE_IDENTIFIER(OP_SAR, sar) \ - ON_OPCODE_UNDEFINED(0x1e) \ + ON_OPCODE_IDENTIFIER(OP_CLZ, clz) \ ON_OPCODE_UNDEFINED(0x1f) \ \ ON_OPCODE_IDENTIFIER(OP_KECCAK256, keccak256) \ diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index 116ee60ea7..01a84c67e1 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -29,6 +29,7 @@ target_sources( evm_eip3860_initcode_test.cpp evm_eip4844_blobhash_test.cpp evm_eip7516_blobbasefee_test.cpp + evm_eip7939_clz_test.cpp evm_eof_test.cpp evm_eof_calls_test.cpp evm_eof_function_test.cpp diff --git a/test/unittests/evm_eip7939_clz_test.cpp b/test/unittests/evm_eip7939_clz_test.cpp new file mode 100644 index 0000000000..0af27579ff --- /dev/null +++ b/test/unittests/evm_eip7939_clz_test.cpp @@ -0,0 +1,56 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2025 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +/// This file contains EVM unit tests for EIP-7939 "Count leading zeros (CLZ) opcode" +/// https://eips.ethereum.org/EIPS/eip-7939 + +#include "evm_fixture.hpp" + +using namespace evmc::literals; +using namespace evmone::test; + +TEST_P(evm, clz_pre_osaka) +{ + rev = EVMC_PRAGUE; + const auto code = bytecode{OP_CLZ}; + + execute(code); + EXPECT_STATUS(EVMC_UNDEFINED_INSTRUCTION); +} + +TEST_P(evm, clz_gas) +{ + rev = EVMC_OSAKA; + execute(bytecode{} + OP_PUSH0 + OP_CLZ); + EXPECT_GAS_USED(EVMC_SUCCESS, 2 + 5); +} + +TEST_P(evm, clz_osaka) +{ + rev = EVMC_OSAKA; + const std::vector> cases{ + {0, 256}, + {1, 255}, + {0x8000000000000000000000000000000000000000000000000000000000000000_bytes32, 0}, + {0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_bytes32, 0}, + {0x4000000000000000000000000000000000000000000000000000000000000000_bytes32, 1}, + {0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_bytes32, 1}, + {0x0000000000000000000000000000000100000000000000000000000000000000_bytes32, 127}, + {0x0000000000000000000000000000000000000000000000010000000000000000_bytes32, 191}, + {0x0000000000000000000000000000000000000000000000008000000000000000_bytes32, 192}, + }; + for (const auto& [input, expected_output] : cases) + { + execute(clz(input) + ret_top()); + EXPECT_GAS_USED(EVMC_SUCCESS, 23); + EXPECT_OUTPUT_INT(expected_output); + } +} + +TEST_P(evm, clz_stack_underflow) +{ + rev = EVMC_OSAKA; + execute(OP_CLZ); + EXPECT_STATUS(EVMC_STACK_UNDERFLOW); +} diff --git a/test/unittests/instructions_test.cpp b/test/unittests/instructions_test.cpp index bff1273e13..ab77263188 100644 --- a/test/unittests/instructions_test.cpp +++ b/test/unittests/instructions_test.cpp @@ -115,6 +115,7 @@ constexpr bool instruction_only_in_evmone(evmc_revision rev, Opcode op) noexcept switch (op) { + case OP_CLZ: case OP_BLOBHASH: case OP_BLOBBASEFEE: case OP_RJUMP: diff --git a/test/utils/bytecode.hpp b/test/utils/bytecode.hpp index 28df1ef9d7..515f2b0021 100644 --- a/test/utils/bytecode.hpp +++ b/test/utils/bytecode.hpp @@ -296,6 +296,11 @@ inline bytecode eq(bytecode a, bytecode b) return b + a + OP_EQ; } +inline bytecode clz(bytecode a) +{ + return a + OP_CLZ; +} + inline bytecode byte(bytecode a, bytecode n) { return a + n + OP_BYTE; diff --git a/test/utils/utils.cpp b/test/utils/utils.cpp index c6270167a8..4e6e83f622 100644 --- a/test/utils/utils.cpp +++ b/test/utils/utils.cpp @@ -56,6 +56,8 @@ RevisionSchedule to_rev_schedule(std::string_view s) return {EVMC_SHANGHAI, EVMC_CANCUN, 15'000}; if (s == "CancunToPragueAtTime15k") return {EVMC_CANCUN, EVMC_PRAGUE, 15'000}; + if (s == "PragueToOsakaAtTime15k") + return {EVMC_PRAGUE, EVMC_OSAKA, 15'000}; const auto single_rev = to_rev(s); return {single_rev, single_rev, 0};