From ab38a8d4989538825fb5d4b4b3270fd63933997a Mon Sep 17 00:00:00 2001 From: rodiazet Date: Thu, 11 Jan 2024 11:36:43 +0100 Subject: [PATCH] evmmax: Simplify evmmax implementation. --- include/evmmax/evmmax.hpp | 22 +-- lib/evmmax/evmmax.cpp | 189 ++++++++------------ lib/evmone/instructions.hpp | 14 +- lib/evmone/instructions_traits.hpp | 2 +- test/unittests/evmmax_instructions_test.cpp | 6 +- test/unittests/evmmax_test.cpp | 26 ++- test/utils/bytecode.hpp | 5 +- 7 files changed, 124 insertions(+), 140 deletions(-) diff --git a/include/evmmax/evmmax.hpp b/include/evmmax/evmmax.hpp index e91f5d8b35..ac1e7543b2 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; + }; - /// 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; + OpcodesGasCost current_gas_cost; + + 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 28aeb26dd0..f8994f9417 100644 --- a/lib/evmone/instructions.hpp +++ b/lib/evmone/instructions.hpp @@ -1186,7 +1186,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(); @@ -1210,17 +1209,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 66179e726a..ac4b6af88f 100644 --- a/lib/evmone/instructions_traits.hpp +++ b/lib/evmone/instructions_traits.hpp @@ -269,7 +269,7 @@ constexpr inline std::array traits = []() noexcept { table[OP_SAR] = {"SAR", 0, false, 2, -1, EVMC_CONSTANTINOPLE}; table[OP_KECCAK256] = {"KECCAK256", 0, false, 2, -1, EVMC_FRONTIER}; - table[OP_SETUPX] = {"SETUPX", 0, false, 4, -4, EVMC_EVMMAX}; + 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}; 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 b0cf9793c9..9efd0ba58f 100644 --- a/test/unittests/evmmax_test.cpp +++ b/test/unittests/evmmax_test.cpp @@ -149,7 +149,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); } @@ -261,7 +261,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); @@ -283,13 +283,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 cac489e579..14b07de1c8 100644 --- a/test/utils/bytecode.hpp +++ b/test/utils/bytecode.hpp @@ -409,10 +409,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)