From c391c64acd069ee04fc92be868208df6df346495 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Tue, 9 Jan 2024 15:23:00 +0100 Subject: [PATCH 1/8] evmmax: Add evmmax opcodes test: Omit new opcodes with immediate params in unit tests --- lib/evmone/advanced_instructions.cpp | 4 ++++ lib/evmone/instructions_opcodes.hpp | 6 ++++++ lib/evmone/instructions_traits.hpp | 11 +++++++++-- test/unittests/eof_validation_test.cpp | 3 ++- test/unittests/instructions_test.cpp | 8 ++++++++ 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/evmone/advanced_instructions.cpp b/lib/evmone/advanced_instructions.cpp index 562663cfc4..dfd54458fc 100644 --- a/lib/evmone/advanced_instructions.cpp +++ b/lib/evmone/advanced_instructions.cpp @@ -312,6 +312,10 @@ constexpr std::array instruction_implementations = []( table[OP_EOFCREATE] = op_undefined; table[OP_RETURNCONTRACT] = op_undefined; + table[OP_ADDMODX] = op_undefined; + table[OP_SUBMODX] = op_undefined; + table[OP_MULMODX] = op_undefined; + return table; }(); } // namespace diff --git a/lib/evmone/instructions_opcodes.hpp b/lib/evmone/instructions_opcodes.hpp index 6f021f4bd5..984ea7df94 100644 --- a/lib/evmone/instructions_opcodes.hpp +++ b/lib/evmone/instructions_opcodes.hpp @@ -43,6 +43,12 @@ enum Opcode : uint8_t OP_SAR = 0x1d, OP_KECCAK256 = 0x20, + OP_SETUPX = 0x21, + OP_ADDMODX = 0x22, + OP_SUBMODX = 0x23, + OP_MULMODX = 0x24, + OP_LOADX = 0x25, + OP_STOREX = 0x26, OP_ADDRESS = 0x30, OP_BALANCE = 0x31, diff --git a/lib/evmone/instructions_traits.hpp b/lib/evmone/instructions_traits.hpp index c2a7183616..7955ee1a0f 100644 --- a/lib/evmone/instructions_traits.hpp +++ b/lib/evmone/instructions_traits.hpp @@ -18,6 +18,7 @@ inline constexpr auto cold_sload_cost = 2100; inline constexpr auto cold_account_access_cost = 2600; inline constexpr auto warm_storage_read_cost = 100; +static constexpr auto EVMC_EVMMAX = evmc_revision::EVMC_PRAGUE; /// Additional cold account access cost. /// /// The warm access cost is unconditionally applied for every account access instruction. @@ -193,6 +194,13 @@ constexpr inline GasCostTable gas_costs = []() noexcept { table[EVMC_OSAKA][OP_EOFCREATE] = 32000; table[EVMC_OSAKA][OP_RETURNCONTRACT] = 0; + table[EVMC_EVMMAX][OP_SETUPX] = 3; + table[EVMC_EVMMAX][OP_ADDMODX] = 0; + table[EVMC_EVMMAX][OP_SUBMODX] = 0; + table[EVMC_EVMMAX][OP_MULMODX] = 0; + table[EVMC_EVMMAX][OP_LOADX] = 3; + table[EVMC_EVMMAX][OP_STOREX] = 3; + return table; }(); @@ -245,7 +253,6 @@ consteval bool has_const_gas_cost(Opcode op) noexcept return true; } - /// The global, EVM revision independent, table of traits of all known EVM instructions. constexpr inline std::array traits = []() noexcept { std::array table{}; @@ -278,7 +285,7 @@ constexpr inline std::array traits = []() noexcept { 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_KECCAK256] = {"KECCAK256", 0, false, 2, -1, EVMC_FRONTIER, REV_EOF1}; + table[OP_KECCAK256] = {"KECCAK256", 0, false, 2, -1, EVMC_FRONTIER}; table[OP_ADDRESS] = {"ADDRESS", 0, false, 0, 1, EVMC_FRONTIER, REV_EOF1}; table[OP_BALANCE] = {"BALANCE", 0, false, 1, 0, EVMC_FRONTIER, REV_EOF1}; diff --git a/test/unittests/eof_validation_test.cpp b/test/unittests/eof_validation_test.cpp index faf15c4ceb..9c2afa3621 100644 --- a/test/unittests/eof_validation_test.cpp +++ b/test/unittests/eof_validation_test.cpp @@ -307,7 +307,8 @@ TEST_F(eof_validation, EOF1_undefined_opcodes) opcode == OP_SWAPN || opcode == OP_EXCHANGE || opcode == OP_RJUMP || opcode == OP_RJUMPI || opcode == OP_CALLF || opcode == OP_RJUMPV || opcode == OP_DATALOADN || opcode == OP_JUMPF || opcode == OP_EOFCREATE || - opcode == OP_RETURNCONTRACT) + opcode == OP_RETURNCONTRACT || opcode == OP_ADDMODX || opcode == OP_SUBMODX || + opcode == OP_MULMODX) continue; // These opcodes are deprecated since Osaka. // gas_cost table current implementation does not allow to undef instructions. diff --git a/test/unittests/instructions_test.cpp b/test/unittests/instructions_test.cpp index 244dd872d3..48b29951e4 100644 --- a/test/unittests/instructions_test.cpp +++ b/test/unittests/instructions_test.cpp @@ -67,6 +67,8 @@ constexpr void validate_traits_of() noexcept static_assert(tr.immediate_size == 1); else if constexpr (Op == OP_DATALOADN) static_assert(tr.immediate_size == 2); + else if constexpr (Op == OP_ADDMODX || Op == OP_SUBMODX || Op == OP_MULMODX) + static_assert(tr.immediate_size == 3); else static_assert(tr.immediate_size == 0); // Including RJUMPV. @@ -114,6 +116,12 @@ constexpr bool instruction_only_in_evmone(evmc_revision rev, Opcode op) noexcept switch (op) { + case OP_SETUPX: + case OP_ADDMODX: + case OP_SUBMODX: + case OP_MULMODX: + case OP_LOADX: + case OP_STOREX: case OP_BLOBHASH: case OP_BLOBBASEFEE: case OP_RJUMP: From 609e64c972e24fb38e19e1d8a49a15c8274f8a96 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Tue, 9 Jan 2024 13:47:09 +0100 Subject: [PATCH 2/8] evmmax: Implement evmmax state and modulus classes --- include/evmmax/evmmax.hpp | 76 ++++ lib/evmmax/evmmax.cpp | 439 ++++++++++++++++++++++++ test/internal_benchmarks/CMakeLists.txt | 2 +- 3 files changed, 516 insertions(+), 1 deletion(-) create mode 100644 lib/evmmax/evmmax.cpp diff --git a/include/evmmax/evmmax.hpp b/include/evmmax/evmmax.hpp index 70feec2bc3..3a69b0d76f 100644 --- a/include/evmmax/evmmax.hpp +++ b/include/evmmax/evmmax.hpp @@ -3,11 +3,87 @@ // SPDX-License-Identifier: Apache-2.0 #pragma once +#include #include +#include namespace evmmax { +using intx::uint256; + +struct EXMMAXModStateInterface; + +/// Ephemeral EVMMAX (EVM Modular Arithmetic Extensions) state +class EVMMAXState +{ + typedef std::unordered_map> ModulusMap; + ModulusMap mods; ///< Map of initialized and available moduluses. + ModulusMap::const_iterator active_mod = mods.end(); ///< Current active modulus + + /// Validates that memory used by EVMMAX state does not exceed a predefined limit. + [[nodiscard]] bool validate_memory_usage(size_t val_size, size_t num_val) noexcept; + +public: + /// Create new modulus and activates it. In case the modulus already exists, activates it. + /// Deducts gas accordingly. + /// + /// \param gas_left Amount of gas before calling. Is modified by `setupx` + /// \param mod_id Modulus identifier + /// \param mod_ptr Modulus big endian value memory pointer + /// \param mod_size Modulus size in bytes + /// \param vals_used Number of needed value slots + /// \return Status code. + [[nodiscard]] evmc_status_code setupx(int64_t& gas_left, const uint256& mod_id, + const uint8_t* mod_ptr, size_t mod_size, size_t vals_used) noexcept; + + /// Loads EVMMAX values into EVM memory. Deducts gas accordingly. + /// Converts to the Montgomery form + [[nodiscard]] evmc_status_code loadx( + int64_t& gas_left, uint8_t* out_ptr, size_t val_idx, size_t num_vals) noexcept; + + /// Stores EVM memory into EVMMAX value slots. Deducts gas accordingly. + /// Converts from the Montgomery form + [[nodiscard]] evmc_status_code storex( + int64_t& gas_left, const uint8_t* in_ptr, size_t dst_val_idx, size_t num_vals) noexcept; + + /// Computes modular addition. Deducts gas accordingly. Operates on active modulus. + /// + /// (x + y) % active_modulus. + /// Gets inputs from values slots under indexes `x_idx` and `y_idx`. + /// Saves result in value slot under index `dst_idx` + [[nodiscard]] evmc_status_code addmodx( + int64_t& gas_left, size_t dst_idx, size_t x_idx, size_t y_idx) noexcept; + + /// Computes modular subtraction. Deducts gas accordingly. Operates on active modulus. + /// + /// (x - y) % active_modulus. + /// Gets inputs from values slots under indexes `x_idx` and `y_idx`. + /// Saves result in value slot under index `dst_idx` + [[nodiscard]] evmc_status_code submodx( + int64_t& gas_left, size_t dst_idx, size_t x_idx, size_t y_idx) noexcept; + + /// Computes modular multiplication. Deducts gas accordingly. Operates on active modulus. + /// + /// (x * y) % active_modulus. + /// Gets inputs from values slots under indexes `x_idx` and `y_idx`. + /// Saves result in value slot under index `dst_idx` + [[nodiscard]] evmc_status_code mulmodx( + int64_t& gas_left, size_t dst_idx, size_t x_idx, size_t y_idx) noexcept; + + /// Checks that modulus with `mod_id` exists. + [[nodiscard]] bool exists(const uint256& mod_id) const noexcept; + + /// Returns active modulus size multiplier. + /// Size (expressed in multiples of 8 bytes) needed to represent modulus. + [[nodiscard]] size_t active_mod_value_size_multiplier() const noexcept; + + void clear() noexcept; + + explicit EVMMAXState() noexcept; + virtual ~EVMMAXState(); +}; + /// The modular arithmetic operations for EVMMAX (EVM Modular Arithmetic Extensions). template class ModArith diff --git a/lib/evmmax/evmmax.cpp b/lib/evmmax/evmmax.cpp new file mode 100644 index 0000000000..81e1afe52f --- /dev/null +++ b/lib/evmmax/evmmax.cpp @@ -0,0 +1,439 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2023 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include + +using namespace intx; + +namespace evmmax +{ +struct EXMMAXModStateInterface +{ + virtual void loadx(uint8_t* out_ptr, size_t val_idx, size_t num_vals) const noexcept = 0; + virtual void storex(const uint8_t* in_ptr, size_t dst_val_idx, size_t num_vals) noexcept = 0; + virtual void addmodx(size_t dst_idx, size_t x_idx, size_t y_idx) noexcept = 0; + virtual void submodx(size_t dst_idx, size_t x_idx, size_t y_idx) noexcept = 0; + virtual void mulmodx(size_t dst_idx, size_t x_idx, size_t y_idx) noexcept = 0; + + [[nodiscard]] virtual size_t value_size_multiplier() const noexcept = 0; + [[nodiscard]] virtual int64_t addmodx_gas_cost() const noexcept = 0; + [[nodiscard]] virtual int64_t mulmodx_gas_cost() const noexcept = 0; + [[nodiscard]] virtual size_t num_values() const noexcept = 0; + + virtual ~EXMMAXModStateInterface() noexcept = default; +}; + +namespace +{ +/// Copy of intx::be::unsafe::load but with additional src size parameter +template +inline IntT load(const uint8_t* src, size_t src_size) noexcept +{ + // Align bytes. + // TODO: Using memcpy() directly triggers this optimization bug in GCC: + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107837 + alignas(IntT) std::byte aligned_storage[sizeof(IntT)] = {}; + std::memcpy(&aligned_storage[sizeof(IntT) - src_size], src, src_size); + // TODO(C++23): Use std::start_lifetime_as(). + return to_big_endian(*reinterpret_cast(&aligned_storage)); +} + +template +inline void store(uint8_t* dst, const T& x, size_t dst_size) noexcept +{ + const auto d = to_big_endian(x); + std::memcpy(dst, &reinterpret_cast(&d)[sizeof(T) - dst_size], dst_size); +} + +inline constexpr int64_t compute_addmodx_cost(size_t value_size_mult) noexcept +{ + constexpr int64_t ADDMODX_GAS_A = 20; + constexpr int64_t ADDMODX_GAS_B = 15; + + return (ADDMODX_GAS_A * static_cast(value_size_mult) + ADDMODX_GAS_B + 49) / 100; +} + +inline constexpr int64_t compute_mulmodx_cost(size_t value_size_mult) noexcept +{ + constexpr int64_t MULMODX_LO_GAS_A = 9; + constexpr int64_t MULMODX_LO_GAS_B = 0; + constexpr int64_t MULMODX_LO_GAS_C = 24; + + return (MULMODX_LO_GAS_A * static_cast(value_size_mult * value_size_mult) + + MULMODX_LO_GAS_B * static_cast(value_size_mult) + MULMODX_LO_GAS_C + 99) / + 100; +} + +template +struct EXMMAXModState : public EXMMAXModStateInterface +{ + std::vector values; + const ModArith arith; + const size_t value_size_mult; + const int64_t addmodx_cost; + const int64_t mulmodx_cost; + + explicit EXMMAXModState(const UintT& mod, size_t mod_size, size_t vals_used) noexcept + : arith(mod), + value_size_mult((mod_size + 7) / 8), + addmodx_cost(compute_addmodx_cost(value_size_mult)), + mulmodx_cost(compute_mulmodx_cost(value_size_mult)) + { + values.resize(vals_used); + } + + void loadx(uint8_t* out_ptr, size_t val_idx, size_t num_vals) const noexcept override + { + assert(val_idx + num_vals <= values.size()); + + for (unsigned i = 0; i < num_vals; ++i) + { + store(out_ptr + i * value_size_mult * 8, arith.from_mont(values[val_idx + i]), + value_size_mult * 8); + } + } + + void storex(const uint8_t* in_ptr, size_t dst_val_idx, size_t num_vals) noexcept override + { + assert(dst_val_idx + num_vals <= values.size()); + + for (unsigned i = 0; i < num_vals; ++i) + { + values[dst_val_idx + i] = + arith.to_mont(load(in_ptr + value_size_mult * 8 * i, value_size_mult * 8)); + } + } + + void addmodx(size_t dst_idx, size_t x_idx, size_t y_idx) noexcept override + { + assert(dst_idx < values.size() && x_idx < values.size() && y_idx < values.size()); + + values[dst_idx] = arith.add(values[x_idx], values[y_idx]); + } + + void submodx(size_t dst_idx, size_t x_idx, size_t y_idx) noexcept override + { + assert(dst_idx < values.size() && x_idx < values.size() && y_idx < values.size()); + + values[dst_idx] = arith.sub(values[x_idx], values[y_idx]); + } + + void mulmodx(size_t dst_idx, size_t x_idx, size_t y_idx) noexcept override + { + assert(dst_idx < values.size() && x_idx < values.size() && y_idx < values.size()); + + values[dst_idx] = arith.mul(values[x_idx], values[y_idx]); + } + + [[nodiscard]] size_t num_values() const noexcept override { return values.size(); } + [[nodiscard]] size_t value_size_multiplier() const noexcept override { return value_size_mult; } + [[nodiscard]] int64_t addmodx_gas_cost() const noexcept override { return addmodx_cost; } + [[nodiscard]] int64_t mulmodx_gas_cost() const noexcept override { return mulmodx_cost; } +}; + +[[nodiscard]] std::unique_ptr create_mod_state( + const uint8_t* mod_ptr, size_t mod_size, size_t vals_used) noexcept +{ + // Must be odd. + assert((mod_ptr[mod_size - 1] & 1) == 1); + // Max mod size must be <= 4096 bits + assert(mod_size <= 512); + + if (mod_size <= 16) + { + return std::make_unique>>( + load>(mod_ptr, mod_size), mod_size, vals_used); + } + else if (mod_size <= 24) + { + return std::make_unique>>( + load>(mod_ptr, mod_size), mod_size, vals_used); + } + else if (mod_size <= 32) + { + return std::make_unique>( + load(mod_ptr, mod_size), mod_size, vals_used); + } + else if (mod_size <= 40) + { + return std::make_unique>( + load(mod_ptr, mod_size), mod_size, vals_used); + } + else if (mod_size <= 48) + { + return std::make_unique>( + load(mod_ptr, mod_size), mod_size, vals_used); + } + else if (mod_size <= 56) + { + return std::make_unique>>( + load>(mod_ptr, mod_size), mod_size, vals_used); + } + else if (mod_size <= 64) + { + return std::make_unique>( + load(mod_ptr, mod_size), mod_size, vals_used); + } + else + { + /// TODO: Implement for intermediate `mod_size` values up to 512 bytes + return std::make_unique>>( + load>(mod_ptr, mod_size), mod_size, vals_used); + } +} + +[[nodiscard]] bool charge_gas_precompute_mont(int64_t& gas_left, size_t mod_size) noexcept +{ + // TODO: Set proper values for A and B + static constexpr int64_t A = 1; + static constexpr int64_t B = 5; + + const size_t val_size_multiplier = (mod_size + 7) / 8; + + if (val_size_multiplier < 50) + gas_left -= A * static_cast(val_size_multiplier) + B; + else + { + // TODO: Add support for subquadratic mulmont cost model + assert(false); + return false; + } + + return gas_left >= 0; +} + +} // namespace + +[[nodiscard]] bool EVMMAXState::exists(const intx::uint256& mod_id) const noexcept +{ + // TODO: Add support for uint256 to be a key in std::unordered_map + const auto mod_id_bytes = intx::be::store(mod_id); + + return mods.contains(mod_id_bytes); +} + +[[nodiscard]] evmc_status_code EVMMAXState::setupx(int64_t& gas_left, const uint256& mod_id, + const uint8_t* mod_ptr, size_t mod_size, size_t vals_used) noexcept +{ + // TODO: Add support for uint256 to be a key in std::unordered_map + const auto mod_id_bytes = intx::be::store(mod_id); + + if (active_mod != mods.end() && active_mod->first == mod_id_bytes) + return EVMC_SUCCESS; + + active_mod = mods.find(mod_id_bytes); + if (active_mod != mods.end()) + return EVMC_SUCCESS; + + if (!validate_memory_usage(mod_size, vals_used)) + return EVMC_FAILURE; + + if (!charge_gas_precompute_mont(gas_left, mod_size)) + return EVMC_OUT_OF_GAS; + + active_mod = mods.emplace(mod_id_bytes, create_mod_state(mod_ptr, mod_size, vals_used)).first; + return EVMC_SUCCESS; +} + +[[nodiscard]] evmc_status_code EVMMAXState::loadx( + int64_t& gas_left, uint8_t* out_ptr, size_t val_idx, size_t num_vals) noexcept +{ + if (active_mod == mods.end()) + return EVMC_FAILURE; + + if ((gas_left -= active_mod->second->mulmodx_gas_cost() * static_cast(num_vals)) < 0) + return EVMC_OUT_OF_GAS; + + active_mod->second->loadx(out_ptr, val_idx, num_vals); + return EVMC_SUCCESS; +} + +[[nodiscard]] evmc_status_code EVMMAXState::storex( + int64_t& gas_left, const uint8_t* in_ptr, size_t dst_val_idx, size_t num_vals) noexcept +{ + if (active_mod == mods.end()) + return EVMC_FAILURE; + + if ((gas_left -= active_mod->second->mulmodx_gas_cost() * static_cast(num_vals)) < 0) + return EVMC_OUT_OF_GAS; + + active_mod->second->storex(in_ptr, dst_val_idx, num_vals); + return EVMC_SUCCESS; +} + +[[nodiscard]] evmc_status_code EVMMAXState::addmodx( + int64_t& gas_left, size_t dst_idx, size_t x_idx, size_t y_idx) noexcept +{ + if (active_mod == mods.end()) + return EVMC_FAILURE; + + if ((gas_left -= active_mod->second->addmodx_gas_cost()) < 0) + return EVMC_OUT_OF_GAS; + + active_mod->second->addmodx(dst_idx, x_idx, y_idx); + return EVMC_SUCCESS; +} + +[[nodiscard]] evmc_status_code EVMMAXState::submodx( + int64_t& gas_left, size_t dst_idx, size_t x_idx, size_t y_idx) noexcept +{ + if (active_mod == mods.end()) + return EVMC_FAILURE; + + if ((gas_left -= active_mod->second->addmodx_gas_cost()) < 0) + return EVMC_OUT_OF_GAS; + + active_mod->second->submodx(dst_idx, x_idx, y_idx); + return EVMC_SUCCESS; +} + +[[nodiscard]] evmc_status_code EVMMAXState::mulmodx( + int64_t& gas_left, size_t dst_idx, size_t x_idx, size_t y_idx) noexcept +{ + if (active_mod == mods.end()) + return EVMC_FAILURE; + + if ((gas_left -= active_mod->second->mulmodx_gas_cost()) < 0) + return EVMC_OUT_OF_GAS; + + active_mod->second->mulmodx(dst_idx, x_idx, y_idx); + return EVMC_SUCCESS; +} + +[[nodiscard]] size_t EVMMAXState::active_mod_value_size_multiplier() const noexcept +{ + if (active_mod == mods.end()) + return 0; + + return active_mod->second->value_size_multiplier(); +} + +[[nodiscard]] bool EVMMAXState::validate_memory_usage(size_t val_size, size_t num_val) noexcept +{ + static constexpr auto EVMMAX_MAX_MEM_SIZE = 65536; + + size_t total_size = val_size * num_val; + for (const auto& item : mods) + total_size += item.second->num_values() * item.second->value_size_multiplier() * 8; + + return total_size <= EVMMAX_MAX_MEM_SIZE; +} + +void EVMMAXState::clear() noexcept +{ + mods.clear(); + active_mod = mods.end(); +} + +EVMMAXState::EVMMAXState() noexcept = default; +EVMMAXState::~EVMMAXState() = default; + +namespace +{ +/// Compute the modulus inverse for Montgomery multiplication, i.e. N': mod⋅N' = 2⁶⁴-1. +/// +/// @param mod0 The least significant word of the modulus. +inline constexpr uint64_t compute_mod_inv(uint64_t mod0) noexcept +{ + // TODO: Find what is this algorithm and why it works. + uint64_t base = 0 - mod0; + uint64_t result = 1; + for (auto i = 0; i < 64; ++i) + { + result *= base; + base *= base; + } + return result; +} + +/// Compute R² % mod. +template +inline UintT compute_r_squared(const UintT& mod) noexcept +{ + // R is 2^num_bits, R² is 2^(2*num_bits) and needs 2*num_bits+1 bits to represent, + // rounded to 2*num_bits+64) for intx requirements. + static constexpr auto r2 = intx::uint{1} << (UintT::num_bits * 2); + return intx::udivrem(r2, mod).rem; +} + +inline constexpr std::pair addmul( + uint64_t t, uint64_t a, uint64_t b, uint64_t c) noexcept +{ + const auto p = umul(a, b) + t + c; + return {p[1], p[0]}; +} +} // namespace + +template +ModArith::ModArith(const UintT& modulus) noexcept + : mod{modulus}, m_r_squared{compute_r_squared(modulus)}, m_mod_inv{compute_mod_inv(modulus[0])} +{} + +template +UintT ModArith::mul(const UintT& x, const UintT& y) const noexcept +{ + // Coarsely Integrated Operand Scanning (CIOS) Method + // Based on 2.3.2 from + // High-Speed Algorithms & Architectures For Number-Theoretic Cryptosystems + // https://www.microsoft.com/en-us/research/wp-content/uploads/1998/06/97Acar.pdf + + static constexpr auto S = UintT::num_words; + + intx::uint t; + for (size_t i = 0; i != S; ++i) + { + uint64_t c = 0; + for (size_t j = 0; j != S; ++j) + std::tie(c, t[j]) = addmul(t[j], x[j], y[i], c); + auto tmp = addc(t[S], c); + t[S] = tmp.value; + auto d = tmp.carry; + + c = 0; + auto m = t[0] * m_mod_inv; + std::tie(c, t[0]) = addmul(t[0], m, mod[0], c); + for (size_t j = 1; j != S; ++j) + std::tie(c, t[j - 1]) = addmul(t[j], m, mod[j], c); + tmp = addc(t[S], c); + t[S - 1] = tmp.value; + t[S] = d + tmp.carry; // TODO: Carry is 0 for sparse modulus. + } + + if (t >= mod) // TODO: cannot overflow if modulus is sparse (e.g. 255 bits). + t -= mod; + + return static_cast(t); +} + +template +UintT ModArith::to_mont(const UintT& x) const noexcept +{ + return mul(x, m_r_squared); +} + +template +UintT ModArith::from_mont(const UintT& x) const noexcept +{ + return mul(x, 1); +} + +template +UintT ModArith::add(const UintT& x, const UintT& y) const noexcept +{ + const auto s = addc(x, y); // TODO: cannot overflow if modulus is sparse (e.g. 255 bits). + const auto d = subc(s.value, mod); + return (!s.carry && d.carry) ? s.value : d.value; +} + +template +UintT ModArith::sub(const UintT& x, const UintT& y) const noexcept +{ + const auto d = subc(x, y); + const auto s = d.value + mod; + return (d.carry) ? s : d.value; +} + +template class ModArith; +template class ModArith; +} // namespace evmmax diff --git a/test/internal_benchmarks/CMakeLists.txt b/test/internal_benchmarks/CMakeLists.txt index 4b5228d687..7dd38f20a5 100644 --- a/test/internal_benchmarks/CMakeLists.txt +++ b/test/internal_benchmarks/CMakeLists.txt @@ -9,4 +9,4 @@ add_executable( memory_allocation.cpp ) -target_link_libraries(evmone-bench-internal PRIVATE evmone::evmmax benchmark::benchmark) +target_link_libraries(evmone-bench-internal PRIVATE evmc::evmc evmone::evmmax benchmark::benchmark) From 9e1976dbb94173a1473f2bff7e038eea5dad4e7d Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 8 Dec 2023 19:45:37 +0100 Subject: [PATCH 3/8] state: Add `EVMMAXState` state to `ExecutionState` --- lib/evmone/CMakeLists.txt | 2 +- lib/evmone/execution_state.hpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/evmone/CMakeLists.txt b/lib/evmone/CMakeLists.txt index 777c0800f2..fd49028584 100644 --- a/lib/evmone/CMakeLists.txt +++ b/lib/evmone/CMakeLists.txt @@ -31,7 +31,7 @@ add_library(evmone vm.hpp ) target_compile_features(evmone PUBLIC cxx_std_20) -target_link_libraries(evmone PUBLIC evmc::evmc intx::intx PRIVATE ethash::keccak) +target_link_libraries(evmone PUBLIC evmc::evmc intx::intx evmmax PRIVATE ethash::keccak) target_include_directories(evmone PUBLIC $$ ) diff --git a/lib/evmone/execution_state.hpp b/lib/evmone/execution_state.hpp index 1462fe858b..038fd30436 100644 --- a/lib/evmone/execution_state.hpp +++ b/lib/evmone/execution_state.hpp @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include #include @@ -170,6 +171,8 @@ class ExecutionState /// Container to be deployed returned from RETURNCONTRACT, used only inside EOFCREATE execution. std::optional deploy_container; + evmmax::EVMMAXState evmmax_state; + private: evmc_tx_context m_tx = {}; @@ -215,6 +218,7 @@ class ExecutionState deploy_container = {}; m_tx = {}; call_stack = {}; + evmmax_state.clear(); } [[nodiscard]] bool in_static_mode() const { return (msg->flags & EVMC_STATIC) != 0; } From 2ea9899abea8dd1e20498029bdcb3c289dea5819 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Wed, 6 Dec 2023 13:02:29 +0100 Subject: [PATCH 4/8] evmmax: Implement evmmax instructions Add new instruction implementation signature `code_iterator (*instr_fn)(StackTop, ExecutionState&, code_iterator, int64_t&)` --- lib/evmone/advanced_instructions.cpp | 9 ++ lib/evmone/baseline_execution.cpp | 7 ++ lib/evmone/instructions.hpp | 132 +++++++++++++++++++++++++++ lib/evmone/instructions_xmacro.hpp | 12 +-- 4 files changed, 154 insertions(+), 6 deletions(-) diff --git a/lib/evmone/advanced_instructions.cpp b/lib/evmone/advanced_instructions.cpp index dfd54458fc..c599111d55 100644 --- a/lib/evmone/advanced_instructions.cpp +++ b/lib/evmone/advanced_instructions.cpp @@ -94,6 +94,15 @@ inline code_iterator impl(AdvancedExecutionState& state, code_iterator pos) noex state.adjust_stack_size(instr::traits[Op].stack_height_change); return new_pos; } + +template > +inline code_iterator impl(AdvancedExecutionState& state, code_iterator pos) noexcept +{ + const auto new_pos = CoreFn(state.stack.top_item, state, pos, state.gas_left); + state.stack.top_item += instr::traits[Op].stack_height_change; + return new_pos; +} /// @} } // namespace instr diff --git a/lib/evmone/baseline_execution.cpp b/lib/evmone/baseline_execution.cpp index 5a404a9cec..6babb231b7 100644 --- a/lib/evmone/baseline_execution.cpp +++ b/lib/evmone/baseline_execution.cpp @@ -135,6 +135,13 @@ struct Position return instr_fn(pos.stack_top, pos.code_it); } +[[release_inline]] inline code_iterator invoke( + code_iterator (*instr_fn)(StackTop, ExecutionState&, code_iterator, int64_t&) noexcept, + Position pos, int64_t& gas, ExecutionState& state) noexcept +{ + return instr_fn(pos.stack_top, state, pos.code_it, gas); +} + [[release_inline]] inline code_iterator invoke( TermResult (*instr_fn)(StackTop, int64_t, ExecutionState&) noexcept, Position pos, int64_t& gas, ExecutionState& state) noexcept diff --git a/lib/evmone/instructions.hpp b/lib/evmone/instructions.hpp index 84f5e09a3a..dd8ae513af 100644 --- a/lib/evmone/instructions.hpp +++ b/lib/evmone/instructions.hpp @@ -1220,6 +1220,138 @@ inline TermResult selfdestruct(StackTop stack, int64_t gas_left, ExecutionState& return {EVMC_SUCCESS, gas_left}; } +/// EVMMAX instructions +namespace +{ +// TODO: Use it in `grom_memory` function +[[nodiscard]] inline int64_t evm_memory_expansion_cost( + const Memory& memory, uint64_t new_size) noexcept +{ + const auto new_words = num_words(new_size); + const auto current_words = static_cast(memory.size() / word_size); + const auto new_cost = 3 * new_words + new_words * new_words / 512; + const auto current_cost = 3 * current_words + current_words * current_words / 512; + return new_cost - current_cost; +} +} // namespace + +inline Result setupx(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept +{ + static constexpr auto MAX_MOD_SIZE = 4096; + + const auto mod_id = stack.pop(); + const auto mod_offset_256 = stack.pop(); + const auto mod_size_256 = stack.pop(); + const auto vals_used_256 = stack.pop(); + + if (!check_memory(gas_left, state.memory, mod_offset_256, mod_size_256)) + return {EVMC_OUT_OF_GAS, gas_left}; + + // Maximum allowed modulus size + if (mod_size_256 > MAX_MOD_SIZE / 8) + return {EVMC_FAILURE, gas_left}; + + const auto mod_ptr = &state.memory[static_cast(mod_offset_256)]; + const auto mod_size = static_cast(mod_size_256); + + // Modulus must be odd + if (mod_ptr[mod_size - 1] % 2 == 0) + return {EVMC_FAILURE, gas_left}; + + const auto vals_used = static_cast(vals_used_256); + // max number of value slots is 256 + if (vals_used_256 > 256) + return {EVMC_FAILURE, gas_left}; + + if (!state.evmmax_state.exists(mod_id)) + { + const auto value_size_multiplier = (mod_size + 7) / 8; + if ((gas_left -= evm_memory_expansion_cost( + state.memory, (vals_used * value_size_multiplier * 8 + 31) / 32)) < 0) + { + return {EVMC_OUT_OF_GAS, gas_left}; + } + } + + const auto status = state.evmmax_state.setupx(gas_left, mod_id, mod_ptr, mod_size, vals_used); + return {status, gas_left}; +} + +inline Result loadx(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept +{ + const auto dst_offset = stack.pop(); + const auto val_idx = stack.pop(); + const auto num_vals = stack.pop(); + + if (!check_memory(gas_left, state.memory, dst_offset, + num_vals * state.evmmax_state.active_mod_value_size_multiplier() * 8)) + return {EVMC_OUT_OF_GAS, gas_left}; + + const auto status = + state.evmmax_state.loadx(gas_left, &state.memory[static_cast(dst_offset)], + static_cast(val_idx), static_cast(num_vals)); + + return {status, gas_left}; +} + +inline Result storex(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept +{ + const auto dst_val = stack.pop(); + const auto offset = stack.pop(); + const auto num_vals = stack.pop(); + + if (!check_memory(gas_left, state.memory, offset, + num_vals * state.evmmax_state.active_mod_value_size_multiplier() * 8)) + return {EVMC_OUT_OF_GAS, gas_left}; + + const auto status = + state.evmmax_state.storex(gas_left, &state.memory[static_cast(offset)], + static_cast(dst_val), static_cast(num_vals)); + + return {status, gas_left}; +} + +inline code_iterator addmodx( + StackTop /*stack*/, ExecutionState& state, code_iterator pos, int64_t& gas_left) noexcept +{ + const auto dst_idx = pos[1]; + const auto x_idx = pos[2]; + const auto y_idx = pos[3]; + + state.status = state.evmmax_state.addmodx(gas_left, dst_idx, x_idx, y_idx); + if (state.status == EVMC_SUCCESS) + return pos + 4; + else + return nullptr; +} + +inline code_iterator submodx( + StackTop /*stack*/, ExecutionState& state, code_iterator pos, int64_t& gas_left) noexcept +{ + const auto dst_idx = pos[1]; + const auto x_idx = pos[2]; + const auto y_idx = pos[3]; + + state.status = state.evmmax_state.submodx(gas_left, dst_idx, x_idx, y_idx); + if (state.status == EVMC_SUCCESS) + return pos + 4; + else + return nullptr; +} + +inline code_iterator mulmodx( + StackTop /*stack*/, ExecutionState& state, code_iterator pos, int64_t& gas_left) noexcept +{ + const auto dst_idx = pos[1]; + const auto x_idx = pos[2]; + const auto y_idx = pos[3]; + + state.status = state.evmmax_state.mulmodx(gas_left, dst_idx, x_idx, y_idx); + if (state.status == EVMC_SUCCESS) + return pos + 4; + else + return nullptr; +} /// Maps an opcode to the instruction implementation. /// diff --git a/lib/evmone/instructions_xmacro.hpp b/lib/evmone/instructions_xmacro.hpp index 2897952db4..32d3d7a3fa 100644 --- a/lib/evmone/instructions_xmacro.hpp +++ b/lib/evmone/instructions_xmacro.hpp @@ -67,12 +67,12 @@ ON_OPCODE_UNDEFINED(0x1f) \ \ ON_OPCODE_IDENTIFIER(OP_KECCAK256, keccak256) \ - ON_OPCODE_UNDEFINED(0x21) \ - ON_OPCODE_UNDEFINED(0x22) \ - ON_OPCODE_UNDEFINED(0x23) \ - ON_OPCODE_UNDEFINED(0x24) \ - ON_OPCODE_UNDEFINED(0x25) \ - ON_OPCODE_UNDEFINED(0x26) \ + ON_OPCODE_IDENTIFIER(OP_SETUPX, setupx) \ + ON_OPCODE_IDENTIFIER(OP_ADDMODX, addmodx) \ + ON_OPCODE_IDENTIFIER(OP_SUBMODX, submodx) \ + ON_OPCODE_IDENTIFIER(OP_MULMODX, mulmodx) \ + ON_OPCODE_IDENTIFIER(OP_LOADX, loadx) \ + ON_OPCODE_IDENTIFIER(OP_STOREX, storex) \ ON_OPCODE_UNDEFINED(0x27) \ ON_OPCODE_UNDEFINED(0x28) \ ON_OPCODE_UNDEFINED(0x29) \ From 6e23559201ab40f92cebd810d212cc5a4524c7cb Mon Sep 17 00:00:00 2001 From: rodiazet Date: Thu, 30 Nov 2023 12:11:27 +0100 Subject: [PATCH 5/8] bytecode: Add `bytecode` API for exmmax instructions --- test/utils/bytecode.hpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/utils/bytecode.hpp b/test/utils/bytecode.hpp index 8ee590d2a4..e865ae1d69 100644 --- a/test/utils/bytecode.hpp +++ b/test/utils/bytecode.hpp @@ -468,6 +468,37 @@ inline bytecode blobhash(bytecode index) return index + OP_BLOBHASH; } +inline bytecode setupx( + bytecode num_val_slots, bytecode mod_size, bytecode mod_offset, bytecode mod_id) +{ + return num_val_slots + mod_size + mod_offset + mod_id + OP_SETUPX; +} + +inline bytecode storex(bytecode num_vals, bytecode val_offset, bytecode val_slot) +{ + return num_vals + val_offset + val_slot + OP_STOREX; +} + +inline bytecode loadx(bytecode num_vals, bytecode val_idx, bytecode mem_offset) +{ + return num_vals + val_idx + mem_offset + OP_LOADX; +} + +inline bytecode addmodx(uint8_t result_slot_idx, uint8_t x_slot_idx, uint8_t y_slot_idx) +{ + return hex(OP_ADDMODX) + hex(result_slot_idx) + hex(x_slot_idx) + hex(y_slot_idx); +} + +inline bytecode submodx(uint8_t result_slot_idx, uint8_t x_slot_idx, uint8_t y_slot_idx) +{ + return hex(OP_SUBMODX) + hex(result_slot_idx) + hex(x_slot_idx) + hex(y_slot_idx); +} + +inline bytecode mulmodx(uint8_t result_slot_idx, uint8_t x_slot_idx, uint8_t y_slot_idx) +{ + return hex(OP_MULMODX) + hex(result_slot_idx) + hex(x_slot_idx) + hex(y_slot_idx); +} + template struct call_instruction { From 8873bbc035e7ddfee2dbbfd56de36ea50b8700ca Mon Sep 17 00:00:00 2001 From: rodiazet Date: Tue, 9 Jan 2024 11:08:22 +0100 Subject: [PATCH 6/8] test: Add evmmax unit tests --- test/unittests/CMakeLists.txt | 1 + test/unittests/evmmax_instructions_test.cpp | 131 +++++++++++++++ test/unittests/evmmax_test.cpp | 168 ++++++++++++++++++++ 3 files changed, 300 insertions(+) create mode 100644 test/unittests/evmmax_instructions_test.cpp diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index 03014aaa43..73849b24a4 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -41,6 +41,7 @@ target_sources( evmmax_bn254_add_test.cpp evmmax_bn254_mul_test.cpp evmmax_test.cpp + evmmax_instructions_test.cpp evmmax_secp256k1_test.cpp evmone_test.cpp execution_state_test.cpp diff --git a/test/unittests/evmmax_instructions_test.cpp b/test/unittests/evmmax_instructions_test.cpp new file mode 100644 index 0000000000..fcd500f4b6 --- /dev/null +++ b/test/unittests/evmmax_instructions_test.cpp @@ -0,0 +1,131 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2023 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "evm_fixture.hpp" +#include +#include +#include + +using namespace intx; +using namespace evmmax; +using namespace evmc::literals; +using evmone::test::evm; + +TEST_P(evm, evmmax_32bytes_modulus_test) +{ + if (is_advanced()) + return; + + rev = EVMC_PRAGUE; /// TODO: Use EVMC_EVMMAX + // Modulus == 7 + auto code = mstore(0, 0x07); + // 3 values slots + // Modulus size in bytes + // Modulus offset in EVM memory + // Modulus ID + code += setupx(3, 32, 0, 1); + // value 3 + code += mstore(32, 0x03); + // value 6 + code += mstore(64, 0x06); + // num values + // values offset + // store values + code += storex(2, 32, 0); + // ADDMODX for values in slots 0 and 1 save result in slot 2 + code += addmodx(2, 1, 0); + // MULMODX for values in slots 1 and 2 save result in slot 2 + code += mulmodx(2, 2, 1); + // SUBMODX for values in slots 1 and 2 save result in slot 2 + code += submodx(2, 2, 1); + // load values from slot 2 into EVM memory + code += loadx(1, 2, 96); + // return loaded result + code += ret(96, 32); + + execute(1000, code); + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + EXPECT_OUTPUT_INT(6); +} + +TEST_P(evm, evmmax_1byte_modulus_test) +{ + if (is_advanced()) + return; + + rev = EVMC_PRAGUE; /// TODO: Use EVMC_EVMMAX + // Modulus == 7 + auto code = mstore8(0, 0x07); + // 3 values slots + // Modulus size in bytes + // Modulus offset in EVM memory + // Modulus ID + code += setupx(3, 1, 0, 1); + // value 3 + code += mstore8(8, 0x03); + // value 6 + code += mstore8(16, 0x06); + // num values + // values offset + // store values + code += storex(2, 1, 0); + // ADDMODX for values in slots 0 and 1 save result in slot 2 + code += addmodx(2, 1, 0); + // MULMODX for values in slots 1 and 2 save result in slot 2 + code += mulmodx(2, 2, 1); + // SUBMODX for values in slots 1 and 2 save result in slot 2 + code += submodx(2, 2, 1); + // load values from slot 2 into EVM memory + code += loadx(1, 2, 17); + // return loaded result + code += ret(17, 8); + + execute(1000, code); + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + + ASSERT_EQ(result.output_size, 8); + EXPECT_EQ(hex({result.output_data, result.output_size}), "0000000000000006"); +} + +TEST_P(evm, evmmax_2byte_modulus_test) +{ + if (is_advanced()) + return; + + rev = EVMC_PRAGUE; /// TODO: Use EVMC_EVMMAX + // Modulus == 263 (0x0107) + auto code = mstore8(0, 0x01); + code += mstore8(1, 0x07); + // 3 values slots + // Modulus size in bytes + // Modulus offset in EVM memory + // Modulus ID + code += setupx(3, 2, 0, 1); + // value 258 + code += mstore8(8, 0x01); + code += mstore8(9, 0x02); + // value 254 + code += mstore8(16, 0x00); + code += mstore8(17, 0xfe); + // num values + // values offset + // store values + code += storex(2, 2, 0); + // ADDMODX for values in slots 0 and 1 save result in slot 2 + code += addmodx(2, 1, 0); // 258 + 254 = 249 mod 263 + // MULMODX for values in slots 1 and 2 save result in slot 2 + code += mulmodx(2, 2, 1); // 249 * 254 = 126 mod 263 + // SUBMODX for values in slots 1 and 2 save result in slot 2 + code += submodx(2, 2, 1); // 126 - 254 = 135 mod 263 + // load values from slot 2 into EVM memory + code += loadx(1, 2, 18); + // return loaded result + code += ret(18, 8); + + execute(1000, code); + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + + ASSERT_EQ(result.output_size, 8); + EXPECT_EQ(hex({result.output_data, result.output_size}), "0000000000000087"); +} diff --git a/test/unittests/evmmax_test.cpp b/test/unittests/evmmax_test.cpp index e078623caa..33f111b2f4 100644 --- a/test/unittests/evmmax_test.cpp +++ b/test/unittests/evmmax_test.cpp @@ -2,12 +2,14 @@ // Copyright 2023 The evmone Authors. // SPDX-License-Identifier: Apache-2.0 +#include "evm_fixture.hpp" #include #include #include using namespace intx; using namespace evmmax; +using evmone::test::evm; constexpr auto P23 = 23_u256; constexpr auto BN254Mod = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47_u256; @@ -147,3 +149,169 @@ TYPED_TEST(evmmax_test, mul) } } } + +namespace +{ +template +inline bytecode create_test_bytecode() +{ + constexpr auto size = sizeof(UintT); + return calldatacopy(push(0), push(0), push(size * 3)) + setupx(3, size, 0, 1) + + storex(2, size, 0) + mulmodx(2, 1, 0) + loadx(1, 2, size * 3) + ret(size * 3, size); +} + +} // namespace + +TEST_P(evm, exec_bn254_test) +{ + using namespace evmone::test; + + if (evm::is_advanced()) + return; + + evm::rev = EVMC_PRAGUE; /// TODO: Use EVMC_EVMMAX + + const ModA m; + + uint8_t calldata[3 * sizeof(uint256)]; + intx::be::unsafe::store(&calldata[0], BN254Mod); + + const auto values = get_test_values(m); + + const auto code = create_test_bytecode(); + + for (const auto& x : values) + { + for (const auto& y : values) + { + const auto expected = udivrem(umul(x, y), m.mod).rem; + + intx::be::unsafe::store(&calldata[32], x); + intx::be::unsafe::store(&calldata[64], y); + + execute(1000, code, {calldata, 96}); + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + EXPECT_OUTPUT_INT(expected); + } + } +} + +TEST_P(evm, exec_bls_test) +{ + using namespace evmone::test; + + if (evm::is_advanced()) + return; + + evm::rev = EVMC_PRAGUE; /// TODO: Use EVMC_EVMMAX + + const ModA m; + + constexpr auto size = sizeof(uint384); + uint8_t calldata[3 * size]; + intx::be::unsafe::store(&calldata[0], BLS12384Mod); + + const auto values = get_test_values(m); + + const auto code = create_test_bytecode(); + + for (const auto& x : values) + { + for (const auto& y : values) + { + const auto expected = udivrem(umul(x, y), m.mod).rem; + + intx::be::unsafe::store(&calldata[size], x); + intx::be::unsafe::store(&calldata[size * 2], y); + + execute(1000, code, {calldata, size * 3}); + EXPECT_EQ(result.status_code, EVMC_SUCCESS); + ASSERT_EQ(result.output_size, size); + EXPECT_EQ(intx::be::unsafe::load(result.output_data), expected); + } + } +} + +TEST_P(evm, exec_invalid_test) +{ + using namespace evmone::test; + + if (evm::is_advanced()) + return; + + evm::rev = EVMC_PRAGUE; /// TODO: Use EVMC_EVMMAX + + { + // Even modulus + constexpr auto size = sizeof(uint256); + uint8_t calldata[3 * size]; + + const auto code = create_test_bytecode(); + intx::be::unsafe::store(&calldata[0], BN254Mod + 1); + execute(1000, code, {calldata, size * 3}); + EXPECT_EQ(result.status_code, EVMC_FAILURE); + } + + { + // Modulus too big + constexpr auto size = sizeof(intx::uint<4160>); + uint8_t calldata[3 * size]; + + const auto code = create_test_bytecode>(); + intx::be::unsafe::store(&calldata[0], intx::uint<4160>(7)); + execute(1000, code, {calldata, size * 3}); + EXPECT_EQ(result.status_code, EVMC_FAILURE); + } + + { + // Too many value slots + constexpr auto size = sizeof(uint256); + uint8_t calldata[size]; + + const auto code = calldatacopy(push(0), push(0), push(size)) + setupx(257, size, 0, 1); + intx::be::unsafe::store(&calldata[0], BN254Mod); + execute(1000, code, {calldata, size}); + EXPECT_EQ(result.status_code, EVMC_FAILURE); + } + + { + // not enough gas + constexpr auto size = sizeof(uint256); + uint8_t calldata[3 * size]; + + const auto code = create_test_bytecode(); + intx::be::unsafe::store(&calldata[0], BN254Mod); + execute(45, code, {calldata, size * 3}); + EXPECT_EQ(result.status_code, EVMC_OUT_OF_GAS); + } + + { + // Too much evmmax memory used + constexpr auto size = sizeof(intx::uint<2048>); + uint8_t calldata[size * 3]; + + const auto code = calldatacopy(push(0), push(0), push(size)) + setupx(1, size, 0, 1) + + setupx(256, size, 0, 2); + intx::be::unsafe::store(&calldata[0], intx::uint<2048>(BN254Mod)); + execute(1000, code, {calldata, size}); + EXPECT_EQ(result.status_code, EVMC_FAILURE); + } + + { + // No active modulus + execute(1000, addmodx(0, 0, 1)); + EXPECT_EQ(result.status_code, EVMC_FAILURE); + + execute(1000, mulmodx(0, 0, 2)); + EXPECT_EQ(result.status_code, EVMC_FAILURE); + + execute(1000, submodx(0, 0, 2)); + EXPECT_EQ(result.status_code, EVMC_FAILURE); + + execute(1000, loadx(1, 0, 0)); + EXPECT_EQ(result.status_code, EVMC_FAILURE); + + execute(1000, storex(1, 0, 0)); + EXPECT_EQ(result.status_code, EVMC_FAILURE); + } +} From 75c438cb470bfcc379afeec6b0b1d530e974999a Mon Sep 17 00:00:00 2001 From: rodiazet Date: Thu, 11 Jan 2024 11:36:43 +0100 Subject: [PATCH 7/8] evmmax: Simplify evmmax implementation. --- include/evmmax/evmmax.hpp | 22 +-- lib/evmmax/evmmax.cpp | 189 ++++++++------------ lib/evmone/instructions.hpp | 14 +- lib/evmone/instructions_traits.hpp | 6 + test/unittests/evmmax_instructions_test.cpp | 6 +- test/unittests/evmmax_test.cpp | 26 ++- test/utils/bytecode.hpp | 5 +- 7 files changed, 129 insertions(+), 139 deletions(-) diff --git a/include/evmmax/evmmax.hpp b/include/evmmax/evmmax.hpp index 3a69b0d76f..5a27d61866 100644 --- a/include/evmmax/evmmax.hpp +++ b/include/evmmax/evmmax.hpp @@ -17,25 +17,27 @@ struct EXMMAXModStateInterface; /// Ephemeral EVMMAX (EVM Modular Arithmetic Extensions) state class EVMMAXState { - typedef std::unordered_map> ModulusMap; - ModulusMap mods; ///< Map of initialized and available moduluses. - ModulusMap::const_iterator active_mod = mods.end(); ///< Current active modulus + struct OpcodesGasCost + { + int64_t addmodx = 0; + int64_t mulmodx = 0; + }; + + OpcodesGasCost current_gas_cost; - /// Validates that memory used by EVMMAX state does not exceed a predefined limit. - [[nodiscard]] bool validate_memory_usage(size_t val_size, size_t num_val) noexcept; + std::unique_ptr active_mod; ///< Current active modulus public: /// Create new modulus and activates it. In case the modulus already exists, activates it. /// Deducts gas accordingly. /// /// \param gas_left Amount of gas before calling. Is modified by `setupx` - /// \param mod_id Modulus identifier /// \param mod_ptr Modulus big endian value memory pointer /// \param mod_size Modulus size in bytes /// \param vals_used Number of needed value slots /// \return Status code. - [[nodiscard]] evmc_status_code setupx(int64_t& gas_left, const uint256& mod_id, - const uint8_t* mod_ptr, size_t mod_size, size_t vals_used) noexcept; + [[nodiscard]] evmc_status_code setupx( + int64_t& gas_left, const uint8_t* mod_ptr, size_t mod_size, size_t vals_used) noexcept; /// Loads EVMMAX values into EVM memory. Deducts gas accordingly. /// Converts to the Montgomery form @@ -71,8 +73,8 @@ class EVMMAXState [[nodiscard]] evmc_status_code mulmodx( int64_t& gas_left, size_t dst_idx, size_t x_idx, size_t y_idx) noexcept; - /// Checks that modulus with `mod_id` exists. - [[nodiscard]] bool exists(const uint256& mod_id) const noexcept; + /// Checks that there exists an active modulus + [[nodiscard]] bool is_activated() const noexcept; /// Returns active modulus size multiplier. /// Size (expressed in multiples of 8 bytes) needed to represent modulus. diff --git a/lib/evmmax/evmmax.cpp b/lib/evmmax/evmmax.cpp index 81e1afe52f..af0d0a0ed6 100644 --- a/lib/evmmax/evmmax.cpp +++ b/lib/evmmax/evmmax.cpp @@ -10,15 +10,15 @@ namespace evmmax { struct EXMMAXModStateInterface { - virtual void loadx(uint8_t* out_ptr, size_t val_idx, size_t num_vals) const noexcept = 0; - virtual void storex(const uint8_t* in_ptr, size_t dst_val_idx, size_t num_vals) noexcept = 0; - virtual void addmodx(size_t dst_idx, size_t x_idx, size_t y_idx) noexcept = 0; - virtual void submodx(size_t dst_idx, size_t x_idx, size_t y_idx) noexcept = 0; - virtual void mulmodx(size_t dst_idx, size_t x_idx, size_t y_idx) noexcept = 0; + [[nodiscard]] virtual bool loadx( + uint8_t* out_ptr, size_t val_idx, size_t num_vals) const noexcept = 0; + [[nodiscard]] virtual bool storex( + const uint8_t* in_ptr, size_t dst_val_idx, size_t num_vals) noexcept = 0; + [[nodiscard]] virtual bool addmodx(size_t dst_idx, size_t x_idx, size_t y_idx) noexcept = 0; + [[nodiscard]] virtual bool submodx(size_t dst_idx, size_t x_idx, size_t y_idx) noexcept = 0; + [[nodiscard]] virtual bool mulmodx(size_t dst_idx, size_t x_idx, size_t y_idx) noexcept = 0; [[nodiscard]] virtual size_t value_size_multiplier() const noexcept = 0; - [[nodiscard]] virtual int64_t addmodx_gas_cost() const noexcept = 0; - [[nodiscard]] virtual int64_t mulmodx_gas_cost() const noexcept = 0; [[nodiscard]] virtual size_t num_values() const noexcept = 0; virtual ~EXMMAXModStateInterface() noexcept = default; @@ -71,65 +71,75 @@ struct EXMMAXModState : public EXMMAXModStateInterface std::vector values; const ModArith arith; const size_t value_size_mult; - const int64_t addmodx_cost; - const int64_t mulmodx_cost; explicit EXMMAXModState(const UintT& mod, size_t mod_size, size_t vals_used) noexcept - : arith(mod), - value_size_mult((mod_size + 7) / 8), - addmodx_cost(compute_addmodx_cost(value_size_mult)), - mulmodx_cost(compute_mulmodx_cost(value_size_mult)) + : arith(mod), value_size_mult((mod_size + 7) / 8) { values.resize(vals_used); } - void loadx(uint8_t* out_ptr, size_t val_idx, size_t num_vals) const noexcept override + [[nodiscard]] bool loadx( + uint8_t* out_ptr, size_t val_idx, size_t num_vals) const noexcept override { - assert(val_idx + num_vals <= values.size()); + if (!(val_idx + num_vals <= values.size())) + return false; for (unsigned i = 0; i < num_vals; ++i) { store(out_ptr + i * value_size_mult * 8, arith.from_mont(values[val_idx + i]), value_size_mult * 8); } + + return true; } - void storex(const uint8_t* in_ptr, size_t dst_val_idx, size_t num_vals) noexcept override + [[nodiscard]] bool storex( + const uint8_t* in_ptr, size_t dst_val_idx, size_t num_vals) noexcept override { - assert(dst_val_idx + num_vals <= values.size()); + if (!(dst_val_idx + num_vals <= values.size())) + return false; for (unsigned i = 0; i < num_vals; ++i) { values[dst_val_idx + i] = arith.to_mont(load(in_ptr + value_size_mult * 8 * i, value_size_mult * 8)); } + + return true; } - void addmodx(size_t dst_idx, size_t x_idx, size_t y_idx) noexcept override + [[nodiscard]] bool addmodx(size_t dst_idx, size_t x_idx, size_t y_idx) noexcept override { - assert(dst_idx < values.size() && x_idx < values.size() && y_idx < values.size()); + if (!(dst_idx < values.size() && x_idx < values.size() && y_idx < values.size())) + return false; values[dst_idx] = arith.add(values[x_idx], values[y_idx]); + + return true; } - void submodx(size_t dst_idx, size_t x_idx, size_t y_idx) noexcept override + [[nodiscard]] bool submodx(size_t dst_idx, size_t x_idx, size_t y_idx) noexcept override { - assert(dst_idx < values.size() && x_idx < values.size() && y_idx < values.size()); + if (!(dst_idx < values.size() && x_idx < values.size() && y_idx < values.size())) + return false; values[dst_idx] = arith.sub(values[x_idx], values[y_idx]); + + return true; } - void mulmodx(size_t dst_idx, size_t x_idx, size_t y_idx) noexcept override + [[nodiscard]] bool mulmodx(size_t dst_idx, size_t x_idx, size_t y_idx) noexcept override { - assert(dst_idx < values.size() && x_idx < values.size() && y_idx < values.size()); + if (!(dst_idx < values.size() && x_idx < values.size() && y_idx < values.size())) + return false; values[dst_idx] = arith.mul(values[x_idx], values[y_idx]); + + return true; } [[nodiscard]] size_t num_values() const noexcept override { return values.size(); } [[nodiscard]] size_t value_size_multiplier() const noexcept override { return value_size_mult; } - [[nodiscard]] int64_t addmodx_gas_cost() const noexcept override { return addmodx_cost; } - [[nodiscard]] int64_t mulmodx_gas_cost() const noexcept override { return mulmodx_cost; } }; [[nodiscard]] std::unique_ptr create_mod_state( @@ -140,47 +150,18 @@ struct EXMMAXModState : public EXMMAXModStateInterface // Max mod size must be <= 4096 bits assert(mod_size <= 512); - if (mod_size <= 16) - { - return std::make_unique>>( - load>(mod_ptr, mod_size), mod_size, vals_used); - } - else if (mod_size <= 24) - { - return std::make_unique>>( - load>(mod_ptr, mod_size), mod_size, vals_used); - } - else if (mod_size <= 32) + if (mod_size <= 32) { return std::make_unique>( load(mod_ptr, mod_size), mod_size, vals_used); } - else if (mod_size <= 40) - { - return std::make_unique>( - load(mod_ptr, mod_size), mod_size, vals_used); - } else if (mod_size <= 48) { return std::make_unique>( load(mod_ptr, mod_size), mod_size, vals_used); } - else if (mod_size <= 56) - { - return std::make_unique>>( - load>(mod_ptr, mod_size), mod_size, vals_used); - } - else if (mod_size <= 64) - { - return std::make_unique>( - load(mod_ptr, mod_size), mod_size, vals_used); - } else - { - /// TODO: Implement for intermediate `mod_size` values up to 512 bytes - return std::make_unique>>( - load>(mod_ptr, mod_size), mod_size, vals_used); - } + return nullptr; } [[nodiscard]] bool charge_gas_precompute_mont(int64_t& gas_left, size_t mod_size) noexcept @@ -203,127 +184,115 @@ struct EXMMAXModState : public EXMMAXModStateInterface return gas_left >= 0; } -} // namespace - -[[nodiscard]] bool EVMMAXState::exists(const intx::uint256& mod_id) const noexcept +[[nodiscard]] bool validate_memory_usage(size_t val_size, size_t num_val) noexcept { - // TODO: Add support for uint256 to be a key in std::unordered_map - const auto mod_id_bytes = intx::be::store(mod_id); + static constexpr auto EVMMAX_MAX_MEM_SIZE = 65536; - return mods.contains(mod_id_bytes); + return val_size * num_val <= EVMMAX_MAX_MEM_SIZE; } -[[nodiscard]] evmc_status_code EVMMAXState::setupx(int64_t& gas_left, const uint256& mod_id, - const uint8_t* mod_ptr, size_t mod_size, size_t vals_used) noexcept -{ - // TODO: Add support for uint256 to be a key in std::unordered_map - const auto mod_id_bytes = intx::be::store(mod_id); - - if (active_mod != mods.end() && active_mod->first == mod_id_bytes) - return EVMC_SUCCESS; +} // namespace - active_mod = mods.find(mod_id_bytes); - if (active_mod != mods.end()) - return EVMC_SUCCESS; +[[nodiscard]] bool EVMMAXState::is_activated() const noexcept +{ + return active_mod != nullptr; +} +[[nodiscard]] evmc_status_code EVMMAXState::setupx( + int64_t& gas_left, const uint8_t* mod_ptr, size_t mod_size, size_t vals_used) noexcept +{ if (!validate_memory_usage(mod_size, vals_used)) return EVMC_FAILURE; if (!charge_gas_precompute_mont(gas_left, mod_size)) return EVMC_OUT_OF_GAS; - active_mod = mods.emplace(mod_id_bytes, create_mod_state(mod_ptr, mod_size, vals_used)).first; - return EVMC_SUCCESS; + active_mod = create_mod_state(mod_ptr, mod_size, vals_used); + if (active_mod != nullptr) + { + const auto current_value_size_multiplier = active_mod->value_size_multiplier(); + current_gas_cost = { + .addmodx = compute_addmodx_cost(current_value_size_multiplier), + .mulmodx = compute_mulmodx_cost(current_value_size_multiplier), + }; + + return EVMC_SUCCESS; + } + else + return EVMC_FAILURE; } [[nodiscard]] evmc_status_code EVMMAXState::loadx( int64_t& gas_left, uint8_t* out_ptr, size_t val_idx, size_t num_vals) noexcept { - if (active_mod == mods.end()) + if (!is_activated()) return EVMC_FAILURE; - if ((gas_left -= active_mod->second->mulmodx_gas_cost() * static_cast(num_vals)) < 0) + if ((gas_left -= current_gas_cost.mulmodx * static_cast(num_vals)) < 0) return EVMC_OUT_OF_GAS; - active_mod->second->loadx(out_ptr, val_idx, num_vals); - return EVMC_SUCCESS; + return active_mod->loadx(out_ptr, val_idx, num_vals) ? EVMC_SUCCESS : EVMC_FAILURE; } [[nodiscard]] evmc_status_code EVMMAXState::storex( int64_t& gas_left, const uint8_t* in_ptr, size_t dst_val_idx, size_t num_vals) noexcept { - if (active_mod == mods.end()) + if (!is_activated()) return EVMC_FAILURE; - if ((gas_left -= active_mod->second->mulmodx_gas_cost() * static_cast(num_vals)) < 0) + if ((gas_left -= current_gas_cost.mulmodx * static_cast(num_vals)) < 0) return EVMC_OUT_OF_GAS; - active_mod->second->storex(in_ptr, dst_val_idx, num_vals); - return EVMC_SUCCESS; + return active_mod->storex(in_ptr, dst_val_idx, num_vals) ? EVMC_SUCCESS : EVMC_FAILURE; } [[nodiscard]] evmc_status_code EVMMAXState::addmodx( int64_t& gas_left, size_t dst_idx, size_t x_idx, size_t y_idx) noexcept { - if (active_mod == mods.end()) + if (!is_activated()) return EVMC_FAILURE; - if ((gas_left -= active_mod->second->addmodx_gas_cost()) < 0) + if ((gas_left -= current_gas_cost.addmodx) < 0) return EVMC_OUT_OF_GAS; - active_mod->second->addmodx(dst_idx, x_idx, y_idx); - return EVMC_SUCCESS; + return active_mod->addmodx(dst_idx, x_idx, y_idx) ? EVMC_SUCCESS : EVMC_FAILURE; } [[nodiscard]] evmc_status_code EVMMAXState::submodx( int64_t& gas_left, size_t dst_idx, size_t x_idx, size_t y_idx) noexcept { - if (active_mod == mods.end()) + if (!is_activated()) return EVMC_FAILURE; - if ((gas_left -= active_mod->second->addmodx_gas_cost()) < 0) + if ((gas_left -= current_gas_cost.addmodx) < 0) return EVMC_OUT_OF_GAS; - active_mod->second->submodx(dst_idx, x_idx, y_idx); - return EVMC_SUCCESS; + return active_mod->submodx(dst_idx, x_idx, y_idx) ? EVMC_SUCCESS : EVMC_FAILURE; } [[nodiscard]] evmc_status_code EVMMAXState::mulmodx( int64_t& gas_left, size_t dst_idx, size_t x_idx, size_t y_idx) noexcept { - if (active_mod == mods.end()) + if (!is_activated()) return EVMC_FAILURE; - if ((gas_left -= active_mod->second->mulmodx_gas_cost()) < 0) + if ((gas_left -= current_gas_cost.mulmodx) < 0) return EVMC_OUT_OF_GAS; - active_mod->second->mulmodx(dst_idx, x_idx, y_idx); - return EVMC_SUCCESS; + return active_mod->mulmodx(dst_idx, x_idx, y_idx) ? EVMC_SUCCESS : EVMC_FAILURE; } [[nodiscard]] size_t EVMMAXState::active_mod_value_size_multiplier() const noexcept { - if (active_mod == mods.end()) + if (!is_activated()) return 0; - return active_mod->second->value_size_multiplier(); -} - -[[nodiscard]] bool EVMMAXState::validate_memory_usage(size_t val_size, size_t num_val) noexcept -{ - static constexpr auto EVMMAX_MAX_MEM_SIZE = 65536; - - size_t total_size = val_size * num_val; - for (const auto& item : mods) - total_size += item.second->num_values() * item.second->value_size_multiplier() * 8; - - return total_size <= EVMMAX_MAX_MEM_SIZE; + return active_mod->value_size_multiplier(); } void EVMMAXState::clear() noexcept { - mods.clear(); - active_mod = mods.end(); + active_mod = nullptr; } EVMMAXState::EVMMAXState() noexcept = default; diff --git a/lib/evmone/instructions.hpp b/lib/evmone/instructions.hpp index dd8ae513af..b0eba941a2 100644 --- a/lib/evmone/instructions.hpp +++ b/lib/evmone/instructions.hpp @@ -1239,7 +1239,6 @@ inline Result setupx(StackTop stack, int64_t gas_left, ExecutionState& state) no { static constexpr auto MAX_MOD_SIZE = 4096; - const auto mod_id = stack.pop(); const auto mod_offset_256 = stack.pop(); const auto mod_size_256 = stack.pop(); const auto vals_used_256 = stack.pop(); @@ -1263,17 +1262,14 @@ inline Result setupx(StackTop stack, int64_t gas_left, ExecutionState& state) no if (vals_used_256 > 256) return {EVMC_FAILURE, gas_left}; - if (!state.evmmax_state.exists(mod_id)) + const auto value_size_multiplier = (mod_size + 7) / 8; + if ((gas_left -= evm_memory_expansion_cost( + state.memory, (vals_used * value_size_multiplier * 8 + 31) / 32)) < 0) { - const auto value_size_multiplier = (mod_size + 7) / 8; - if ((gas_left -= evm_memory_expansion_cost( - state.memory, (vals_used * value_size_multiplier * 8 + 31) / 32)) < 0) - { - return {EVMC_OUT_OF_GAS, gas_left}; - } + return {EVMC_OUT_OF_GAS, gas_left}; } - const auto status = state.evmmax_state.setupx(gas_left, mod_id, mod_ptr, mod_size, vals_used); + const auto status = state.evmmax_state.setupx(gas_left, mod_ptr, mod_size, vals_used); return {status, gas_left}; } diff --git a/lib/evmone/instructions_traits.hpp b/lib/evmone/instructions_traits.hpp index 7955ee1a0f..49f901582f 100644 --- a/lib/evmone/instructions_traits.hpp +++ b/lib/evmone/instructions_traits.hpp @@ -286,6 +286,12 @@ constexpr inline std::array traits = []() noexcept { table[OP_SAR] = {"SAR", 0, false, 2, -1, EVMC_CONSTANTINOPLE, REV_EOF1}; table[OP_KECCAK256] = {"KECCAK256", 0, false, 2, -1, EVMC_FRONTIER}; + table[OP_SETUPX] = {"SETUPX", 0, false, 3, -3, EVMC_EVMMAX}; + table[OP_ADDMODX] = {"ADDMODX", 3, false, 0, 0, EVMC_EVMMAX}; + table[OP_SUBMODX] = {"SUBMODX", 3, false, 0, 0, EVMC_EVMMAX}; + table[OP_MULMODX] = {"MULMODX", 3, false, 0, 0, EVMC_EVMMAX}; + table[OP_LOADX] = {"LOADX", 0, false, 3, -3, EVMC_EVMMAX}; + table[OP_STOREX] = {"STOREX", 0, false, 3, -3, EVMC_EVMMAX}; table[OP_ADDRESS] = {"ADDRESS", 0, false, 0, 1, EVMC_FRONTIER, REV_EOF1}; table[OP_BALANCE] = {"BALANCE", 0, false, 1, 0, EVMC_FRONTIER, REV_EOF1}; diff --git a/test/unittests/evmmax_instructions_test.cpp b/test/unittests/evmmax_instructions_test.cpp index fcd500f4b6..ab71aad233 100644 --- a/test/unittests/evmmax_instructions_test.cpp +++ b/test/unittests/evmmax_instructions_test.cpp @@ -24,7 +24,7 @@ TEST_P(evm, evmmax_32bytes_modulus_test) // Modulus size in bytes // Modulus offset in EVM memory // Modulus ID - code += setupx(3, 32, 0, 1); + code += setupx(3, 32, 0); // value 3 code += mstore(32, 0x03); // value 6 @@ -61,7 +61,7 @@ TEST_P(evm, evmmax_1byte_modulus_test) // Modulus size in bytes // Modulus offset in EVM memory // Modulus ID - code += setupx(3, 1, 0, 1); + code += setupx(3, 1, 0); // value 3 code += mstore8(8, 0x03); // value 6 @@ -101,7 +101,7 @@ TEST_P(evm, evmmax_2byte_modulus_test) // Modulus size in bytes // Modulus offset in EVM memory // Modulus ID - code += setupx(3, 2, 0, 1); + code += setupx(3, 2, 0); // value 258 code += mstore8(8, 0x01); code += mstore8(9, 0x02); diff --git a/test/unittests/evmmax_test.cpp b/test/unittests/evmmax_test.cpp index 33f111b2f4..f9b2d592ae 100644 --- a/test/unittests/evmmax_test.cpp +++ b/test/unittests/evmmax_test.cpp @@ -156,7 +156,7 @@ template inline bytecode create_test_bytecode() { constexpr auto size = sizeof(UintT); - return calldatacopy(push(0), push(0), push(size * 3)) + setupx(3, size, 0, 1) + + return calldatacopy(push(0), push(0), push(size * 3)) + setupx(3, size, 0) + storex(2, size, 0) + mulmodx(2, 1, 0) + loadx(1, 2, size * 3) + ret(size * 3, size); } @@ -268,7 +268,7 @@ TEST_P(evm, exec_invalid_test) constexpr auto size = sizeof(uint256); uint8_t calldata[size]; - const auto code = calldatacopy(push(0), push(0), push(size)) + setupx(257, size, 0, 1); + const auto code = calldatacopy(push(0), push(0), push(size)) + setupx(257, size, 0); intx::be::unsafe::store(&calldata[0], BN254Mod); execute(1000, code, {calldata, size}); EXPECT_EQ(result.status_code, EVMC_FAILURE); @@ -290,13 +290,31 @@ TEST_P(evm, exec_invalid_test) constexpr auto size = sizeof(intx::uint<2048>); uint8_t calldata[size * 3]; - const auto code = calldatacopy(push(0), push(0), push(size)) + setupx(1, size, 0, 1) + - setupx(256, size, 0, 2); + const auto code = + calldatacopy(push(0), push(0), push(size)) + setupx(1, size, 0) + setupx(256, size, 0); intx::be::unsafe::store(&calldata[0], intx::uint<2048>(BN254Mod)); execute(1000, code, {calldata, size}); EXPECT_EQ(result.status_code, EVMC_FAILURE); } + { + // Invalid instruction index + constexpr auto size = sizeof(intx::uint<256>); + uint8_t calldata[size * 3]; + + const auto common_code = calldatacopy(push(0), push(0), push(size)) + setupx(1, size, 0); + intx::be::unsafe::store(&calldata[0], intx::uint<256>(BN254Mod)); + + execute(1000, common_code + addmodx(0, 0, 2), {calldata, size}); + EXPECT_EQ(result.status_code, EVMC_FAILURE); + + execute(1000, common_code + mulmodx(0, 0, 2), {calldata, size}); + EXPECT_EQ(result.status_code, EVMC_FAILURE); + + execute(1000, common_code + submodx(0, 0, 2), {calldata, size}); + EXPECT_EQ(result.status_code, EVMC_FAILURE); + } + { // No active modulus execute(1000, addmodx(0, 0, 1)); diff --git a/test/utils/bytecode.hpp b/test/utils/bytecode.hpp index e865ae1d69..ed2354444a 100644 --- a/test/utils/bytecode.hpp +++ b/test/utils/bytecode.hpp @@ -468,10 +468,9 @@ inline bytecode blobhash(bytecode index) return index + OP_BLOBHASH; } -inline bytecode setupx( - bytecode num_val_slots, bytecode mod_size, bytecode mod_offset, bytecode mod_id) +inline bytecode setupx(bytecode num_val_slots, bytecode mod_size, bytecode mod_offset) { - return num_val_slots + mod_size + mod_offset + mod_id + OP_SETUPX; + return num_val_slots + mod_size + mod_offset + OP_SETUPX; } inline bytecode storex(bytecode num_vals, bytecode val_offset, bytecode val_slot) From da87a62cc966db1263eca25524373357fcbafda7 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Tue, 28 Jan 2025 16:15:19 +0100 Subject: [PATCH 8/8] evmmax: Fix after rebase --- include/evmmax/evmmax.hpp | 2 + lib/evmmax/CMakeLists.txt | 9 +- lib/evmmax/evmmax.cpp | 109 +------------------- lib/evmone/advanced_instructions.cpp | 4 +- lib/evmone/instructions_traits.hpp | 17 ++- test/unittests/eof_validation_test.cpp | 5 +- test/unittests/evmmax_instructions_test.cpp | 7 +- test/unittests/evmmax_test.cpp | 7 +- 8 files changed, 30 insertions(+), 130 deletions(-) diff --git a/include/evmmax/evmmax.hpp b/include/evmmax/evmmax.hpp index 5a27d61866..2901813a65 100644 --- a/include/evmmax/evmmax.hpp +++ b/include/evmmax/evmmax.hpp @@ -82,7 +82,9 @@ class EVMMAXState void clear() noexcept; + EVMMAXState& operator=(EVMMAXState&&) noexcept; explicit EVMMAXState() noexcept; + explicit EVMMAXState(EVMMAXState&&) noexcept; virtual ~EVMMAXState(); }; diff --git a/lib/evmmax/CMakeLists.txt b/lib/evmmax/CMakeLists.txt index ae4c6aa238..61523415e6 100644 --- a/lib/evmmax/CMakeLists.txt +++ b/lib/evmmax/CMakeLists.txt @@ -2,11 +2,11 @@ # Copyright 2023 The evmone Authors. # SPDX-License-Identifier: Apache-2.0 -add_library(evmmax INTERFACE) +add_library(evmmax STATIC) add_library(evmone::evmmax ALIAS evmmax) -target_compile_features(evmmax INTERFACE cxx_std_20) -target_include_directories(evmmax INTERFACE ${PROJECT_SOURCE_DIR}/include) -target_link_libraries(evmmax INTERFACE intx::intx) +target_compile_features(evmmax PUBLIC cxx_std_20) +target_include_directories(evmmax PUBLIC ${PROJECT_SOURCE_DIR}/include) +target_link_libraries(evmmax PUBLIC intx::intx PRIVATE evmc::evmc_cpp) if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.19) # We want to add the header file to the library for IDEs. @@ -14,5 +14,6 @@ if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.19) target_sources( evmmax PRIVATE ${PROJECT_SOURCE_DIR}/include/evmmax/evmmax.hpp + evmmax.cpp ) endif() diff --git a/lib/evmmax/evmmax.cpp b/lib/evmmax/evmmax.cpp index af0d0a0ed6..dd09edba8b 100644 --- a/lib/evmmax/evmmax.cpp +++ b/lib/evmmax/evmmax.cpp @@ -295,114 +295,9 @@ void EVMMAXState::clear() noexcept active_mod = nullptr; } +EVMMAXState& EVMMAXState::operator=(EVMMAXState&&) noexcept = default; +EVMMAXState::EVMMAXState(EVMMAXState&&) noexcept = default; EVMMAXState::EVMMAXState() noexcept = default; EVMMAXState::~EVMMAXState() = default; -namespace -{ -/// Compute the modulus inverse for Montgomery multiplication, i.e. N': mod⋅N' = 2⁶⁴-1. -/// -/// @param mod0 The least significant word of the modulus. -inline constexpr uint64_t compute_mod_inv(uint64_t mod0) noexcept -{ - // TODO: Find what is this algorithm and why it works. - uint64_t base = 0 - mod0; - uint64_t result = 1; - for (auto i = 0; i < 64; ++i) - { - result *= base; - base *= base; - } - return result; -} - -/// Compute R² % mod. -template -inline UintT compute_r_squared(const UintT& mod) noexcept -{ - // R is 2^num_bits, R² is 2^(2*num_bits) and needs 2*num_bits+1 bits to represent, - // rounded to 2*num_bits+64) for intx requirements. - static constexpr auto r2 = intx::uint{1} << (UintT::num_bits * 2); - return intx::udivrem(r2, mod).rem; -} - -inline constexpr std::pair addmul( - uint64_t t, uint64_t a, uint64_t b, uint64_t c) noexcept -{ - const auto p = umul(a, b) + t + c; - return {p[1], p[0]}; -} -} // namespace - -template -ModArith::ModArith(const UintT& modulus) noexcept - : mod{modulus}, m_r_squared{compute_r_squared(modulus)}, m_mod_inv{compute_mod_inv(modulus[0])} -{} - -template -UintT ModArith::mul(const UintT& x, const UintT& y) const noexcept -{ - // Coarsely Integrated Operand Scanning (CIOS) Method - // Based on 2.3.2 from - // High-Speed Algorithms & Architectures For Number-Theoretic Cryptosystems - // https://www.microsoft.com/en-us/research/wp-content/uploads/1998/06/97Acar.pdf - - static constexpr auto S = UintT::num_words; - - intx::uint t; - for (size_t i = 0; i != S; ++i) - { - uint64_t c = 0; - for (size_t j = 0; j != S; ++j) - std::tie(c, t[j]) = addmul(t[j], x[j], y[i], c); - auto tmp = addc(t[S], c); - t[S] = tmp.value; - auto d = tmp.carry; - - c = 0; - auto m = t[0] * m_mod_inv; - std::tie(c, t[0]) = addmul(t[0], m, mod[0], c); - for (size_t j = 1; j != S; ++j) - std::tie(c, t[j - 1]) = addmul(t[j], m, mod[j], c); - tmp = addc(t[S], c); - t[S - 1] = tmp.value; - t[S] = d + tmp.carry; // TODO: Carry is 0 for sparse modulus. - } - - if (t >= mod) // TODO: cannot overflow if modulus is sparse (e.g. 255 bits). - t -= mod; - - return static_cast(t); -} - -template -UintT ModArith::to_mont(const UintT& x) const noexcept -{ - return mul(x, m_r_squared); -} - -template -UintT ModArith::from_mont(const UintT& x) const noexcept -{ - return mul(x, 1); -} - -template -UintT ModArith::add(const UintT& x, const UintT& y) const noexcept -{ - const auto s = addc(x, y); // TODO: cannot overflow if modulus is sparse (e.g. 255 bits). - const auto d = subc(s.value, mod); - return (!s.carry && d.carry) ? s.value : d.value; -} - -template -UintT ModArith::sub(const UintT& x, const UintT& y) const noexcept -{ - const auto d = subc(x, y); - const auto s = d.value + mod; - return (d.carry) ? s : d.value; -} - -template class ModArith; -template class ModArith; } // namespace evmmax diff --git a/lib/evmone/advanced_instructions.cpp b/lib/evmone/advanced_instructions.cpp index c599111d55..c11bf68d64 100644 --- a/lib/evmone/advanced_instructions.cpp +++ b/lib/evmone/advanced_instructions.cpp @@ -99,8 +99,8 @@ template > inline code_iterator impl(AdvancedExecutionState& state, code_iterator pos) noexcept { - const auto new_pos = CoreFn(state.stack.top_item, state, pos, state.gas_left); - state.stack.top_item += instr::traits[Op].stack_height_change; + const auto new_pos = CoreFn(state.stack, state, pos, state.gas_left); + state.adjust_stack_size(instr::traits[Op].stack_height_change); return new_pos; } /// @} diff --git a/lib/evmone/instructions_traits.hpp b/lib/evmone/instructions_traits.hpp index 49f901582f..4816f0bd0a 100644 --- a/lib/evmone/instructions_traits.hpp +++ b/lib/evmone/instructions_traits.hpp @@ -18,7 +18,7 @@ inline constexpr auto cold_sload_cost = 2100; inline constexpr auto cold_account_access_cost = 2600; inline constexpr auto warm_storage_read_cost = 100; -static constexpr auto EVMC_EVMMAX = evmc_revision::EVMC_PRAGUE; +constexpr auto EVMC_EVMMAX = EVMC_OSAKA; /// Additional cold account access cost. /// /// The warm access cost is unconditionally applied for every account access instruction. @@ -209,7 +209,6 @@ static_assert(gas_costs[EVMC_MAX_REVISION][OP_ADD] > 0, "gas costs missing for a /// The revision related to introduction of the EOFv1. constexpr auto REV_EOF1 = EVMC_OSAKA; - /// The EVM instruction traits. struct Traits { @@ -285,13 +284,13 @@ constexpr inline std::array traits = []() noexcept { 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_KECCAK256] = {"KECCAK256", 0, false, 2, -1, EVMC_FRONTIER}; - table[OP_SETUPX] = {"SETUPX", 0, false, 3, -3, EVMC_EVMMAX}; - table[OP_ADDMODX] = {"ADDMODX", 3, false, 0, 0, EVMC_EVMMAX}; - table[OP_SUBMODX] = {"SUBMODX", 3, false, 0, 0, EVMC_EVMMAX}; - table[OP_MULMODX] = {"MULMODX", 3, false, 0, 0, EVMC_EVMMAX}; - table[OP_LOADX] = {"LOADX", 0, false, 3, -3, EVMC_EVMMAX}; - table[OP_STOREX] = {"STOREX", 0, false, 3, -3, EVMC_EVMMAX}; + table[OP_KECCAK256] = {"KECCAK256", 0, false, 2, -1, EVMC_FRONTIER, REV_EOF1}; + table[OP_SETUPX] = {"SETUPX", 0, false, 3, -3, EVMC_EVMMAX, EVMC_EVMMAX}; + table[OP_ADDMODX] = {"ADDMODX", 3, false, 0, 0, EVMC_EVMMAX, EVMC_EVMMAX}; + table[OP_SUBMODX] = {"SUBMODX", 3, false, 0, 0, EVMC_EVMMAX, EVMC_EVMMAX}; + table[OP_MULMODX] = {"MULMODX", 3, false, 0, 0, EVMC_EVMMAX, EVMC_EVMMAX}; + table[OP_LOADX] = {"LOADX", 0, false, 3, -3, EVMC_EVMMAX, EVMC_EVMMAX}; + table[OP_STOREX] = {"STOREX", 0, false, 3, -3, EVMC_EVMMAX, EVMC_EVMMAX}; table[OP_ADDRESS] = {"ADDRESS", 0, false, 0, 1, EVMC_FRONTIER, REV_EOF1}; table[OP_BALANCE] = {"BALANCE", 0, false, 1, 0, EVMC_FRONTIER, REV_EOF1}; diff --git a/test/unittests/eof_validation_test.cpp b/test/unittests/eof_validation_test.cpp index 9c2afa3621..ada68b0301 100644 --- a/test/unittests/eof_validation_test.cpp +++ b/test/unittests/eof_validation_test.cpp @@ -307,8 +307,9 @@ TEST_F(eof_validation, EOF1_undefined_opcodes) opcode == OP_SWAPN || opcode == OP_EXCHANGE || opcode == OP_RJUMP || opcode == OP_RJUMPI || opcode == OP_CALLF || opcode == OP_RJUMPV || opcode == OP_DATALOADN || opcode == OP_JUMPF || opcode == OP_EOFCREATE || - opcode == OP_RETURNCONTRACT || opcode == OP_ADDMODX || opcode == OP_SUBMODX || - opcode == OP_MULMODX) + opcode == OP_RETURNCONTRACT || opcode == OP_SETUPX || opcode == OP_ADDMODX || + opcode == OP_SUBMODX || opcode == OP_MULMODX || opcode == OP_LOADX || + opcode == OP_STOREX) continue; // These opcodes are deprecated since Osaka. // gas_cost table current implementation does not allow to undef instructions. diff --git a/test/unittests/evmmax_instructions_test.cpp b/test/unittests/evmmax_instructions_test.cpp index ab71aad233..2ded5f35dd 100644 --- a/test/unittests/evmmax_instructions_test.cpp +++ b/test/unittests/evmmax_instructions_test.cpp @@ -10,6 +10,7 @@ using namespace intx; using namespace evmmax; using namespace evmc::literals; +using namespace evmone::test; using evmone::test::evm; TEST_P(evm, evmmax_32bytes_modulus_test) @@ -17,7 +18,7 @@ TEST_P(evm, evmmax_32bytes_modulus_test) if (is_advanced()) return; - rev = EVMC_PRAGUE; /// TODO: Use EVMC_EVMMAX + rev = EVMC_OSAKA; /// TODO: Use EVMC_EVMMAX // Modulus == 7 auto code = mstore(0, 0x07); // 3 values slots @@ -54,7 +55,7 @@ TEST_P(evm, evmmax_1byte_modulus_test) if (is_advanced()) return; - rev = EVMC_PRAGUE; /// TODO: Use EVMC_EVMMAX + rev = EVMC_OSAKA; /// TODO: Use EVMC_EVMMAX // Modulus == 7 auto code = mstore8(0, 0x07); // 3 values slots @@ -93,7 +94,7 @@ TEST_P(evm, evmmax_2byte_modulus_test) if (is_advanced()) return; - rev = EVMC_PRAGUE; /// TODO: Use EVMC_EVMMAX + rev = EVMC_OSAKA; /// TODO: Use EVMC_EVMMAX // Modulus == 263 (0x0107) auto code = mstore8(0, 0x01); code += mstore8(1, 0x07); diff --git a/test/unittests/evmmax_test.cpp b/test/unittests/evmmax_test.cpp index f9b2d592ae..4bd2abe82d 100644 --- a/test/unittests/evmmax_test.cpp +++ b/test/unittests/evmmax_test.cpp @@ -152,6 +152,7 @@ TYPED_TEST(evmmax_test, mul) namespace { +using namespace evmone::test; template inline bytecode create_test_bytecode() { @@ -169,7 +170,7 @@ TEST_P(evm, exec_bn254_test) if (evm::is_advanced()) return; - evm::rev = EVMC_PRAGUE; /// TODO: Use EVMC_EVMMAX + evm::rev = EVMC_OSAKA; /// TODO: Use EVMC_EVMMAX const ModA m; @@ -203,7 +204,7 @@ TEST_P(evm, exec_bls_test) if (evm::is_advanced()) return; - evm::rev = EVMC_PRAGUE; /// TODO: Use EVMC_EVMMAX + evm::rev = EVMC_OSAKA; /// TODO: Use EVMC_EVMMAX const ModA m; @@ -239,7 +240,7 @@ TEST_P(evm, exec_invalid_test) if (evm::is_advanced()) return; - evm::rev = EVMC_PRAGUE; /// TODO: Use EVMC_EVMMAX + evm::rev = EVMC_OSAKA; /// TODO: Use EVMC_EVMMAX { // Even modulus