Add evmc and host interface implementation. Modify fuzzer harness to make use of evmc host/vm.

This commit is contained in:
Christian Parpart 2019-07-08 16:04:52 +02:00 committed by Bhargava Shastry
parent 4fa7800458
commit 810a0de1aa
21 changed files with 2869 additions and 14 deletions

View File

@ -36,8 +36,9 @@ defaults:
mkdir -p build
cd build
protoc --proto_path=../test/tools/ossfuzz yulProto.proto --cpp_out=../test/tools/ossfuzz
protoc --proto_path=../test/tools/ossfuzz abiV2Proto.proto --cpp_out=../test/tools/ossfuzz
cmake .. -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Release} $CMAKE_OPTIONS
make ossfuzz ossfuzz_proto -j4
make ossfuzz ossfuzz_proto ossfuzz_abiv2 -j4
- run_proofs: &run_proofs
name: Correctness proofs for optimization rules
@ -69,6 +70,7 @@ defaults:
- artifacts_executables_ossfuzz: &artifacts_executables_ossfuzz
root: build
paths:
- test/tools/ossfuzz/abiv2_proto_ossfuzz
- test/tools/ossfuzz/const_opt_ossfuzz
- test/tools/ossfuzz/solc_noopt_ossfuzz
- test/tools/ossfuzz/solc_opt_ossfuzz

View File

@ -11,7 +11,7 @@ eth_policy()
# project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.5.11")
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES CXX)
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
if (${CMAKE_VERSION} VERSION_LESS "3.9.0")
# needed for the big endian test for older cmake versions

View File

@ -97,6 +97,22 @@ scanner/token:
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
evmc:
The code in test/evmc is licensed under the Apache License version 2:
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
All other code is licensed under GPL version 3:
)"};

View File

@ -30,7 +30,8 @@ add_executable(soltest ${sources} ${headers}
${libsolidity_sources} ${libsolidity_headers}
${libsolidity_util_sources} ${libsolidity_util_headers}
)
target_link_libraries(soltest PRIVATE libsolc yul solidity yulInterpreter evmasm devcore Boost::boost Boost::program_options Boost::unit_test_framework)
target_link_libraries(soltest PRIVATE libsolc yul solidity yulInterpreter evmasm devcore Boost::boost Boost::program_options Boost::unit_test_framework evmc)
# Special compilation flag for Visual Studio (version 2019 at least affected)
# in order to compile SolidityEndToEndTest.cpp, which is quite huge.
@ -50,3 +51,4 @@ if (NOT Boost_USE_STATIC_LIBS)
endif()
add_subdirectory(tools)
add_subdirectory(evmc)

215
test/EVMHost.cpp Normal file
View File

@ -0,0 +1,215 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* EVM execution host, i.e. component that implements a simulated Ethereum blockchain
* for testing purposes.
*/
#include <test/EVMHost.h>
#include <test/evmc/helpers.hpp>
#include <test/evmc/loader.h>
#include <libdevcore/Exceptions.h>
#include <libdevcore/Assertions.h>
#include <libdevcore/Keccak256.h>
#include <libdevcore/picosha2.h>
using namespace std;
using namespace dev;
using namespace dev::test;
EVMHost::EVMHost(langutil::EVMVersion _evmVersion, evmc::vm& _vmInstance):
m_vm(_vmInstance)
{
if (_evmVersion == langutil::EVMVersion::homestead())
m_evmVersion = EVMC_HOMESTEAD;
else if (_evmVersion == langutil::EVMVersion::tangerineWhistle())
m_evmVersion = EVMC_TANGERINE_WHISTLE;
else if (_evmVersion == langutil::EVMVersion::spuriousDragon())
m_evmVersion = EVMC_SPURIOUS_DRAGON;
else if (_evmVersion == langutil::EVMVersion::byzantium())
m_evmVersion = EVMC_BYZANTIUM;
else if (_evmVersion == langutil::EVMVersion::constantinople())
m_evmVersion = EVMC_CONSTANTINOPLE;
else //if (_evmVersion == langutil::EVMVersion::petersburg())
m_evmVersion = EVMC_PETERSBURG;
}
evmc_storage_status EVMHost::set_storage(const evmc_address& _addr, const evmc_bytes32& _key, const evmc_bytes32& _value) noexcept
{
evmc_bytes32 previousValue = m_state.accounts[_addr].storage[_key];
m_state.accounts[_addr].storage[_key] = _value;
// TODO EVMC_STORAGE_MODIFIED_AGAIN should be also used
if (previousValue == _value)
return EVMC_STORAGE_UNCHANGED;
else if (previousValue == evmc_bytes32{})
return EVMC_STORAGE_ADDED;
else if (_value == evmc_bytes32{})
return EVMC_STORAGE_DELETED;
else
return EVMC_STORAGE_MODIFIED;
}
void EVMHost::selfdestruct(const evmc_address& _addr, const evmc_address& _beneficiary) noexcept
{
// TODO actual selfdestruct is even more complicated.
evmc_uint256be balance = m_state.accounts[_addr].balance;
m_state.accounts.erase(_addr);
m_state.accounts[_beneficiary].balance = balance;
}
evmc::result EVMHost::call(evmc_message const& _message) noexcept
{
if (_message.destination == convertToEVMC(Address(2)))
return precompileSha256(_message);
State stateBackup = m_state;
u256 value{convertFromEVMC(_message.value)};
Account& sender = m_state.accounts[_message.sender];
bytes code;
evmc_message message = _message;
if (message.kind == EVMC_CREATE)
{
// TODO this is not the right formula
// TODO is the nonce incremented on failure, too?
Address createAddress(keccak256(
bytes(begin(message.sender.bytes), end(message.sender.bytes)) +
asBytes(to_string(sender.nonce++))
));
message.destination = convertToEVMC(createAddress);
code = bytes(message.input_data, message.input_data + message.input_size);
}
else if (message.kind == EVMC_DELEGATECALL)
{
code = m_state.accounts[message.destination].code;
message.destination = m_currentAddress;
}
else if (message.kind == EVMC_CALLCODE)
{
code = m_state.accounts[message.destination].code;
message.destination = m_currentAddress;
}
else
code = m_state.accounts[message.destination].code;
//TODO CREATE2
Account& destination = m_state.accounts[message.destination];
if (value != 0 && message.kind != EVMC_DELEGATECALL && message.kind != EVMC_CALLCODE)
{
sender.balance = convertToEVMC(u256(convertFromEVMC(sender.balance)) - value);
destination.balance = convertToEVMC(u256(convertFromEVMC(destination.balance)) + value);
}
evmc_address currentAddress = m_currentAddress;
m_currentAddress = message.destination;
evmc::result result = m_vm.execute(*this, m_evmVersion, message, code.data(), code.size());
m_currentAddress = currentAddress;
if (result.status_code != EVMC_SUCCESS)
m_state = stateBackup;
else if (message.kind == EVMC_CREATE)
{
result.create_address = message.destination;
destination.code = bytes(result.output_data, result.output_data + result.output_size);
destination.codeHash = convertToEVMC(keccak256(destination.code));
}
return result;
}
evmc_tx_context EVMHost::get_tx_context() noexcept
{
evmc_tx_context ctx = {};
ctx.block_timestamp = m_state.timestamp;
ctx.block_number = m_state.blockNumber;
ctx.block_coinbase = m_coinbase;
ctx.block_difficulty = convertToEVMC(u256("200000000"));
ctx.block_gas_limit = 20000000;
ctx.tx_gas_price = convertToEVMC(u256("3000000000"));
ctx.tx_origin = convertToEVMC(Address("0x9292929292929292929292929292929292929292"));
return ctx;
}
evmc_bytes32 EVMHost::get_block_hash(int64_t _number) noexcept
{
return convertToEVMC(u256("0x3737373737373737373737373737373737373737373737373737373737373737") + _number);
}
void EVMHost::emit_log(
evmc_address const& _addr,
uint8_t const* _data,
size_t _dataSize,
evmc_bytes32 const _topics[],
size_t _topicsCount
) noexcept
{
LogEntry entry;
entry.address = convertFromEVMC(_addr);
for (size_t i = 0; i < _topicsCount; ++i)
entry.topics.emplace_back(convertFromEVMC(_topics[i]));
entry.data = bytes(_data, _data + _dataSize);
m_state.logs.emplace_back(std::move(entry));
}
Address EVMHost::convertFromEVMC(evmc_address const& _addr)
{
return Address(bytes(begin(_addr.bytes), end(_addr.bytes)));
}
evmc_address EVMHost::convertToEVMC(Address const& _addr)
{
evmc_address a;
for (size_t i = 0; i < 20; ++i)
a.bytes[i] = _addr[i];
return a;
}
h256 EVMHost::convertFromEVMC(evmc_bytes32 const& _data)
{
return h256(bytes(begin(_data.bytes), end(_data.bytes)));
}
evmc_bytes32 EVMHost::convertToEVMC(h256 const& _data)
{
evmc_bytes32 d;
for (size_t i = 0; i < 32; ++i)
d.bytes[i] = _data[i];
return d;
}
evmc::result EVMHost::precompileSha256(evmc_message const& _message) noexcept
{
// static data so that we do not need a release routine...
bytes static hash;
hash = picosha2::hash256(bytes(
_message.input_data,
_message.input_data + _message.input_size
));
evmc::result result({});
result.output_data = hash.data();
result.output_size = hash.size();
return result;
}

