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