EVMC
mocked_host.hpp
1// EVMC: Ethereum Client-VM Connector API.
2// Copyright 2019 The EVMC Authors.
3// Licensed under the Apache License, Version 2.0.
4#pragma once
5
6#include <evmc/evmc.hpp>
7#include <algorithm>
8#include <cassert>
9#include <string>
10#include <unordered_map>
11#include <vector>
12
13namespace evmc
14{
16using bytes = std::basic_string<uint8_t>;
17
20{
23
26
29
31 StorageValue() noexcept = default;
32
34 StorageValue(const bytes32& _value, // NOLINT(hicpp-explicit-conversions)
35 evmc_access_status _access_status = EVMC_ACCESS_COLD) noexcept
36 : current{_value}, original{_value}, access_status{_access_status}
37 {}
38
40 StorageValue(const bytes32& _value,
41 const bytes32& _original,
42 evmc_access_status _access_status = EVMC_ACCESS_COLD) noexcept
43 : current{_value}, original{_original}, access_status{_access_status}
44 {}
45};
46
49{
51 int nonce = 0;
52
55
58
61
63 std::unordered_map<bytes32, StorageValue> storage;
64
66 void set_balance(uint64_t x) noexcept
67 {
69 for (std::size_t i = 0; i < sizeof(x); ++i)
70 balance.bytes[sizeof(balance) - 1 - i] = static_cast<uint8_t>(x >> (8 * i));
71 }
72};
73
75class MockedHost : public Host
76{
77public:
80 {
83
86
88 std::vector<bytes32> topics;
89
91 bool operator==(const log_record& other) const noexcept
92 {
93 return creator == other.creator && data == other.data && topics == other.topics;
94 }
95 };
96
98 std::unordered_map<address, MockedAccount> accounts;
99
102
105
108
110 mutable std::vector<int64_t> recorded_blockhashes;
111
113 mutable std::vector<address> recorded_account_accesses;
114
117 static constexpr auto max_recorded_account_accesses = 200;
118
120 std::vector<evmc_message> recorded_calls;
121
124 static constexpr auto max_recorded_calls = 100;
125
127 std::vector<log_record> recorded_logs;
128
131 std::unordered_map<address, std::vector<address>> recorded_selfdestructs;
132
133private:
135 std::vector<bytes> m_recorded_calls_inputs;
136
139 void record_account_access(const address& addr) const
140 {
141 if (recorded_account_accesses.empty())
143
145 recorded_account_accesses.emplace_back(addr);
146 }
147
148public:
150 bool account_exists(const address& addr) const noexcept override
151 {
152 record_account_access(addr);
153 return accounts.count(addr) != 0;
154 }
155
157 bytes32 get_storage(const address& addr, const bytes32& key) const noexcept override
158 {
159 record_account_access(addr);
160
161 const auto account_iter = accounts.find(addr);
162 if (account_iter == accounts.end())
163 return {};
164
165 const auto storage_iter = account_iter->second.storage.find(key);
166 if (storage_iter != account_iter->second.storage.end())
167 return storage_iter->second.current;
168 return {};
169 }
170
173 const bytes32& key,
174 const bytes32& value) noexcept override
175 {
176 record_account_access(addr);
177
178 // Get the reference to the storage entry value.
179 // This will create the account in case it was not present.
180 // This is convenient for unit testing and standalone EVM execution to preserve the
181 // storage values after the execution terminates.
182 auto& s = accounts[addr].storage[key];
183
184 // Follow the EIP-2200 specification as closely as possible.
185 // https://eips.ethereum.org/EIPS/eip-2200
186 // Warning: this is not the most efficient implementation. The storage status can be
187 // figured out by combining only 4 checks:
188 // - original != current (dirty)
189 // - original == value (restored)
190 // - current != 0
191 // - value != 0
192 const auto status = [&original = s.original, &current = s.current, &value]() {
193 // Clause 1 is irrelevant:
194 // 1. "If gasleft is less than or equal to gas stipend,
195 // fail the current call frame with ‘out of gas’ exception"
196
197 // 2. "If current value equals new value (this is a no-op)"
198 if (current == value)
199 {
200 // "SLOAD_GAS is deducted"
202 }
203 // 3. "If current value does not equal new value"
204 else
205 {
206 // 3.1. "If original value equals current value
207 // (this storage slot has not been changed by the current execution context)"
208 if (original == current)
209 {
210 // 3.1.1 "If original value is 0"
211 if (is_zero(original))
212 {
213 // "SSTORE_SET_GAS is deducted"
214 return EVMC_STORAGE_ADDED;
215 }
216 // 3.1.2 "Otherwise"
217 else
218 {
219 // "SSTORE_RESET_GAS gas is deducted"
220 auto st = EVMC_STORAGE_MODIFIED;
221
222 // "If new value is 0"
223 if (is_zero(value))
224 {
225 // "add SSTORE_CLEARS_SCHEDULE gas to refund counter"
227 }
228
229 return st;
230 }
231 }
232 // 3.2. "If original value does not equal current value
233 // (this storage slot is dirty),
234 // SLOAD_GAS gas is deducted.
235 // Apply both of the following clauses."
236 else
237 {
238 // Because we need to apply "both following clauses"
239 // we first collect information which clause is triggered
240 // then assign status code to combination of these clauses.
241 enum
242 {
243 None = 0,
244 RemoveClearsSchedule = 1 << 0,
245 AddClearsSchedule = 1 << 1,
246 RestoredBySet = 1 << 2,
247 RestoredByReset = 1 << 3,
248 };
249 int triggered_clauses = None;
250
251 // 3.2.1. "If original value is not 0"
252 if (!is_zero(original))
253 {
254 // 3.2.1.1. "If current value is 0"
255 if (is_zero(current))
256 {
257 // "(also means that new value is not 0)"
258 assert(!is_zero(value));
259 // "remove SSTORE_CLEARS_SCHEDULE gas from refund counter"
260 triggered_clauses |= RemoveClearsSchedule;
261 }
262 // 3.2.1.2. "If new value is 0"
263 if (is_zero(value))
264 {
265 // "(also means that current value is not 0)"
266 assert(!is_zero(current));
267 // "add SSTORE_CLEARS_SCHEDULE gas to refund counter"
268 triggered_clauses |= AddClearsSchedule;
269 }
270 }
271
272 // 3.2.2. "If original value equals new value (this storage slot is reset)"
273 // Except: we use term 'storage slot restored'.
274 if (original == value)
275 {
276 // 3.2.2.1. "If original value is 0"
277 if (is_zero(original))
278 {
279 // "add SSTORE_SET_GAS - SLOAD_GAS to refund counter"
280 triggered_clauses |= RestoredBySet;
281 }
282 // 3.2.2.2. "Otherwise"
283 else
284 {
285 // "add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter"
286 triggered_clauses |= RestoredByReset;
287 }
288 }
289
290 switch (triggered_clauses)
291 {
292 case RemoveClearsSchedule:
294 case AddClearsSchedule:
296 case RemoveClearsSchedule | RestoredByReset:
298 case RestoredBySet:
300 case RestoredByReset:
302 case None:
304 default:
305 assert(false); // Other combinations are impossible.
306 return evmc_storage_status{};
307 }
308 }
309 }
310 }();
311
312 s.current = value; // Finally update the current storage value.
313 return status;
314 }
315
317 uint256be get_balance(const address& addr) const noexcept override
318 {
319 record_account_access(addr);
320 const auto it = accounts.find(addr);
321 if (it == accounts.end())
322 return {};
323
324 return it->second.balance;
325 }
326
328 size_t get_code_size(const address& addr) const noexcept override
329 {
330 record_account_access(addr);
331 const auto it = accounts.find(addr);
332 if (it == accounts.end())
333 return 0;
334 return it->second.code.size();
335 }
336
338 bytes32 get_code_hash(const address& addr) const noexcept override
339 {
340 record_account_access(addr);
341 const auto it = accounts.find(addr);
342 if (it == accounts.end())
343 return {};
344 return it->second.codehash;
345 }
346
348 size_t copy_code(const address& addr,
349 size_t code_offset,
350 uint8_t* buffer_data,
351 size_t buffer_size) const noexcept override
352 {
353 record_account_access(addr);
354 const auto it = accounts.find(addr);
355 if (it == accounts.end())
356 return 0;
357
358 const auto& code = it->second.code;
359
360 if (code_offset >= code.size())
361 return 0;
362
363 const auto n = std::min(buffer_size, code.size() - code_offset);
364
365 if (n > 0)
366 std::copy_n(&code[code_offset], n, buffer_data);
367 return n;
368 }
369
371 bool selfdestruct(const address& addr, const address& beneficiary) noexcept override
372 {
373 record_account_access(addr);
374 auto& beneficiaries = recorded_selfdestructs[addr];
375 beneficiaries.emplace_back(beneficiary);
376 return beneficiaries.size() == 1;
377 }
378
380 Result call(const evmc_message& msg) noexcept override
381 {
382 record_account_access(msg.recipient);
383
384 if (recorded_calls.empty())
385 {
387 m_recorded_calls_inputs.reserve(max_recorded_calls); // Iterators will not invalidate.
388 }
389
391 {
392 recorded_calls.emplace_back(msg);
393 auto& call_msg = recorded_calls.back();
394 if (call_msg.input_size > 0)
395 {
396 m_recorded_calls_inputs.emplace_back(call_msg.input_data, call_msg.input_size);
397 const auto& input_copy = m_recorded_calls_inputs.back();
398 call_msg.input_data = input_copy.data();
399 }
400 }
401 return Result{call_result};
402 }
403
405 evmc_tx_context get_tx_context() const noexcept override { return tx_context; }
406
408 bytes32 get_block_hash(int64_t block_number) const noexcept override
409 {
410 recorded_blockhashes.emplace_back(block_number);
411 return block_hash;
412 }
413
415 void emit_log(const address& addr,
416 const uint8_t* data,
417 size_t data_size,
418 const bytes32 topics[],
419 size_t topics_count) noexcept override
420 {
421 recorded_logs.push_back({addr, {data, data_size}, {topics, topics + topics_count}});
422 }
423
440 evmc_access_status access_account(const address& addr) noexcept override
441 {
442 // Check if the address have been already accessed.
443 const auto already_accessed =
444 std::find(recorded_account_accesses.begin(), recorded_account_accesses.end(), addr) !=
446
447 record_account_access(addr);
448
449 // Accessing precompiled contracts is always warm.
450 if (addr >= 0x0000000000000000000000000000000000000001_address &&
451 addr <= 0x0000000000000000000000000000000000000009_address)
452 return EVMC_ACCESS_WARM;
453
454 return already_accessed ? EVMC_ACCESS_WARM : EVMC_ACCESS_COLD;
455 }
456
472 evmc_access_status access_storage(const address& addr, const bytes32& key) noexcept override
473 {
474 auto& value = accounts[addr].storage[key];
475 const auto access_status = value.access_status;
476 value.access_status = EVMC_ACCESS_WARM;
477 return access_status;
478 }
479};
480} // namespace evmc
Abstract class to be used by Host implementations.
Definition: evmc.hpp:597
Mocked EVMC Host implementation.
Definition: mocked_host.hpp:76
size_t get_code_size(const address &addr) const noexcept override
Get the account's code size (EVMC host method).
size_t copy_code(const address &addr, size_t code_offset, uint8_t *buffer_data, size_t buffer_size) const noexcept override
Copy the account's code to the given buffer (EVMC host method).
uint256be get_balance(const address &addr) const noexcept override
Get the account's balance (EVMC Host method).
void emit_log(const address &addr, const uint8_t *data, size_t data_size, const bytes32 topics[], size_t topics_count) noexcept override
Emit LOG (EVMC host method).
evmc_tx_context get_tx_context() const noexcept override
Get transaction context (EVMC host method).
evmc_result call_result
The call result to be returned by the call() method.
bytes32 block_hash
The block header hash value to be returned by get_block_hash().
evmc_access_status access_account(const address &addr) noexcept override
Record an account access.
bool account_exists(const address &addr) const noexcept override
Returns true if an account exists (EVMC Host method).
static constexpr auto max_recorded_account_accesses
The maximum number of entries in recorded_account_accesses record.
std::unordered_map< address, MockedAccount > accounts
The set of all accounts in the Host, organized by their addresses.
Definition: mocked_host.hpp:98
Result call(const evmc_message &msg) noexcept override
Call/create other contract (EVMC host method).
bytes32 get_storage(const address &addr, const bytes32 &key) const noexcept override
Get the account's storage value at the given key (EVMC Host method).
std::vector< log_record > recorded_logs
The record of all LOGs passed to the emit_log() method.
evmc_access_status access_storage(const address &addr, const bytes32 &key) noexcept override
Access the account's storage value at the given key.
bytes32 get_code_hash(const address &addr) const noexcept override
Get the account's code hash (EVMC host method).
static constexpr auto max_recorded_calls
The maximum number of entries in recorded_calls record.
evmc_storage_status set_storage(const address &addr, const bytes32 &key, const bytes32 &value) noexcept override
Set the account's storage value (EVMC Host method).
bool selfdestruct(const address &addr, const address &beneficiary) noexcept override
Selfdestruct the account (EVMC host method).
std::unordered_map< address, std::vector< address > > recorded_selfdestructs
The record of all SELFDESTRUCTs from the selfdestruct() method as a map selfdestructed_address => [be...
std::vector< int64_t > recorded_blockhashes
The record of all block numbers for which get_block_hash() was called.
bytes32 get_block_hash(int64_t block_number) const noexcept override
Get the block header hash (EVMC host method).
evmc_tx_context tx_context
The EVMC transaction context to be returned by get_tx_context().
std::vector< address > recorded_account_accesses
The record of all account accesses.
std::vector< evmc_message > recorded_calls
The record of all call messages requested in the call() method.
The EVM code execution result.
Definition: evmc.hpp:332
evmc_access_status
Access status per EIP-2929: Gas cost increases for state access opcodes.
Definition: evmc.h:724
evmc_storage_status
The effect of an attempt to modify a contract storage item.
Definition: evmc.h:528
@ EVMC_ACCESS_COLD
The entry hasn't been accessed before – it's the first access.
Definition: evmc.h:728
@ EVMC_ACCESS_WARM
The entry is already in accessed_addresses or accessed_storage_keys.
Definition: evmc.h:733
@ EVMC_STORAGE_ADDED_DELETED
A storage item is deleted by changing the current dirty nonzero to the original zero value.
Definition: evmc.h:592
@ EVMC_STORAGE_MODIFIED_RESTORED
A storage item is modified by changing the current dirty nonzero to the original nonzero value other ...
Definition: evmc.h:599
@ EVMC_STORAGE_DELETED_RESTORED
A storage item is added by changing the current dirty zero to the original value.
Definition: evmc.h:585
@ EVMC_STORAGE_ADDED
A new storage item is added by changing the current clean zero to a nonzero value.
Definition: evmc.h:550
@ EVMC_STORAGE_MODIFIED_DELETED
A storage item is deleted by changing the current dirty nonzero to the zero value and the original va...
Definition: evmc.h:578
@ EVMC_STORAGE_ASSIGNED
The new/same value is assigned to the storage item without affecting the cost structure.
Definition: evmc.h:543
@ EVMC_STORAGE_DELETED
A storage item is deleted by changing the current clean nonzero to the zero value.
Definition: evmc.h:557
@ EVMC_STORAGE_DELETED_ADDED
A storage item is added by changing the current dirty zero to a nonzero value other than the original...
Definition: evmc.h:571
@ EVMC_STORAGE_MODIFIED
A storage item is modified by changing the current clean nonzero to other nonzero value.
Definition: evmc.h:564
EVMC C++ API - wrappers and bindings for C++.
Definition: evmc.hpp:22
std::basic_string< uint8_t > bytes
String of uint8_t chars.
Definition: hex.hpp:15
constexpr bool is_zero(const address &a) noexcept
Checks if the given address is the zero address.
Definition: evmc.hpp:261
Mocked account.
Definition: mocked_host.hpp:49
bytes32 codehash
The code hash. Can be a value not related to the actual code.
Definition: mocked_host.hpp:57
std::unordered_map< bytes32, StorageValue > storage
The account storage map.
Definition: mocked_host.hpp:63
int nonce
The account nonce.
Definition: mocked_host.hpp:51
bytes code
The account code.
Definition: mocked_host.hpp:54
uint256be balance
The account balance.
Definition: mocked_host.hpp:60
void set_balance(uint64_t x) noexcept
Helper method for setting balance by numeric type.
Definition: mocked_host.hpp:66
address creator
The address of the account which created the log.
Definition: mocked_host.hpp:82
bool operator==(const log_record &other) const noexcept
Equal operator.
Definition: mocked_host.hpp:91
std::vector< bytes32 > topics
The log topics.
Definition: mocked_host.hpp:88
bytes data
The data attached to the log.
Definition: mocked_host.hpp:85
Extended value (with original value and access flag) for account storage.
Definition: mocked_host.hpp:20
bytes32 original
The original storage value.
Definition: mocked_host.hpp:25
evmc_access_status access_status
Is the storage key cold or warm.
Definition: mocked_host.hpp:28
StorageValue() noexcept=default
Default constructor.
StorageValue(const bytes32 &_value, const bytes32 &_original, evmc_access_status _access_status=EVMC_ACCESS_COLD) noexcept
Constructor with original value and optional access status.
Definition: mocked_host.hpp:40
bytes32 current
The current storage value.
Definition: mocked_host.hpp:22
The big-endian 160-bit hash suitable for keeping an Ethereum address.
Definition: evmc.hpp:30
The fixed size array of 32 bytes for storing 256-bit EVM values.
Definition: evmc.hpp:74
uint8_t bytes[32]
The 32 bytes.
Definition: evmc.h:59
The message describing an EVM call, including a zero-depth calls from a transaction origin.
Definition: evmc.h:97
The EVM code execution result.
Definition: evmc.h:392
The transaction and block data for execution.
Definition: evmc.h:195