From 3b5851cb96ef9cbaf712fad1e49ca96f8c79d331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 18 Nov 2019 17:24:33 +0100 Subject: [PATCH] Add mocked_host library It takes the MockedHost implementation from evmone and creates an interface library out of it. --- include/evmc/mocked_host.hpp | 258 +++++++++++++++++++++++++++++++++ lib/CMakeLists.txt | 1 + lib/mocked_host/CMakeLists.txt | 15 ++ 3 files changed, 274 insertions(+) create mode 100644 include/evmc/mocked_host.hpp create mode 100644 lib/mocked_host/CMakeLists.txt diff --git a/include/evmc/mocked_host.hpp b/include/evmc/mocked_host.hpp new file mode 100644 index 000000000..6a06d1b13 --- /dev/null +++ b/include/evmc/mocked_host.hpp @@ -0,0 +1,258 @@ +// EVMC: Ethereum Client-VM Connector API. +// Copyright 2018-2019 The EVMC Authors. +// Licensed under the Apache License, Version 2.0. +#pragma once + +#include +#include +#include +#include + +namespace evmc +{ +using bytes = std::basic_string; + +struct MockedAccount +{ + struct storage_value + { + bytes32 value; + + /// True means this value has been modified already by the current transaction. + bool dirty{false}; + + storage_value() noexcept = default; + + storage_value(const bytes32& _value, bool _dirty = false) noexcept // NOLINT + : value{_value}, dirty{_dirty} + {} + }; + + bytes code; + bytes32 codehash; + uint256be balance; + std::unordered_map storage; + + /// Helper method for setting balance by numeric type. + void set_balance(uint64_t x) noexcept + { + balance = uint256be{}; + for (std::size_t i = 0; i < sizeof(x); ++i) + balance.bytes[sizeof(balance) - 1 - i] = static_cast(x >> (8 * i)); + } +}; + +class MockedHost : public Host +{ +public: + struct log_record + { + address creator; + bytes data; + std::vector topics; + + bool operator==(const log_record& other) const noexcept + { + return creator == other.creator && data == other.data && + std::equal(topics.begin(), topics.end(), other.topics.begin(), + other.topics.end()); + } + }; + + struct selfdestuct_record + { + address selfdestructed; + address beneficiary; + + bool operator==(const selfdestuct_record& other) const noexcept + { + return selfdestructed == other.selfdestructed && beneficiary == other.beneficiary; + } + }; + + std::unordered_map accounts; + + evmc_tx_context tx_context = {}; + + bytes32 blockhash = {}; + + evmc_result call_result = {}; + + std::vector recorded_blockhashes; + + static constexpr auto max_recorded_account_accesses = 200; + std::vector
recorded_account_accesses; + + static constexpr auto max_recorded_calls = 100; + std::vector recorded_calls; + + std::vector recorded_logs; + std::vector recorded_selfdestructs; + +protected: + std::vector m_recorded_calls_inputs; + + void record_account_access(const address& addr) + { + if (recorded_account_accesses.empty()) + recorded_account_accesses.reserve(max_recorded_account_accesses); + + if (recorded_account_accesses.size() < max_recorded_account_accesses) + recorded_account_accesses.emplace_back(addr); + } + + bool account_exists(const address& addr) noexcept override + { + record_account_access(addr); + return accounts.count(addr); + } + + bytes32 get_storage(const address& addr, const bytes32& key) noexcept override + { + record_account_access(addr); + const auto ait = accounts.find(addr); + if (ait == accounts.end()) + return {}; + + if (const auto sit = ait->second.storage.find(key); sit != ait->second.storage.end()) + return sit->second.value; + return {}; + } + + evmc_storage_status set_storage(const address& addr, + const bytes32& key, + const bytes32& value) noexcept override + { + record_account_access(addr); + const auto it = accounts.find(addr); + if (it == accounts.end()) + return EVMC_STORAGE_UNCHANGED; + + auto& old = it->second.storage[key]; + + // Follow https://eips.ethereum.org/EIPS/eip-1283 specification. + // WARNING! This is not complete implementation as refund is not handled here. + + if (old.value == value) + return EVMC_STORAGE_UNCHANGED; + + evmc_storage_status status; + { + if (!old.dirty) + { + old.dirty = true; + if (!old.value) + status = EVMC_STORAGE_ADDED; + else if (value) + status = EVMC_STORAGE_MODIFIED; + else + status = EVMC_STORAGE_DELETED; + } + else + status = EVMC_STORAGE_MODIFIED_AGAIN; + } + + old.value = value; + return status; + } + + uint256be get_balance(const address& addr) noexcept override + { + record_account_access(addr); + const auto it = accounts.find(addr); + if (it == accounts.end()) + return {}; + + return it->second.balance; + } + + size_t get_code_size(const address& addr) noexcept override + { + record_account_access(addr); + const auto it = accounts.find(addr); + if (it == accounts.end()) + return 0; + return it->second.code.size(); + } + + bytes32 get_code_hash(const address& addr) noexcept override + { + record_account_access(addr); + const auto it = accounts.find(addr); + if (it == accounts.end()) + return {}; + return it->second.codehash; + } + + size_t copy_code(const address& addr, + size_t code_offset, + uint8_t* buffer_data, + size_t buffer_size) noexcept override + { + record_account_access(addr); + const auto it = accounts.find(addr); + if (it == accounts.end()) + return 0; + + const auto& code = it->second.code; + + if (code_offset >= code.size()) + return 0; + + const auto n = std::min(buffer_size, code.size() - code_offset); + + if (n > 0) + std::copy_n(&code[code_offset], n, buffer_data); + return n; + } + + void selfdestruct(const address& addr, const address& beneficiary) noexcept override + { + record_account_access(addr); + recorded_selfdestructs.push_back({addr, beneficiary}); + } + + result call(const evmc_message& msg) noexcept override + { + record_account_access(msg.destination); + + if (recorded_calls.empty()) + { + recorded_calls.reserve(max_recorded_calls); + m_recorded_calls_inputs.reserve(max_recorded_calls); // Iterators will not invalidate. + } + + if (recorded_calls.size() < max_recorded_calls) + { + auto& call_msg = recorded_calls.emplace_back(msg); + if (call_msg.input_size > 0) + { + const auto& input_copy = + m_recorded_calls_inputs.emplace_back(call_msg.input_data, call_msg.input_size); + call_msg.input_data = input_copy.data(); + } + } + return result{call_result}; + } + + evmc_tx_context get_tx_context() noexcept override { return tx_context; } + + bytes32 get_block_hash(int64_t block_number) noexcept override + { + recorded_blockhashes.emplace_back(block_number); + return blockhash; + } + + void emit_log(const address& addr, + const uint8_t* data, + size_t data_size, + const bytes32 topics[], + size_t topics_count) noexcept override + { + recorded_logs.push_back({addr, {data, data_size}, {}}); + auto& record_topics = recorded_logs.back().topics; + record_topics.reserve(topics_count); + std::copy_n(topics, topics_count, std::back_inserter(record_topics)); + } +}; +} // namespace evmc diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index e1ed726b8..c778ac88e 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -4,3 +4,4 @@ add_subdirectory(instructions) add_subdirectory(loader) +add_subdirectory(mocked_host) diff --git a/lib/mocked_host/CMakeLists.txt b/lib/mocked_host/CMakeLists.txt new file mode 100644 index 000000000..c3b8b6c33 --- /dev/null +++ b/lib/mocked_host/CMakeLists.txt @@ -0,0 +1,15 @@ +# EVMC: Ethereum Client-VM Connector API. +# Copyright 2019 The EVMC Authors. +# Licensed under the Apache License, Version 2.0. + +add_library(mocked_host INTERFACE) +target_sources(mocked_host INTERFACE $) + +add_library(evmc::mocked_host ALIAS mocked_host) +target_include_directories(mocked_host INTERFACE + $$ +) + +if(EVMC_INSTALL) + install(TARGETS mocked_host EXPORT evmcTargets) +endif()