175
test/EVMHost.h Normal file
View File

@ -0,0 +1,175 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* EVM execution host, i.e. component that implements a simulated Ethereum blockchain
* for testing purposes.
*/
#pragma once
#include <test/evmc/evmc.hpp>
#include <test/evmc/evmc.h>
#include <test/evmc/helpers.hpp>
#include <liblangutil/EVMVersion.h>
#include <libdevcore/FixedHash.h>
namespace dev
{
namespace test
{
using Address = h160;
class EVMHost: public evmc::Host
{
public:
explicit EVMHost(langutil::EVMVersion _evmVersion, evmc::vm& _vmInstance);
struct Account
{
evmc_uint256be balance = {};
size_t nonce = 0;
bytes code;
evmc_bytes32 codeHash = {};
std::map<evmc_bytes32, evmc_bytes32> storage;
};
struct LogEntry
{
Address address;
std::vector<h256> topics;
bytes data;
};
struct State
{
size_t blockNumber;
uint64_t timestamp;
std::map<evmc_address, Account> accounts;
std::vector<LogEntry> logs;
};
Account* account(evmc_address const& _address)
{
auto it = m_state.accounts.find(_address);
return it == m_state.accounts.end() ? nullptr : &it->second;
}
void reset() { m_state = State{}; m_currentAddress = {}; }
void newBlock()
{
m_state.blockNumber++;
m_state.timestamp += 15;
m_state.logs.clear();
}
bool account_exists(evmc_address const& _addr) noexcept final
{
return account(_addr) != nullptr;
}
evmc_bytes32 get_storage(evmc_address const& _addr, evmc_bytes32 const& _key) noexcept final
{
if (Account* acc = account(_addr))
return acc->storage[_key];
return {};
}
evmc_storage_status set_storage(
evmc_address const& _addr,
evmc_bytes32 const& _key,
evmc_bytes32 const& _value
) noexcept;
evmc_uint256be get_balance(evmc_address const& _addr) noexcept final
{
if (Account const* acc = account(_addr))
return acc->balance;
return {};
}
size_t get_code_size(evmc_address const& _addr) noexcept final
{
if (Account const* acc = account(_addr))
return acc->code.size();
return 0;
}
evmc_bytes32 get_code_hash(evmc_address const& _addr) noexcept final
{
if (Account const* acc = account(_addr))
return acc->codeHash;
return {};
}
size_t copy_code(
evmc_address const& _addr,
size_t _codeOffset,
uint8_t* _bufferData,
size_t _bufferSize
) noexcept final
{
size_t i = 0;
if (Account const* acc = account(_addr))
for (; i < _bufferSize && _codeOffset + i < acc->code.size(); i++)
_bufferData[i] = acc->code[_codeOffset + i];
return i;
}
void selfdestruct(evmc_address const& _addr, evmc_address const& _beneficiary) noexcept;
evmc::result call(evmc_message const& _message) noexcept;
evmc_tx_context get_tx_context() noexcept;
evmc_bytes32 get_block_hash(int64_t number) noexcept;
void emit_log(
evmc_address const& _addr,
uint8_t const* _data,
size_t _dataSize,
evmc_bytes32 const _topics[],
size_t _topicsCount
) noexcept;
evmc_revision getRevision()
{
return m_evmVersion;
}
static Address convertFromEVMC(evmc_address const& _addr);
static evmc_address convertToEVMC(Address const& _addr);
static h256 convertFromEVMC(evmc_bytes32 const& _data);
static evmc_bytes32 convertToEVMC(h256 const& _data);
State m_state;
evmc_address m_currentAddress = {};
evmc_address m_coinbase = convertToEVMC(Address("0x7878787878787878787878787878787878787878"));
private:
evmc::result precompileSha256(evmc_message const& _message) noexcept;
evmc::vm& m_vm;
evmc_revision m_evmVersion;
};
}
}

View File

@ -27,6 +27,10 @@
#include <boost/test/framework.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <test/evmc/evmc.hpp>
#include <test/evmc/loader.h>
#include <test/evmc/helpers.hpp>
#include <cstdlib>
#include <chrono>

23
test/evmc/CMakeLists.txt Normal file
View File

