diff --git a/test/EVMHost.cpp b/test/EVMHost.cpp index 4aad3c994..f33356a21 100644 --- a/test/EVMHost.cpp +++ b/test/EVMHost.cpp @@ -148,6 +148,8 @@ void EVMHost::reset() recorded_selfdestructs.clear(); // Clear call records recorded_calls.clear(); + // Clear EIP-2929 account access indicator + recorded_account_accesses.clear(); // Mark all precompiled contracts as existing. Existing here means to have a balance (as per EIP-161). // NOTE: keep this in sync with `EVMHost::call` below. diff --git a/test/evmc/README.md b/test/evmc/README.md index 655eae4bc..68bcdd92b 100644 --- a/test/evmc/README.md +++ b/test/evmc/README.md @@ -1,5 +1,5 @@ # EVMC -This is an import of [EVMC](https://github.com/ethereum/evmc) version [7.5.0](https://github.com/ethereum/evmc/releases/tag/v7.5.0). +This is an import of [EVMC](https://github.com/ethereum/evmc) version [8.0.0](https://github.com/ethereum/evmc/releases/tag/v8.0.0). Important: The `MockedAccount.storage` is changed to a map from unordered_map as ordering is important for fuzzing. diff --git a/test/evmc/evmc.h b/test/evmc/evmc.h index 88be51b61..5de5f5dae 100644 --- a/test/evmc/evmc.h +++ b/test/evmc/evmc.h @@ -44,7 +44,7 @@ enum * * @see @ref versioning */ - EVMC_ABI_VERSION = 7 + EVMC_ABI_VERSION = 8 }; @@ -603,6 +603,52 @@ typedef void (*evmc_emit_log_fn)(struct evmc_host_context* context, const evmc_bytes32 topics[], size_t topics_count); +/** + * Access status per EIP-2929: Gas cost increases for state access opcodes. + */ +enum evmc_access_status +{ + /** + * The entry hasn't been accessed before – it's the first access. + */ + EVMC_ACCESS_COLD = 0, + + /** + * The entry is already in accessed_addresses or accessed_storage_keys. + */ + EVMC_ACCESS_WARM = 1 +}; + +/** + * Access account callback function. + * + * This callback function is used by a VM to add the given address + * to accessed_addresses substate (EIP-2929). + * + * @param context The Host execution context. + * @param address The address of the account. + * @return EVMC_ACCESS_WARM if accessed_addresses already contained the address + * or EVMC_ACCESS_COLD otherwise. + */ +typedef enum evmc_access_status (*evmc_access_account_fn)(struct evmc_host_context* context, + const evmc_address* address); + +/** + * Access storage callback function. + * + * This callback function is used by a VM to add the given account storage entry + * to accessed_storage_keys substate (EIP-2929). + * + * @param context The Host execution context. + * @param address The address of the account. + * @param key The index of the account's storage entry. + * @return EVMC_ACCESS_WARM if accessed_storage_keys already contained the key + * or EVMC_ACCESS_COLD otherwise. + */ +typedef enum evmc_access_status (*evmc_access_storage_fn)(struct evmc_host_context* context, + const evmc_address* address, + const evmc_bytes32* key); + /** * Pointer to the callback function supporting EVM calls. * @@ -658,6 +704,12 @@ struct evmc_host_interface /** Emit log callback function. */ evmc_emit_log_fn emit_log; + + /** Access account callback function. */ + evmc_access_account_fn access_account; + + /** Access storage callback function. */ + evmc_access_storage_fn access_storage; }; diff --git a/test/evmc/evmc.hpp b/test/evmc/evmc.hpp index fe72656ce..6d0bac9f2 100644 --- a/test/evmc/evmc.hpp +++ b/test/evmc/evmc.hpp @@ -465,6 +465,12 @@ public: size_t data_size, const bytes32 topics[], size_t num_topics) noexcept = 0; + + /// @copydoc evmc_host_interface::access_account + virtual evmc_access_status access_account(const address& addr) noexcept = 0; + + /// @copydoc evmc_host_interface::access_storage + virtual evmc_access_status access_storage(const address& addr, const bytes32& key) noexcept = 0; }; @@ -564,6 +570,16 @@ public: { host->emit_log(context, &addr, data, data_size, topics, topics_count); } + + evmc_access_status access_account(const address& address) noexcept final + { + return host->access_account(context, &address); + } + + evmc_access_status access_storage(const address& address, const bytes32& key) noexcept final + { + return host->access_storage(context, &address, &key); + } }; @@ -805,6 +821,18 @@ inline void emit_log(evmc_host_context* h, Host::from_context(h)->emit_log(*addr, data, data_size, static_cast(topics), num_topics); } + +inline evmc_access_status access_account(evmc_host_context* h, const evmc_address* addr) noexcept +{ + return Host::from_context(h)->access_account(*addr); +} + +inline evmc_access_status access_storage(evmc_host_context* h, + const evmc_address* addr, + const evmc_bytes32* key) noexcept +{ + return Host::from_context(h)->access_storage(*addr, *key); +} } // namespace internal inline const evmc_host_interface& Host::get_interface() noexcept @@ -815,7 +843,9 @@ inline const evmc_host_interface& Host::get_interface() noexcept ::evmc::internal::get_code_size, ::evmc::internal::get_code_hash, ::evmc::internal::copy_code, ::evmc::internal::selfdestruct, ::evmc::internal::call, ::evmc::internal::get_tx_context, - ::evmc::internal::get_block_hash, ::evmc::internal::emit_log}; + ::evmc::internal::get_block_hash, ::evmc::internal::emit_log, + ::evmc::internal::access_account, ::evmc::internal::access_storage, + }; return interface; } } // namespace evmc diff --git a/test/evmc/mocked_host.hpp b/test/evmc/mocked_host.hpp index 257de7a7c..c7d99e6ad 100644 --- a/test/evmc/mocked_host.hpp +++ b/test/evmc/mocked_host.hpp @@ -24,6 +24,9 @@ struct storage_value /// True means this value has been modified already by the current transaction. bool dirty{false}; + /// Is the storage key cold or warm. + evmc_access_status access_status{EVMC_ACCESS_COLD}; + /// Default constructor. storage_value() noexcept = default; @@ -31,6 +34,11 @@ struct storage_value storage_value(const bytes32& _value, bool _dirty = false) noexcept // NOLINT : value{_value}, dirty{_dirty} {} + + /// Constructor with initial access status. + storage_value(const bytes32& _value, evmc_access_status _access_status) noexcept + : value{_value}, access_status{_access_status} + {} }; /// Mocked account. @@ -84,7 +92,7 @@ public: }; /// SELFDESTRUCT record. - struct selfdestuct_record + struct selfdestruct_record { /// The address of the account which has self-destructed. address selfdestructed; @@ -93,7 +101,7 @@ public: address beneficiary; /// Equal operator. - bool operator==(const selfdestuct_record& other) const noexcept + bool operator==(const selfdestruct_record& other) const noexcept { return selfdestructed == other.selfdestructed && beneficiary == other.beneficiary; } @@ -132,13 +140,12 @@ public: std::vector recorded_logs; /// The record of all SELFDESTRUCTs from the selfdestruct() method. - std::vector recorded_selfdestructs; + std::vector recorded_selfdestructs; private: /// The copy of call inputs for the recorded_calls record. std::vector m_recorded_calls_inputs; -public: /// Record an account access. /// @param addr The address of the accessed account. void record_account_access(const address& addr) const @@ -150,6 +157,7 @@ public: recorded_account_accesses.emplace_back(addr); } +public: /// Returns true if an account exists (EVMC Host method). bool account_exists(const address& addr) const noexcept override { @@ -313,5 +321,59 @@ public: { recorded_logs.push_back({addr, {data, data_size}, {topics, topics + topics_count}}); } + + /// Record an account access. + /// + /// This method is required by EIP-2929 introduced in ::EVMC_BERLIN. It will record the account + /// access in MockedHost::recorded_account_accesses and return previous access status. + /// This methods returns ::EVMC_ACCESS_WARM for known addresses of precompiles. + /// The EIP-2929 specifies that evmc_message::sender and evmc_message::destination are always + /// ::EVMC_ACCESS_WARM. Therefore, you should init the MockedHost with: + /// + /// mocked_host.access_account(msg.sender); + /// mocked_host.access_account(msg.destination); + /// + /// The same way you can mock transaction access list (EIP-2930) for account addresses. + /// + /// @param addr The address of the accessed account. + /// @returns The ::EVMC_ACCESS_WARM if the account has been accessed before, + /// the ::EVMC_ACCESS_COLD otherwise. + evmc_access_status access_account(const address& addr) noexcept override + { + // Check if the address have been already accessed. + const auto already_accessed = + std::find(recorded_account_accesses.begin(), recorded_account_accesses.end(), addr) != + recorded_account_accesses.end(); + + record_account_access(addr); + + // Accessing precompiled contracts is always warm. + if (addr >= 0x0000000000000000000000000000000000000001_address && + addr <= 0x0000000000000000000000000000000000000009_address) + return EVMC_ACCESS_WARM; + + return already_accessed ? EVMC_ACCESS_WARM : EVMC_ACCESS_COLD; + } + + /// Access the account's storage value at the given key. + /// + /// This method is required by EIP-2929 introduced in ::EVMC_BERLIN. In records that the given + /// account's storage key has been access and returns the previous access status. + /// To mock storage access list (EIP-2930), you can pre-init account's storage values with + /// the ::EVMC_ACCESS_WARM flag: + /// + /// mocked_host.accounts[msg.destination].storage[key] = {value, EVMC_ACCESS_WARM}; + /// + /// @param addr The account address. + /// @param key The account's storage key. + /// @return The ::EVMC_ACCESS_WARM if the storage key has been accessed before, + /// the ::EVMC_ACCESS_COLD otherwise. + evmc_access_status access_storage(const address& addr, const bytes32& key) noexcept override + { + auto& value = accounts[addr].storage[key]; + const auto access_status = value.access_status; + value.access_status = EVMC_ACCESS_WARM; + return access_status; + } }; } // namespace evmc