/ include / evmc / mocked_host.hpp
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 <string>
  9  #include <unordered_map>
 10  #include <vector>
 11  
 12  namespace evmc
 13  {
 14  /// The string of bytes.
 15  using bytes = std::basic_string<uint8_t>;
 16  
 17  /// Extended value (by dirty flag) for account storage.
 18  struct storage_value
 19  {
 20      /// The storage value.
 21      bytes32 value;
 22  
 23      /// True means this value has been modified already by the current transaction.
 24      bool dirty{false};
 25  
 26      /// Default constructor.
 27      storage_value() noexcept = default;
 28  
 29      /// Constructor.
 30      storage_value(const bytes32& _value, bool _dirty = false) noexcept  // NOLINT
 31        : value{_value}, dirty{_dirty}
 32      {}
 33  };
 34  
 35  /// Mocked account.
 36  struct MockedAccount
 37  {
 38      /// The account nonce.
 39      int nonce = 0;
 40  
 41      /// The account code.
 42      bytes code;
 43  
 44      /// The code hash. Can be a value not related to the actual code.
 45      bytes32 codehash;
 46  
 47      /// The account balance.
 48      uint256be balance;
 49  
 50      /// The account storage map.
 51      std::unordered_map<bytes32, storage_value> storage;
 52  
 53      /// Helper method for setting balance by numeric type.
 54      void set_balance(uint64_t x) noexcept
 55      {
 56          balance = uint256be{};
 57          for (std::size_t i = 0; i < sizeof(x); ++i)
 58              balance.bytes[sizeof(balance) - 1 - i] = static_cast<uint8_t>(x >> (8 * i));
 59      }
 60  };
 61  
 62  /// Mocked EVMC Host implementation.
 63  class MockedHost : public Host
 64  {
 65  public:
 66      /// LOG record.
 67      struct log_record
 68      {
 69          /// The address of the account which created the log.
 70          address creator;
 71  
 72          /// The data attached to the log.
 73          bytes data;
 74  
 75          /// The log topics.
 76          std::vector<bytes32> topics;
 77  
 78          /// Equal operator.
 79          bool operator==(const log_record& other) const noexcept
 80          {
 81              return creator == other.creator && data == other.data && topics == other.topics;
 82          }
 83      };
 84  
 85      /// SELFDESTRUCT record.
 86      struct selfdestuct_record
 87      {
 88          /// The address of the account which has self-destructed.
 89          address selfdestructed;
 90  
 91          /// The address of the beneficiary account.
 92          address beneficiary;
 93  
 94          /// Equal operator.
 95          bool operator==(const selfdestuct_record& other) const noexcept
 96          {
 97              return selfdestructed == other.selfdestructed && beneficiary == other.beneficiary;
 98          }
 99      };
100  
101      /// The set of all accounts in the Host, organized by their addresses.
102      std::unordered_map<address, MockedAccount> accounts;
103  
104      /// The EVMC transaction context to be returned by get_tx_context().
105      evmc_tx_context tx_context = {};
106  
107      /// The block header hash value to be returned by get_block_hash().
108      bytes32 block_hash = {};
109  
110      /// The call result to be returned by the call() method.
111      evmc_result call_result = {};
112  
113      /// The record of all block numbers for which get_block_hash() was called.
114      mutable std::vector<int64_t> recorded_blockhashes;
115  
116      /// The record of all account accesses.
117      mutable std::vector<address> recorded_account_accesses;
118  
119      /// The maximum number of entries in recorded_account_accesses record.
120      /// This is arbitrary value useful in fuzzing when we don't want the record to explode.
121      static constexpr auto max_recorded_account_accesses = 200;
122  
123      /// The record of all call messages requested in the call() method.
124      std::vector<evmc_message> recorded_calls;
125  
126      /// The maximum number of entries in recorded_calls record.
127      /// This is arbitrary value useful in fuzzing when we don't want the record to explode.
128      static constexpr auto max_recorded_calls = 100;
129  
130      /// The record of all LOGs passed to the emit_log() method.
131      std::vector<log_record> recorded_logs;
132  
133      /// The record of all SELFDESTRUCTs from the selfdestruct() method.
134      std::vector<selfdestuct_record> recorded_selfdestructs;
135  
136  private:
137      /// The copy of call inputs for the recorded_calls record.
138      std::vector<bytes> m_recorded_calls_inputs;
139  
140  public:
141      /// Record an account access.
142      /// @param addr  The address of the accessed account.
143      void record_account_access(const address& addr) const
144      {
145          if (recorded_account_accesses.empty())
146              recorded_account_accesses.reserve(max_recorded_account_accesses);
147  
148          if (recorded_account_accesses.size() < max_recorded_account_accesses)
149              recorded_account_accesses.emplace_back(addr);
150      }
151  
152      /// Returns true if an account exists (EVMC Host method).
153      bool account_exists(const address& addr) const noexcept override
154      {
155          record_account_access(addr);
156          return accounts.count(addr) != 0;
157      }
158  
159      /// Get the account's storage value at the given key (EVMC Host method).
160      bytes32 get_storage(const address& addr, const bytes32& key) const noexcept override
161      {
162          record_account_access(addr);
163  
164          const auto account_iter = accounts.find(addr);
165          if (account_iter == accounts.end())
166              return {};
167  
168          const auto storage_iter = account_iter->second.storage.find(key);
169          if (storage_iter != account_iter->second.storage.end())
170              return storage_iter->second.value;
171          return {};
172      }
173  
174      /// Set the account's storage value (EVMC Host method).
175      evmc_storage_status set_storage(const address& addr,
176                                      const bytes32& key,
177                                      const bytes32& value) noexcept override
178      {
179          record_account_access(addr);
180          const auto it = accounts.find(addr);
181          if (it == accounts.end())
182              return EVMC_STORAGE_UNCHANGED;
183  
184          auto& old = it->second.storage[key];
185  
186          // Follow https://eips.ethereum.org/EIPS/eip-1283 specification.
187          // WARNING! This is not complete implementation as refund is not handled here.
188  
189          if (old.value == value)
190              return EVMC_STORAGE_UNCHANGED;
191  
192          evmc_storage_status status{};
193          if (!old.dirty)
194          {
195              old.dirty = true;
196              if (!old.value)
197                  status = EVMC_STORAGE_ADDED;
198              else if (value)
199                  status = EVMC_STORAGE_MODIFIED;
200              else
201                  status = EVMC_STORAGE_DELETED;
202          }
203          else
204              status = EVMC_STORAGE_MODIFIED_AGAIN;
205  
206          old.value = value;
207          return status;
208      }
209  
210      /// Get the account's balance (EVMC Host method).
211      uint256be get_balance(const address& addr) const noexcept override
212      {
213          record_account_access(addr);
214          const auto it = accounts.find(addr);
215          if (it == accounts.end())
216              return {};
217  
218          return it->second.balance;
219      }
220  
221      /// Get the account's code size (EVMC host method).
222      size_t get_code_size(const address& addr) const noexcept override
223      {
224          record_account_access(addr);
225          const auto it = accounts.find(addr);
226          if (it == accounts.end())
227              return 0;
228          return it->second.code.size();
229      }
230  
231      /// Get the account's code hash (EVMC host method).
232      bytes32 get_code_hash(const address& addr) const noexcept override
233      {
234          record_account_access(addr);
235          const auto it = accounts.find(addr);
236          if (it == accounts.end())
237              return {};
238          return it->second.codehash;
239      }
240  
241      /// Copy the account's code to the given buffer (EVMC host method).
242      size_t copy_code(const address& addr,
243                       size_t code_offset,
244                       uint8_t* buffer_data,
245                       size_t buffer_size) const noexcept override
246      {
247          record_account_access(addr);
248          const auto it = accounts.find(addr);
249          if (it == accounts.end())
250              return 0;
251  
252          const auto& code = it->second.code;
253  
254          if (code_offset >= code.size())
255              return 0;
256  
257          const auto n = std::min(buffer_size, code.size() - code_offset);
258  
259          if (n > 0)
260              std::copy_n(&code[code_offset], n, buffer_data);
261          return n;
262      }
263  
264      /// Selfdestruct the account (EVMC host method).
265      void selfdestruct(const address& addr, const address& beneficiary) noexcept override
266      {
267          record_account_access(addr);
268          recorded_selfdestructs.push_back({addr, beneficiary});
269      }
270  
271      /// Call/create other contract (EVMC host method).
272      result call(const evmc_message& msg) noexcept override
273      {
274          record_account_access(msg.destination);
275  
276          if (recorded_calls.empty())
277          {
278              recorded_calls.reserve(max_recorded_calls);
279              m_recorded_calls_inputs.reserve(max_recorded_calls);  // Iterators will not invalidate.
280          }
281  
282          if (recorded_calls.size() < max_recorded_calls)
283          {
284              recorded_calls.emplace_back(msg);
285              auto& call_msg = recorded_calls.back();
286              if (call_msg.input_size > 0)
287              {
288                  m_recorded_calls_inputs.emplace_back(call_msg.input_data, call_msg.input_size);
289                  const auto& input_copy = m_recorded_calls_inputs.back();
290                  call_msg.input_data = input_copy.data();
291              }
292          }
293          return result{call_result};
294      }
295  
296      /// Get transaction context (EVMC host method).
297      evmc_tx_context get_tx_context() const noexcept override { return tx_context; }
298  
299      /// Get the block header hash (EVMC host method).
300      bytes32 get_block_hash(int64_t block_number) const noexcept override
301      {
302          recorded_blockhashes.emplace_back(block_number);
303          return block_hash;
304      }
305  
306      /// Emit LOG (EVMC host method).
307      void emit_log(const address& addr,
308                    const uint8_t* data,
309                    size_t data_size,
310                    const bytes32 topics[],
311                    size_t topics_count) noexcept override
312      {
313          recorded_logs.push_back({addr, {data, data_size}, {topics, topics + topics_count}});
314      }
315  };
316  }  // namespace evmc