@ -0,0 +1,23 @@
# EVMC: Ethereum Client-VM Connector API.
# Copyright 2016-2019 The EVMC Authors.
# Licensed under the Apache License, Version 2.0.
add_library(evmc_loader STATIC loader.h loader.c)
set_target_properties(evmc_loader PROPERTIES LINKER_LANGUAGE C)
target_link_libraries(evmc_loader INTERFACE ${CMAKE_DL_LIBS})
target_include_directories(evmc_loader PUBLIC ${PROJECT_SOURCE_DIR}/test/)
set_target_properties(evmc_loader PROPERTIES
POSITION_INDEPENDENT_CODE TRUE
)
add_library(evmc INTERFACE)
target_sources(evmc INTERFACE
${PROJECT_SOURCE_DIR}/test/evmc/evmc.h
${PROJECT_SOURCE_DIR}/test/evmc/evmc.hpp
${PROJECT_SOURCE_DIR}/test/evmc/helpers.h
${PROJECT_SOURCE_DIR}/test/evmc/helpers.hpp
${PROJECT_SOURCE_DIR}/test/evmc/utils.h
)
target_include_directories(evmc INTERFACE ${PROJECT_SOURCE_DIR}/test/)
target_link_libraries(evmc INTERFACE evmc_loader)

202
test/evmc/LICENSE Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

1029
test/evmc/evmc.h Normal file

File diff suppressed because it is too large Load Diff

365
test/evmc/evmc.hpp Normal file
View File

@ -0,0 +1,365 @@
/* EVMC: Ethereum Client-VM Connector API.
* Copyright 2018-2019 The EVMC Authors.
* Licensed under the Apache License, Version 2.0.
*/
#pragma once
#include <evmc/evmc.h>
#include <evmc/helpers.h>
#include <initializer_list>
#include <utility>
/// EVMC C++ API - wrappers and bindings for C++
/// @ingroup cpp
namespace evmc
{
/// @copydoc evmc_result
///
/// This is a RAII wrapper for evmc_result and objects of this type
/// automatically release attached resources.
class result : private evmc_result
{
public:
using evmc_result::create_address;
using evmc_result::gas_left;
using evmc_result::output_data;
using evmc_result::output_size;
using evmc_result::status_code;
/// Converting constructor from raw evmc_result.
explicit result(evmc_result const& res) noexcept : evmc_result{res} {}
/// Destructor responsible for automatically releasing attached resources.
~result() noexcept
{
if (release)
release(this);
}
/// Move constructor.
result(result&& other) noexcept : evmc_result{other}
{
other.release = nullptr; // Disable releasing of the rvalue object.
}
/// Move assignment operator.
///
/// The self-assigment MUST never happen.
///
/// @param other The other result object.
/// @return The reference to the left-hand side object.
result& operator=(result&& other) noexcept
{
this->~result(); // Release this object.
static_cast<evmc_result&>(*this) = other; // Copy data.
other.release = nullptr; // Disable releasing of the rvalue object.
return *this;
}
/// Releases the ownership and returns the raw copy of evmc_result.
///
/// This method drops the ownership of the result
/// (result's resources are not going to be released when this object is destructed).
/// It is the caller's responsibility having the returned copy of the result to release it.
/// This object MUST NOT be used after this method is invoked.
///
/// @return The copy of this object converted to raw evmc_result.
evmc_result release_raw() noexcept
{
const auto out = evmc_result{*this}; // Copy data.
this->release = nullptr; // Disable releasing of this object.
return out;
}
};
/// @copybrief evmc_instance
///
/// This is a RAII wrapper for evmc_instance and objects of this type
/// automatically destroys the VM instance.
class vm
{
public:
/// Converting constructor from evmc_instance.
explicit vm(evmc_instance* instance) noexcept : m_instance{instance} {}
/// Destructor responsible for automatically destroying the VM instance.
~vm() noexcept { m_instance->destroy(m_instance); }
/// The constructor that captures a VM instance and configures the instance
/// with provided list of options.
vm(evmc_instance* instance,
std::initializer_list<std::pair<const char*, const char*>> options) noexcept
: m_instance{instance}
{
for (auto option : options)
set_option(option.first, option.second);
}
/// 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_instance::name
char const* name() const noexcept { return m_instance->name; }
/// @copydoc evmc_instance::version
char const* version() const noexcept { return m_instance->version; }
/// @copydoc evmc::instance::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(evmc_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, &ctx, rev, &msg, code, code_size)};
}
private:
evmc_instance* const m_instance = nullptr;
};
/// The EVMC Host interface
class HostInterface
{
public:
virtual ~HostInterface() noexcept = default;
/// @copydoc evmc_host_interface::account_exists
virtual bool account_exists(const evmc_address& addr) noexcept = 0;
/// @copydoc evmc_host_interface::get_storage
virtual evmc_bytes32 get_storage(const evmc_address& addr,
const evmc_bytes32& key) noexcept = 0;
/// @copydoc evmc_host_interface::set_storage
virtual evmc_storage_status set_storage(const evmc_address& addr,
const evmc_bytes32& key,
const evmc_bytes32& value) noexcept = 0;
/// @copydoc evmc_host_interface::get_balance
virtual evmc_uint256be get_balance(const evmc_address& addr) noexcept = 0;
/// @copydoc evmc_host_interface::get_code_size
virtual size_t get_code_size(const evmc_address& addr) noexcept = 0;
/// @copydoc evmc_host_interface::get_code_hash
virtual evmc_bytes32 get_code_hash(const evmc_address& addr) noexcept = 0;
/// @copydoc evmc_host_interface::copy_code
virtual size_t copy_code(const evmc_address& addr,
size_t code_offset,
uint8_t* buffer_data,
size_t buffer_size) noexcept = 0;
/// @copydoc evmc_host_interface::selfdestruct
virtual void selfdestruct(const evmc_address& addr,
const evmc_address& beneficiary) noexcept = 0;
/// @copydoc evmc_host_interface::call
virtual result call(const evmc_message& msg) noexcept = 0;
/// @copydoc evmc_host_interface::get_tx_context
virtual evmc_tx_context get_tx_context() noexcept = 0;
/// @copydoc evmc_host_interface::get_block_hash
virtual evmc_bytes32 get_block_hash(int64_t block_number) noexcept = 0;
/// @copydoc evmc_host_interface::emit_log
virtual void emit_log(const evmc_address& addr,
const uint8_t* data,
size_t data_size,
const evmc_bytes32 topics[],
size_t num_topics) noexcept = 0;
};
/// Wrapper around EVMC host context / host interface.
///
/// To be used by VM implementations as better alternative to using ::evmc_context directly.
class HostContext : public HostInterface
{
evmc_context* context = nullptr;
evmc_tx_context tx_context = {};
public:
/// Implicit converting constructor from evmc_context.
HostContext(evmc_context* ctx) noexcept : context{ctx} {} // NOLINT
bool account_exists(const evmc_address& address) noexcept final
{
return context->host->account_exists(context, &address);
}
evmc_bytes32 get_storage(const evmc_address& address, const evmc_bytes32& key) noexcept final
{
return context->host->get_storage(context, &address, &key);
}
evmc_storage_status set_storage(const evmc_address& address,
const evmc_bytes32& key,
const evmc_bytes32& value) noexcept final
{
return context->host->set_storage(context, &address, &key, &value);
}
evmc_uint256be get_balance(const evmc_address& address) noexcept final
{
return context->host->get_balance(context, &address);
}
size_t get_code_size(const evmc_address& address) noexcept final
{
return context->host->get_code_size(context, &address);
}
evmc_bytes32 get_code_hash(const evmc_address& address) noexcept final
{
return context->host->get_code_hash(context, &address);
}
size_t copy_code(const evmc_address& address,
size_t code_offset,
uint8_t* buffer_data,
size_t buffer_size) noexcept final
{
return context->host->copy_code(context, &address, code_offset, buffer_data, buffer_size);
}
void selfdestruct(const evmc_address& address, const evmc_address& beneficiary) noexcept final
{
context->host->selfdestruct(context, &address, &beneficiary);
}
result call(const evmc_message& message) noexcept final
{
return result{context->host->call(context, &message)};
}
/// @copydoc HostInterface::get_tx_context()
///
/// The implementation caches the received transaction context
/// by assuming that the block timestamp should never be zero.
///
/// @return The cached transaction context.
evmc_tx_context get_tx_context() noexcept final
{
if (tx_context.block_timestamp == 0)
tx_context = context->host->get_tx_context(context);
return tx_context;
}
evmc_bytes32 get_block_hash(int64_t number) noexcept final
{
return context->host->get_block_hash(context, number);
}
void emit_log(const evmc_address& address,
const uint8_t* data,
size_t data_size,
const evmc_bytes32 topics[],
size_t topics_count) noexcept final
{
context->host->emit_log(context, &address, data, data_size, topics, topics_count);
}
};
/// Abstract class to be used by Host implementations.
///
/// When implementing EVMC Host, you can directly inherit from the evmc::Host class.
/// This way your implementation will be simpler by avoiding manual handling
/// of the ::evmc_context and the ::evmc_context::host.
class Host : public HostInterface, public evmc_context
{
public:
inline Host() noexcept;
};
namespace internal
{
inline bool account_exists(evmc_context* h, const evmc_address* addr) noexcept
{
return static_cast<Host*>(h)->account_exists(*addr);
}
inline evmc_bytes32 get_storage(evmc_context* h,
const evmc_address* addr,
const evmc_bytes32* key) noexcept
{
return static_cast<Host*>(h)->get_storage(*addr, *key);
}
inline evmc_storage_status set_storage(evmc_context* h,
const evmc_address* addr,
const evmc_bytes32* key,
const evmc_bytes32* value) noexcept
{
return static_cast<Host*>(h)->set_storage(*addr, *key, *value);
}
inline evmc_uint256be get_balance(evmc_context* h, const evmc_address* addr) noexcept
{
return static_cast<Host*>(h)->get_balance(*addr);
}
inline size_t get_code_size(evmc_context* h, const evmc_address* addr) noexcept
{
return static_cast<Host*>(h)->get_code_size(*addr);
}
inline evmc_bytes32 get_code_hash(evmc_context* h, const evmc_address* addr) noexcept
{
return static_cast<Host*>(h)->get_code_hash(*addr);
}
inline size_t copy_code(evmc_context* h,
const evmc_address* addr,
size_t code_offset,
uint8_t* buffer_data,
size_t buffer_size) noexcept
{
return static_cast<Host*>(h)->copy_code(*addr, code_offset, buffer_data, buffer_size);
}
inline void selfdestruct(evmc_context* h,
const evmc_address* addr,
const evmc_address* beneficiary) noexcept
{
static_cast<Host*>(h)->selfdestruct(*addr, *beneficiary);
}
inline evmc_result call(evmc_context* h, const evmc_message* msg) noexcept
{
return static_cast<Host*>(h)->call(*msg).release_raw();
}
inline evmc_tx_context get_tx_context(evmc_context* h) noexcept
{
return static_cast<Host*>(h)->get_tx_context();
}
inline evmc_bytes32 get_block_hash(evmc_context* h, int64_t block_number) noexcept
{
return static_cast<Host*>(h)->get_block_hash(block_number);
}
inline void emit_log(evmc_context* h,
const evmc_address* addr,
const uint8_t* data,
size_t data_size,
const evmc_bytes32 topics[],
size_t num_topics) noexcept
{
static_cast<Host*>(h)->emit_log(*addr, data, data_size, topics, num_topics);
}
constexpr evmc_host_interface interface{
account_exists, get_storage, set_storage, get_balance, get_code_size, get_code_hash,
copy_code, selfdestruct, call, get_tx_context, get_block_hash, emit_log,
};
} // namespace internal
inline Host::Host() noexcept : evmc_context{&internal::interface} {}
} // namespace evmc

