diff --git a/include/evmc/evmc.hpp b/include/evmc/evmc.hpp index 1b52ae8d0..fd35b8751 100644 --- a/include/evmc/evmc.hpp +++ b/include/evmc/evmc.hpp @@ -334,118 +334,6 @@ class result : private evmc_result } }; -class Host; - -/// @copybrief evmc_vm -/// -/// This is a RAII wrapper for evmc_vm and objects of this type -/// automatically destroys the VM instance. -class VM -{ -public: - VM() noexcept = default; - - /// Converting constructor from evmc_vm. - explicit VM(evmc_vm* vm) noexcept : m_instance{vm} {} - - /// Destructor responsible for automatically destroying the VM instance. - ~VM() noexcept - { - if (m_instance) - m_instance->destroy(m_instance); - } - - VM(const VM&) = delete; - VM& operator=(const VM&) = delete; - - /// Move constructor. - VM(VM&& other) noexcept : m_instance{other.m_instance} { other.m_instance = nullptr; } - - /// Move assignment operator. - VM& operator=(VM&& other) noexcept - { - this->~VM(); - m_instance = other.m_instance; - other.m_instance = nullptr; - return *this; - } - - /// The constructor that captures a VM instance and configures the instance - /// with the provided list of options. - inline VM(evmc_vm* vm, - std::initializer_list> options) noexcept; - - /// Checks if contains a valid pointer to the VM instance. - explicit operator bool() const noexcept { return m_instance != nullptr; } - - /// Checks whenever the VM instance is ABI compatible with the current EVMC API. - bool is_abi_compatible() const noexcept { return m_instance->abi_version == EVMC_ABI_VERSION; } - - /// @copydoc evmc_vm::name - char const* name() const noexcept { return m_instance->name; } - - /// @copydoc evmc_vm::version - char const* version() const noexcept { return m_instance->version; } - - /// @copydoc evmc::vm::get_capabilities - evmc_capabilities_flagset get_capabilities() const noexcept - { - return m_instance->get_capabilities(m_instance); - } - - /// @copydoc evmc_set_option() - evmc_set_option_result set_option(const char name[], const char value[]) noexcept - { - return evmc_set_option(m_instance, name, value); - } - - /// @copydoc evmc_execute() - result execute(const evmc_host_interface& host, - evmc_host_context* ctx, - evmc_revision rev, - const evmc_message& msg, - const uint8_t* code, - size_t code_size) noexcept - { - return result{m_instance->execute(m_instance, &host, ctx, rev, &msg, code, code_size)}; - } - - /// Convenient variant of the VM::execute() that takes reference to evmc::Host class. - inline result execute(Host& host, - evmc_revision rev, - const evmc_message& msg, - const uint8_t* code, - size_t code_size) noexcept; - - /// Executes code without the Host context. - /// - /// The same as - /// execute(const evmc_host_interface&, evmc_host_context*, evmc_revision, - /// const evmc_message&, const uint8_t*, size_t), - /// but without providing the Host context and interface. - /// This method is for experimental precompiles support where execution is - /// guaranteed not to require any Host access. - result execute(evmc_revision rev, - const evmc_message& msg, - const uint8_t* code, - size_t code_size) noexcept - { - return result{ - m_instance->execute(m_instance, nullptr, nullptr, rev, &msg, code, code_size)}; - } - -private: - evmc_vm* m_instance = nullptr; -}; - -inline VM::VM(evmc_vm* vm, - std::initializer_list> options) noexcept - : m_instance{vm} -{ - for (const auto& option : options) - set_option(option.first, option.second); -} - /// The EVMC Host interface class HostInterface @@ -596,6 +484,7 @@ class HostContext : public HostInterface } }; + /// Abstract class to be used by Host implementations. /// /// When implementing EVMC Host, you can directly inherit from the evmc::Host class. @@ -628,13 +517,118 @@ class Host : public HostInterface }; -inline result VM::execute(Host& host, - evmc_revision rev, - const evmc_message& msg, - const uint8_t* code, - size_t code_size) noexcept +/// @copybrief evmc_vm +/// +/// This is a RAII wrapper for evmc_vm, and object of this type +/// automatically destroys the VM instance. +class VM +{ +public: + VM() noexcept = default; + + /// Converting constructor from evmc_vm. + explicit VM(evmc_vm* vm) noexcept : m_instance{vm} {} + + /// Destructor responsible for automatically destroying the VM instance. + ~VM() noexcept + { + if (m_instance) + m_instance->destroy(m_instance); + } + + VM(const VM&) = delete; + VM& operator=(const VM&) = delete; + + /// Move constructor. + VM(VM&& other) noexcept : m_instance{other.m_instance} { other.m_instance = nullptr; } + + /// Move assignment operator. + VM& operator=(VM&& other) noexcept + { + this->~VM(); + m_instance = other.m_instance; + other.m_instance = nullptr; + return *this; + } + + /// The constructor that captures a VM instance and configures the instance + /// with the provided list of options. + inline VM(evmc_vm* vm, + std::initializer_list> options) noexcept; + + /// Checks if contains a valid pointer to the VM instance. + explicit operator bool() const noexcept { return m_instance != nullptr; } + + /// Checks whenever the VM instance is ABI compatible with the current EVMC API. + bool is_abi_compatible() const noexcept { return m_instance->abi_version == EVMC_ABI_VERSION; } + + /// @copydoc evmc_vm::name + char const* name() const noexcept { return m_instance->name; } + + /// @copydoc evmc_vm::version + char const* version() const noexcept { return m_instance->version; } + + /// @copydoc evmc::vm::get_capabilities + evmc_capabilities_flagset get_capabilities() const noexcept + { + return m_instance->get_capabilities(m_instance); + } + + /// @copydoc evmc_set_option() + evmc_set_option_result set_option(const char name[], const char value[]) noexcept + { + return evmc_set_option(m_instance, name, value); + } + + /// @copydoc evmc_execute() + result execute(const evmc_host_interface& host, + evmc_host_context* ctx, + evmc_revision rev, + const evmc_message& msg, + const uint8_t* code, + size_t code_size) noexcept + { + return result{m_instance->execute(m_instance, &host, ctx, rev, &msg, code, code_size)}; + } + + /// Convenient variant of the VM::execute() that takes reference to evmc::Host class. + result execute(Host& host, + evmc_revision rev, + const evmc_message& msg, + const uint8_t* code, + size_t code_size) noexcept + { + return execute(Host::get_interface(), host.to_context(), rev, msg, code, code_size); + } + + /// Executes code without the Host context. + /// + /// The same as + /// execute(const evmc_host_interface&, evmc_host_context*, evmc_revision, + /// const evmc_message&, const uint8_t*, size_t), + /// but without providing the Host context and interface. + /// This method is for experimental precompiles support where execution is + /// guaranteed not to require any Host access. + result execute(evmc_revision rev, + const evmc_message& msg, + const uint8_t* code, + size_t code_size) noexcept + { + return result{ + m_instance->execute(m_instance, nullptr, nullptr, rev, &msg, code, code_size)}; + } + +private: + evmc_vm* m_instance = nullptr; +}; + +inline VM::VM(evmc_vm* vm, + std::initializer_list> options) noexcept + : m_instance{vm} { - return execute(Host::get_interface(), host.to_context(), rev, msg, code, code_size); + // This constructor is implemented outside of the class definition to workaround a doxygen bug. + for (const auto& option : options) + set_option(option.first, option.second); } diff --git a/test/unittests/test_cpp.cpp b/test/unittests/test_cpp.cpp index 6637d25af..49784d315 100644 --- a/test/unittests/test_cpp.cpp +++ b/test/unittests/test_cpp.cpp @@ -19,6 +19,47 @@ #include #include +class NullHost : public evmc::Host +{ +public: + bool account_exists(const evmc::address&) noexcept final { return false; } + + evmc::bytes32 get_storage(const evmc::address&, const evmc::bytes32&) noexcept final + { + return {}; + } + + evmc_storage_status set_storage(const evmc::address&, + const evmc::bytes32&, + const evmc::bytes32&) noexcept final + { + return {}; + } + + evmc::uint256be get_balance(const evmc::address&) noexcept final { return {}; } + + size_t get_code_size(const evmc::address&) noexcept final { return 0; } + + evmc::bytes32 get_code_hash(const evmc::address&) noexcept final { return {}; } + + size_t copy_code(const evmc::address&, size_t, uint8_t*, size_t) noexcept final { return 0; } + + void selfdestruct(const evmc::address&, const evmc::address&) noexcept final {} + + evmc::result call(const evmc_message&) noexcept final { return evmc::result{evmc_result{}}; } + + evmc_tx_context get_tx_context() noexcept final { return {}; } + + evmc::bytes32 get_block_hash(int64_t) noexcept final { return {}; } + + void emit_log(const evmc::address&, + const uint8_t*, + size_t, + const evmc::bytes32[], + size_t) noexcept final + {} +}; + TEST(cpp, address) { evmc::address a; @@ -285,6 +326,23 @@ TEST(cpp, vm_set_option) EXPECT_EQ(vm.set_option("1", "2"), EVMC_SET_OPTION_INVALID_NAME); } +TEST(cpp, vm_set_option_in_constructor) +{ + static int num_calls = 0; + const auto set_option_method = [](evmc_vm* /*unused*/, const char* name, const char* value) { + ++num_calls; + EXPECT_STREQ(name, "o"); + EXPECT_EQ(value, std::to_string(num_calls)); + return EVMC_SET_OPTION_INVALID_NAME; + }; + + evmc_vm raw{EVMC_ABI_VERSION, "", "", nullptr, nullptr, nullptr, set_option_method}; + raw.destroy = [](evmc_vm*) {}; + + const auto vm = evmc::VM{&raw, {{"o", "1"}, {"o", "2"}}}; + EXPECT_EQ(num_calls, 2); +} + TEST(cpp, vm_null) { evmc::VM vm; @@ -365,6 +423,19 @@ TEST(cpp, vm_execute_precompiles) EXPECT_TRUE(std::equal(input.begin(), input.end(), res.output_data)); } +TEST(cpp, vm_execute_with_null_host) +{ + // This tests only if the used VM::execute() overload is at least implemented. + // We know that the example VM will not use the host context in this case. + + auto host = NullHost{}; + + auto vm = evmc::VM{evmc_create_example_vm()}; + evmc_message msg{}; + auto res = vm.execute(host, EVMC_MAX_REVISION, msg, nullptr, 0); + EXPECT_EQ(res.status_code, EVMC_FAILURE); +} + TEST(cpp, host) { // Use example host to execute all methods from the C++ host wrapper.