171
test/evmc/helpers.h Normal file
View File

@ -0,0 +1,171 @@
/* EVMC: Ethereum Client-VM Connector API.
* Copyright 2018-2019 The EVMC Authors.
* Licensed under the Apache License, Version 2.0.
*/
/**
* EVMC Helpers
*
* A collection of C helper functions for invoking a VM instance methods.
* These are convenient for languages where invoking function pointers
* is "ugly" or impossible (such as Go).
*
* It also contains helpers (overloaded operators) for using EVMC types effectively in C++.
*
* @defgroup helpers EVMC Helpers
* @{
*/
#pragma once
#include <evmc/evmc.h>
/**
* Returns true if the VM instance has a compatible ABI version.
*/
static inline int evmc_is_abi_compatible(struct evmc_instance* instance)
{
return instance->abi_version == EVMC_ABI_VERSION;
}
/**
* Returns the name of the VM instance.
*/
static inline const char* evmc_vm_name(struct evmc_instance* instance)
{
return instance->name;
}
/**
* Returns the version of the VM instance.
*/
static inline const char* evmc_vm_version(struct evmc_instance* instance)
{
return instance->version;
}
/**
* Checks if the VM instance has the given capability.
*
* @see evmc_get_capabilities_fn
*/
static inline bool evmc_vm_has_capability(struct evmc_instance* vm,
enum evmc_capabilities capability)
{
return (vm->get_capabilities(vm) & (evmc_capabilities_flagset)capability) != 0;
}
/**
* Destroys the VM instance.
*
* @see evmc_destroy_fn
*/
static inline void evmc_destroy(struct evmc_instance* instance)
{
instance->destroy(instance);
}
/**
* Sets the option for the VM instance, if the feature is supported by the VM.
*
* @see evmc_set_option_fn
*/
static inline enum evmc_set_option_result evmc_set_option(struct evmc_instance* instance,
char const* name,
char const* value)
{
if (instance->set_option)
return instance->set_option(instance, name, value);
return EVMC_SET_OPTION_INVALID_NAME;
}
/**
* Sets the tracer callback for the VM instance, if the feature is supported by the VM.
*
* @see evmc_set_tracer_fn
*/
static inline void evmc_set_tracer(struct evmc_instance* instance,
evmc_trace_callback callback,
struct evmc_tracer_context* context)
{
if (instance->set_tracer)
instance->set_tracer(instance, callback, context);
}
/**
* Executes code in the VM instance.
*
* @see evmc_execute_fn.
*/
static inline struct evmc_result evmc_execute(struct evmc_instance* instance,
struct evmc_context* context,
enum evmc_revision rev,
const struct evmc_message* msg,
uint8_t const* code,
size_t code_size)
{
return instance->execute(instance, context, rev, msg, code, code_size);
}
/**
* Releases the resources allocated to the execution result.
*
* @param result The result object to be released. MUST NOT be NULL.
*
* @see evmc_result::release() evmc_release_result_fn
*/
static inline void evmc_release_result(struct evmc_result* result)
{
if (result->release)
result->release(result);
}
/**
* Helpers for optional storage of evmc_result.
*
* In some contexts (i.e. evmc_result::create_address is unused) objects of
* type evmc_result contains a memory storage that MAY be used by the object
* owner. This group defines helper types and functions for accessing
* the optional storage.
*
* @defgroup result_optional_storage Result Optional Storage
* @{
*/
/**
* The union representing evmc_result "optional storage".
*
* The evmc_result struct contains 24 bytes of optional storage that can be
* reused by the object creator if the object does not contain
* evmc_result::create_address.
*
* A VM implementation MAY use this memory to keep additional data
* when returning result from evmc_execute_fn().
* The host application MAY use this memory to keep additional data
* when returning result of performed calls from evmc_call_fn().
*
* @see evmc_get_optional_storage(), evmc_get_const_optional_storage().
*/
union evmc_result_optional_storage
{
uint8_t bytes[24]; /**< 24 bytes of optional storage. */
void* pointer; /**< Optional pointer. */
};
/** Provides read-write access to evmc_result "optional storage". */
static inline union evmc_result_optional_storage* evmc_get_optional_storage(
struct evmc_result* result)
{
return (union evmc_result_optional_storage*)&result->create_address;
}
/** Provides read-only access to evmc_result "optional storage". */
static inline const union evmc_result_optional_storage* evmc_get_const_optional_storage(
const struct evmc_result* result)
{
return (const union evmc_result_optional_storage*)&result->create_address;
}
/** @} */
/** @} */

98
test/evmc/helpers.hpp Normal file
View File

@ -0,0 +1,98 @@
/* EVMC: Ethereum Client-VM Connector API.
* Copyright 2018-2019 The EVMC Authors.
* Licensed under the Apache License, Version 2.0.
*/
/**
* @file
* A collection of helpers (overloaded operators) for using EVMC types effectively in C++.
*
* @addtogroup helpers
* @{
*/
#pragma once
#include <evmc/evmc.h>
#include <cstring>
#include <functional>
/// The comparator for std::map<evmc_address, ...>.
inline bool operator<(const evmc_address& a, const evmc_address& b)
{
return std::memcmp(a.bytes, b.bytes, sizeof(a.bytes)) < 0;
}
/// The comparator for std::map<evmc_bytes32, ...>.
inline bool operator<(const evmc_bytes32& a, const evmc_bytes32& b)
{
return std::memcmp(a.bytes, b.bytes, sizeof(a.bytes)) < 0;
}
/// The comparator for equality.
inline bool operator==(const evmc_address& a, const evmc_address& b)
{
return std::memcmp(a.bytes, b.bytes, sizeof(a.bytes)) == 0;
}
/// The comparator for equality.
inline bool operator==(const evmc_bytes32& a, const evmc_bytes32& b)
{
return std::memcmp(a.bytes, b.bytes, sizeof(a.bytes)) == 0;
}
/// Check if the address is zero (all bytes are zeros).
inline bool is_zero(const evmc_address& address) noexcept
{
return address == evmc_address{};
}
/// Check if the hash is zero (all bytes are zeros).
inline bool is_zero(const evmc_bytes32& x) noexcept
{
return x == evmc_bytes32{};
}
/// FNV1a hash function with 64-bit result.
inline uint64_t fnv1a_64(const uint8_t* ptr, size_t len)
{
constexpr uint64_t prime = 1099511628211ULL;
constexpr uint64_t offset_basis = 14695981039346656037ULL;
uint64_t ret = offset_basis;
for (size_t i = 0; i < len; i++)
{
ret ^= ptr[i];
ret *= prime;
}
return ret;
}
namespace std
{
/// Hash operator template specialization for evmc_address needed for unordered containers.
template <>
struct hash<evmc_address>
{
/// Hash operator using FNV1a.
std::enable_if<sizeof(size_t) == 8, std::size_t>::type operator()(const evmc_address& s) const
noexcept
{
return fnv1a_64(s.bytes, sizeof(s.bytes));
}
};
/// Hash operator template needed for std::unordered_set and others using hashes.
template <>
struct hash<evmc_bytes32>
{
/// Hash operator using FNV1a.
std::enable_if<sizeof(size_t) == 8, std::size_t>::type operator()(const evmc_bytes32& s) const
noexcept
{
return fnv1a_64(s.bytes, sizeof(s.bytes));
}
};
} // namespace std
/** @} */

240
test/evmc/loader.c Normal file
View File

@ -0,0 +1,240 @@
/* EVMC: Ethereum Client-VM Connector API.
* Copyright 2018-2019 The EVMC Authors.
* Licensed under the Apache License, Version 2.0.
*/
#include <evmc/loader.h>
#include <evmc/evmc.h>
#include <evmc/helpers.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#if defined(EVMC_LOADER_MOCK)
#include "../../test/unittests/loader_mock.h"
#elif _WIN32
#include <Windows.h>
#define DLL_HANDLE HMODULE
#define DLL_OPEN(filename) LoadLibrary(filename)
#define DLL_CLOSE(handle) FreeLibrary(handle)
#define DLL_GET_CREATE_FN(handle, name) (evmc_create_fn)(uintptr_t) GetProcAddress(handle, name)
#define DLL_GET_ERROR_MSG() NULL
#else
#include <dlfcn.h>
#define DLL_HANDLE void*
#define DLL_OPEN(filename) dlopen(filename, RTLD_LAZY)
#define DLL_CLOSE(handle) dlclose(handle)
#define DLL_GET_CREATE_FN(handle, name) (evmc_create_fn)(uintptr_t) dlsym(handle, name)
#define DLL_GET_ERROR_MSG() dlerror()
#endif
#ifdef __has_attribute
#if __has_attribute(format)
#define ATTR_FORMAT(archetype, string_index, first_to_check) \
__attribute__((format(archetype, string_index, first_to_check)))
#endif
#endif
#ifndef ATTR_FORMAT
#define ATTR_FORMAT(...)
#endif
#if _WIN32
#define strcpy_sx strcpy_s
#else
/*
* Limited variant of strcpy_s().
*
* Provided for C standard libraries where strcpy_s() is not available.
* The availability check might need to adjusted for other C standard library implementations.
*/
#if !defined(EVMC_LOADER_MOCK)
static
#endif
int
strcpy_sx(char* restrict dest, size_t destsz, const char* restrict src)
{
size_t len = strlen(src);
if (len >= destsz)
{
// The input src will not fit into the dest buffer.
// Set the first byte of the dest to null to make it effectively empty string
// and return error.
dest[0] = 0;
return 1;
}
memcpy(dest, src, len);
dest[len] = 0;
return 0;
}
#endif
#define PATH_MAX_LENGTH 4096
static const char* last_error_msg = NULL;
#define LAST_ERROR_MSG_BUFFER_SIZE 511
// Buffer for formatted error messages.
// It has one null byte extra to avoid buffer read overflow during concurrent access.
static char last_error_msg_buffer[LAST_ERROR_MSG_BUFFER_SIZE + 1];
ATTR_FORMAT(printf, 2, 3)
static enum evmc_loader_error_code set_error(enum evmc_loader_error_code error_code,
const char* format,
...)
{
va_list args;
va_start(args, format);
if (vsnprintf(last_error_msg_buffer, LAST_ERROR_MSG_BUFFER_SIZE, format, args) <
LAST_ERROR_MSG_BUFFER_SIZE)
last_error_msg = last_error_msg_buffer;
va_end(args);
return error_code;
}
evmc_create_fn evmc_load(const char* filename, enum evmc_loader_error_code* error_code)
{
last_error_msg = NULL; // Reset last error.
enum evmc_loader_error_code ec = EVMC_LOADER_SUCCESS;
evmc_create_fn create_fn = NULL;
if (!filename)
{
ec = set_error(EVMC_LOADER_INVALID_ARGUMENT, "invalid argument: file name cannot be null");
goto exit;
}
const size_t length = strlen(filename);
if (length == 0)
{
ec = set_error(EVMC_LOADER_INVALID_ARGUMENT, "invalid argument: file name cannot be empty");
goto exit;
}
else if (length > PATH_MAX_LENGTH)
{
ec = set_error(EVMC_LOADER_INVALID_ARGUMENT,
"invalid argument: file name is too long (%d, maximum allowed length is %d)",
(int)length, PATH_MAX_LENGTH);
goto exit;
}
DLL_HANDLE handle = DLL_OPEN(filename);
if (!handle)
{
// Get error message if available.
last_error_msg = DLL_GET_ERROR_MSG();
if (last_error_msg)
ec = EVMC_LOADER_CANNOT_OPEN;
else
ec = set_error(EVMC_LOADER_CANNOT_OPEN, "cannot open %s", filename);
goto exit;
}
// Create name buffer with the prefix.
const char prefix[] = "evmc_create_";
const size_t prefix_length = strlen(prefix);
char prefixed_name[sizeof(prefix) + PATH_MAX_LENGTH];
strcpy_sx(prefixed_name, sizeof(prefixed_name), prefix);
// Find filename in the path.
const char* sep_pos = strrchr(filename, '/');
#if _WIN32
// On Windows check also Windows classic path separator.
const char* sep_pos_windows = strrchr(filename, '\\');
sep_pos = sep_pos_windows > sep_pos ? sep_pos_windows : sep_pos;
#endif
const char* name_pos = sep_pos ? sep_pos + 1 : filename;
// Skip "lib" prefix if present.
const char lib_prefix[] = "lib";
const size_t lib_prefix_length = strlen(lib_prefix);
if (strncmp(name_pos, lib_prefix, lib_prefix_length) == 0)
name_pos += lib_prefix_length;
char* base_name = prefixed_name + prefix_length;
strcpy_sx(base_name, PATH_MAX_LENGTH, name_pos);
// Trim the file extension.
char* ext_pos = strrchr(prefixed_name, '.');
if (ext_pos)
*ext_pos = 0;
// Replace all "-" with "_".
char* dash_pos = base_name;
while ((dash_pos = strchr(dash_pos, '-')) != NULL)
*dash_pos++ = '_';
// Search for the built function name.
while ((create_fn = DLL_GET_CREATE_FN(handle, prefixed_name)) == NULL)
{
// Shorten the base name by skipping the `word_` segment.
const char* shorter_name_pos = strchr(base_name, '_');
if (!shorter_name_pos)
break;
memmove(base_name, shorter_name_pos + 1, strlen(shorter_name_pos) + 1);
}
if (!create_fn)
create_fn = DLL_GET_CREATE_FN(handle, "evmc_create");
if (!create_fn)
{
DLL_CLOSE(handle);
ec = set_error(EVMC_LOADER_SYMBOL_NOT_FOUND, "EVMC create function not found in %s",
filename);
}
exit:
if (error_code)
*error_code = ec;
return create_fn;
}
const char* evmc_last_error_msg()
{
const char* m = last_error_msg;
last_error_msg = NULL;
return m;
}
struct evmc_instance* evmc_load_and_create(const char* filename,
enum evmc_loader_error_code* error_code)
{
// First load the DLL. This also resets the last_error_msg;
evmc_create_fn create_fn = evmc_load(filename, error_code);
if (!create_fn)
return NULL;
enum evmc_loader_error_code ec = EVMC_LOADER_SUCCESS;
struct evmc_instance* instance = create_fn();
if (!instance)
{
ec = set_error(EVMC_LOADER_INSTANCE_CREATION_FAILURE,
"creating EVMC instance of %s has failed", filename);
goto exit;
}
if (!evmc_is_abi_compatible(instance))
{
ec = set_error(EVMC_LOADER_ABI_VERSION_MISMATCH,
"EVMC ABI version %d of %s mismatches the expected version %d",
instance->abi_version, filename, EVMC_ABI_VERSION);
evmc_destroy(instance);
instance = NULL;
goto exit;
}
exit:
if (error_code)
*error_code = ec;
return instance;
}

133
test/evmc/loader.h Normal file
View File

@ -0,0 +1,133 @@
/* EVMC: Ethereum Client-VM Connector API.
* Copyright 2018-2019 The EVMC Authors.
* Licensed under the Apache License, Version 2.0.
*/
/**
* EVMC Loader Library
*
* The EVMC Loader Library supports loading VMs implemented as Dynamically Loaded Libraries
* (DLLs, shared objects).
*
* @defgroup loader EVMC Loader
* @{
*/
#pragma once
#if __cplusplus
extern "C" {
#endif
/** The function pointer type for EVMC create functions. */
typedef struct evmc_instance* (*evmc_create_fn)(void);
/** Error codes for the EVMC loader. */
enum evmc_loader_error_code
{
/** The loader succeeded. */
EVMC_LOADER_SUCCESS = 0,
/** The loader cannot open the given file name. */
EVMC_LOADER_CANNOT_OPEN = 1,
/** The VM create function not found. */
EVMC_LOADER_SYMBOL_NOT_FOUND = 2,
/** The invalid argument value provided. */
EVMC_LOADER_INVALID_ARGUMENT = 3,
/** The creation of a VM instance has failed. */
EVMC_LOADER_INSTANCE_CREATION_FAILURE = 4,
/** The ABI version of the VM instance has mismatched. */
EVMC_LOADER_ABI_VERSION_MISMATCH = 5
};
/**
* Dynamically loads the EVMC module with a VM implementation.
*
* This function tries to open a dynamically loaded library (DLL) at the given `filename`.
* On UNIX-like systems dlopen() function is used. On Windows LoadLibrary() function is used.
*
* If the file does not exist or is not a valid shared library the ::EVMC_LOADER_CANNOT_OPEN error
* code is signaled and NULL is returned.
*
* After the DLL is successfully loaded the function tries to find the EVM create function in the
* library. The `filename` is used to guess the EVM name and the name of the create function.
* The create function name is constructed by the following rules. Consider example path:
* "/ethereum/libexample-interpreter.so".
* - the filename is taken from the path:
* "libexample-interpreter.so",
* - the "lib" prefix and file extension are stripped from the name:
* "example-interpreter"
* - all "-" are replaced with "_" to construct _base name_:
* "example_interpreter",
* - the function name "evmc_create_" + _base name_ is searched in the library:
* "evmc_create_example_interpreter",
* - if function not found, the _base name_ is shorten by skipping the first word separated by "_":
* "interpreter",
* - then, the function of the shorter name "evmc_create_" + _base name_ is searched in the library:
* "evmc_create_interpreter",
* - the name shortening continues until a function is found or the name cannot be shorten more,
* - lastly, when no function found, the function name "evmc_create" is searched in the library.
*
* If the create function is found in the library, the pointer to the function is returned.
* Otherwise, the ::EVMC_LOADER_SYMBOL_NOT_FOUND error code is signaled and NULL is returned.
*
* It is safe to call this function with the same filename argument multiple times
* (the DLL is not going to be loaded multiple times).
*
* @param filename The null terminated path (absolute or relative) to an EVMC module
* (dynamically loaded library) containing the VM implementation.
* If the value is NULL, an empty C-string or longer than the path maximum length
* the ::EVMC_LOADER_INVALID_ARGUMENT is signaled.
* @param error_code The pointer to the error code. If not NULL the value is set to
* ::EVMC_LOADER_SUCCESS on success or any other error code as described above.
* @return The pointer to the EVM create function or NULL in case of error.
*/
evmc_create_fn evmc_load(const char* filename, enum evmc_loader_error_code* error_code);
/**
* Dynamically loads the EVMC module and creates the VM instance.
*
* This is a macro for creating the VM instance with the function returned from evmc_load().
* The function signals the same errors as evmc_load() and additionally:
* - ::EVMC_LOADER_INSTANCE_CREATION_FAILURE when the create function returns NULL,
* - ::EVMC_LOADER_ABI_VERSION_MISMATCH when the created VM instance has ABI version different
* from the ABI version of this library (::EVMC_ABI_VERSION).
*
* It is safe to call this function with the same filename argument multiple times:
* the DLL is not going to be loaded multiple times, but the function will return new VM instance
* each time.
*
* @param filename The null terminated path (absolute or relative) to an EVMC module
* (dynamically loaded library) containing the VM implementation.
* If the value is NULL, an empty C-string or longer than the path maximum length
* the ::EVMC_LOADER_INVALID_ARGUMENT is signaled.
* @param error_code The pointer to the error code. If not NULL the value is set to
* ::EVMC_LOADER_SUCCESS on success or any other error code as described above.
* @return The pointer to the created VM or NULL in case of error.
*/
struct evmc_instance* evmc_load_and_create(const char* filename,
enum evmc_loader_error_code* error_code);
/**
* Returns the human-readable message describing the most recent error
* that occurred in EVMC loading since the last call to this function.
*
* In case any loading function returned ::EVMC_LOADER_SUCCESS this function always returns NULL.
* In case of error code other than success returned, this function MAY return the error message.
* Calling this function "consumes" the error message and the function will return NULL
* from subsequent invocations.
* This function is not thread-safe.
*
* @return Error message or NULL if no additional information is available.
* The returned pointer MUST NOT be freed by the caller.
*/
const char* evmc_last_error_msg();
#if __cplusplus
}
#endif
/** @} */

36
test/evmc/utils.h Normal file
View File

@ -0,0 +1,36 @@
/* EVMC: Ethereum Client-VM Connector API.
* Copyright 2018-2019 The EVMC Authors.
* Licensed under the Apache License, Version 2.0.
*/
#pragma once
/**
* @file
* A collection of helper macros to handle some non-portable features of C/C++ compilers.
*
* @addtogroup helpers
* @{
*/
/**
* @def EVMC_EXPORT
* Marks a function to be exported from a shared library.
*/
#if defined _MSC_VER || defined __MINGW32__
#define EVMC_EXPORT __declspec(dllexport)
#else
#define EVMC_EXPORT __attribute__((visibility("default")))
#endif
/**
* @def EVMC_NOEXCEPT
* Safe way of marking a function with `noexcept` C++ specifier.
*/
#if __cplusplus
#define EVMC_NOEXCEPT noexcept
#else
#define EVMC_NOEXCEPT
#endif
/** @} */

View File

@ -34,4 +34,4 @@ add_executable(isoltest
../libyul/YulOptimizerTest.cpp
../libyul/YulInterpreterTest.cpp
)
target_link_libraries(isoltest PRIVATE libsolc solidity yulInterpreter evmasm Boost::boost Boost::program_options Boost::unit_test_framework)
target_link_libraries(isoltest PRIVATE evmc libsolc solidity yulInterpreter evmasm Boost::boost Boost::program_options Boost::unit_test_framework)

View File

@ -9,8 +9,11 @@ add_dependencies(ossfuzz
)
if (OSSFUZZ)
add_custom_target(ossfuzz_proto)
add_dependencies(ossfuzz_proto yul_proto_ossfuzz yul_proto_diff_ossfuzz)
add_custom_target(ossfuzz_proto)
add_dependencies(ossfuzz_proto yul_proto_ossfuzz yul_proto_diff_ossfuzz)
add_custom_target(ossfuzz_abiv2)
add_dependencies(ossfuzz_abiv2 abiv2_proto_ossfuzz)
endif()
if (OSSFUZZ)
@ -49,6 +52,26 @@ if (OSSFUZZ)
protobuf-mutator.a
protobuf.a
FuzzingEngine.a)
add_executable(abiv2_proto_ossfuzz
../../EVMHost.cpp
abiV2ProtoFuzzer.cpp
abiV2FuzzerCommon.cpp
protoToAbiV2.cpp
abiV2Proto.pb.cc
)
target_include_directories(abiv2_proto_ossfuzz PRIVATE
/src/LPM/external.protobuf/include
/src/libprotobuf-mutator
/src/evmone/include
)
target_link_libraries(abiv2_proto_ossfuzz PRIVATE solidity
evmone intx ethash keccak evmc-instructions evmc
protobuf-mutator-libfuzzer.a
protobuf-mutator.a
protobuf.a
FuzzingEngine.a
)
else()
add_library(solc_opt_ossfuzz
solc_opt_ossfuzz.cpp
@ -99,4 +122,23 @@ else()
# protobuf-mutator.a
# protobuf.a
# FuzzingEngine.a)
# add_executable(abiv2_proto_ossfuzz
# ../../EVMHost.cpp
# abiV2ProtoFuzzer.cpp
# abiV2FuzzerCommon.cpp
# protoToAbiV2.cpp
# abiV2Proto.pb.cc
# )
# target_include_directories(abiv2_proto_ossfuzz PRIVATE
# /src/LPM/external.protobuf/include
# /src/libprotobuf-mutator
# /src/evmone/include
# )
# target_link_libraries(abiv2_proto_ossfuzz PRIVATE solidity
# evmone intx ethash keccak evmc-instructions evmc
# protobuf-mutator-libfuzzer.a
# protobuf-mutator.a
# protobuf.a
# FuzzingEngine.a
# )
endif()

View File

@ -27,7 +27,7 @@ dev::bytes SolidityCompilationFramework::compileContract(
);
std::cerr << "Compiling contract failed" << std::endl;
}
dev::eth::LinkerObject obj = m_compiler.runtimeObject(
dev::eth::LinkerObject obj = m_compiler.object(
_contractName.empty() ?
m_compiler.lastContractName() :
_contractName

View File

@ -28,7 +28,7 @@ public:
protected:
dev::solidity::CompilerStack m_compiler;
langutil::EVMVersion m_evmVersion;
dev::solidity::OptimiserSettings m_optimiserSettings = dev::solidity::OptimiserSettings::full();
dev::solidity::OptimiserSettings m_optimiserSettings = dev::solidity::OptimiserSettings::none();
};
}
}

View File

@ -15,15 +15,86 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <test/EVMHost.h>
#include <test/tools/ossfuzz/abiV2FuzzerCommon.h>
#include <test/tools/ossfuzz/protoToAbiV2.h>
#include <evmone/evmone.h>
#include <src/libfuzzer/libfuzzer_macro.h>
#include <fstream>
static evmc::vm evmone = evmc::vm{evmc_create_evmone()};
using namespace dev::test::abiv2fuzzer;
using namespace dev::test;
using namespace dev;
using namespace std;
namespace
{
/// Test function returns a uint256 value
static size_t const expectedOutputLength = 32;
/// Expected output value is decimal 1000 or hex 03E8
static uint8_t const expectedOutput[expectedOutputLength] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, u'\x03', u'\xe8'
};
/// Compares the contents of the memory address pointed to
/// by `_result` of `_length` bytes to the expected output.
/// Returns true if `_result` matches expected output, false
/// otherwise.
bool isOutputExpected(uint8_t const* _result, size_t _length)
{
if (_length != expectedOutputLength)
return false;
return (memcmp(_result, expectedOutput, expectedOutputLength) == 0);
}
/// Accepts a reference to a user-specified input and returns an
/// evmc_message with all of its fields zero initialized except
/// gas and input fields.
/// The gas field is set to the maximum permissible value so that we
/// don't run into out of gas errors. The input field is copied from
/// user input.
evmc_message initializeMessage(dev::bytes const& _input)
{
// Zero initialize all message fields
evmc_message msg = {};
// Gas available (value of type int64_t) is set to its maximum
// value.
msg.gas = std::numeric_limits<int64_t>::max();
msg.input_data = _input.data();
msg.input_size = _input.size();
return msg;
}
/// Accepts host context implementation, and keccak256 hash of the function
/// to be called at a specified address in the simulated blockchain as
/// input and returns the result of the execution of the called function.
evmc::result executeContract(
EVMHost& _hostContext,
dev::bytes const& _functionHash,
evmc_address _deployedAddress
)
{
evmc_message message = initializeMessage(_functionHash);
message.destination = _deployedAddress;
message.kind = EVMC_CALL;
return _hostContext.call(message);
}
/// Accepts a reference to host context implementation and byte code
/// as input and deploys it on the simulated blockchain. Returns the
/// result of deployment.
evmc::result deployContract(EVMHost& _hostContext, dev::bytes const& _code)
{
evmc_message message = initializeMessage(_code);
message.kind = EVMC_CREATE;
return _hostContext.call(message);
}
}
DEFINE_PROTO_FUZZER(Contract const& _input)
{
string contract_source = ProtoConverter{}.contractToString(_input);
@ -44,17 +115,48 @@ DEFINE_PROTO_FUZZER(Contract const& _input)
{
// Compile contract generated by the proto fuzzer
SolidityCompilationFramework solCompilationFramework;
std::string contractName = ":Factory";
std::string contractName = ":C";
byteCode = solCompilationFramework.compileContract(contract_source, contractName);
Json::Value methodIdentifiers = solCompilationFramework.getMethodIdentifiers();
// We always call the function test() that is defined in proto converter template
hexEncodedInput = methodIdentifiers["test()"].asString();
}
catch (...)
// Ignore compilation failures
catch (Exception const&)
{
cout << contract_source << endl;
throw;
return;
}
// TODO: Call evmone wrapper here
return;
if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_CODE"))
{
ofstream of(dump_path);
of << toHex(byteCode);
}
// We target the default EVM which is the latest
langutil::EVMVersion version = {};
EVMHost hostContext(version, evmone);
// Deploy contract and signal failure if deploy failed
evmc::result createResult = deployContract(hostContext, byteCode);
solAssert(
createResult.status_code == EVMC_SUCCESS,
"Proto ABIv2 Fuzzer: Contract creation failed"
);
// Execute test function and signal failure if EVM reverted or
// did not return expected output on successful execution.
evmc::result callResult = executeContract(
hostContext,
fromHex(hexEncodedInput),
createResult.create_address
);
// We don't care about EVM One failures other than EVMC_REVERT
solAssert(callResult.status_code != EVMC_REVERT, "Proto ABIv2 fuzzer: EVM One reverted");
if (callResult.status_code == EVMC_SUCCESS)
solAssert(
isOutputExpected(callResult.output_data, callResult.output_size),
"Proto ABIv2 fuzzer: ABIv2 coding failure found"
);
}