Compare commits

...

8 Commits

Author SHA1 Message Date
Dmytro Sydorchenko d1912a39b5 comments and some refactoring 2023-04-04 16:42:10 -04:00
Dmytro Sydorchenko b54a28c2d6 draft solution without unit tests, exceptions, etc 2023-03-31 18:37:24 -04:00
Dmytro Sydorchenko c92664d5b8 antler-run draft changes 2023-03-14 10:18:34 -04:00
Dmytro Sydorchenko 004569022b Revert "sketchy runner code with example so"
This reverts commit e246a9f21e.
2023-03-01 20:07:01 -05:00
Dmytro Sydorchenko 389cb1d432 empty antler-run with neccesary libraries linked 2023-03-01 01:17:51 -05:00
Dmytro Sydorchenko e9b221c176 restore examples/hello 2023-03-01 01:16:03 -05:00
Dmytro Sydorchenko e246a9f21e sketchy runner code with example so 2023-03-01 01:15:44 -05:00
Dmytro Sydorchenko 4a359eb77b test code for antler-run 2023-03-01 01:12:28 -05:00
20 changed files with 1203 additions and 94 deletions
+3
View File
@@ -13,3 +13,6 @@
[submodule "tools/external/eos-vm"]
path = tools/external/eos-vm
url = https://github.com/AntelopeIO/eos-vm
[submodule "tools/external/antler-interfaces"]
path = tools/external/antler-interfaces
url = git@github.com:AntelopeIO/antler-interfaces
@@ -560,6 +560,37 @@ datastream<Stream>& operator >> ( datastream<Stream>& ds, std::array<T,N>& v ) {
return ds;
}
/**
* Serialize capi_checksum160, capi_checksum256, capi_checksum512 struct
*
* @param ds - The stream to write
* @param v - The value to serialize
* @tparam Stream - Type of datastream buffer
* @return datastream<Stream>& - Reference to the datastream
*/
template<typename Stream, typename T, typename = std::enable_if_t<std::is_array_v<decltype(T::hash)>>>
datastream<Stream>& operator << ( datastream<Stream>& ds, const T& v ) {
for( int i = 0; i < sizeof(v.hash)/sizeof(v.hash[0]); ++i )
ds << v.hash[i];
return ds;
}
/**
* Deserialize capi_checksum160, capi_checksum256, capi_checksum512 struct
*
* @param ds - The stream to read
* @param v - The destination for deserialized value
* @tparam Stream - Type of datastream buffer
* @return datastream<Stream>& - Reference to the datastream
*/
template<typename Stream, typename T, typename = std::enable_if_t<std::is_array_v<decltype(T::hash)>>>
datastream<Stream>& operator >> ( datastream<Stream>& ds, T& v ) {
for( int i = 0; i < sizeof(v.hash)/sizeof(v.hash[0]); ++i )
ds >> v.hash[i];
return ds;
}
namespace _datastream_detail {
/**
* Check if type T is a pointer
+1 -79
View File
@@ -85,85 +85,7 @@ extern "C" {
___has_failed = false;
___earlier_unit_test_has_failed = false;
// preset the print functions
intrinsics::set_intrinsic<intrinsics::prints_l>([](const char* cs, uint32_t l) {
_prints_l(cs, l, eosio::cdt::output_stream_kind::std_out);
});
intrinsics::set_intrinsic<intrinsics::prints>([](const char* cs) {
_prints(cs, eosio::cdt::output_stream_kind::std_out);
});
intrinsics::set_intrinsic<intrinsics::printi>([](int64_t v) {
printf("%lli", v);
});
intrinsics::set_intrinsic<intrinsics::printui>([](uint64_t v) {
printf("%llu", v);
});
intrinsics::set_intrinsic<intrinsics::printi128>([](const int128_t* v) {
int* tmp = (int*)v;
printf("0x%04x%04x%04x%04x", tmp[0], tmp[1], tmp[2], tmp[3]);
});
intrinsics::set_intrinsic<intrinsics::printui128>([](const uint128_t* v) {
int* tmp = (int*)v;
printf("0x%04x%04x%04x%04x", tmp[0], tmp[1], tmp[2], tmp[3]);
});
intrinsics::set_intrinsic<intrinsics::printsf>([](float v) {
char buff[512] = {0};
std::string ret = std::to_string((int)v);
memcpy(buff, ret.c_str(), ret.size());
v -= (int)v;
buff[ret.size()] = '.';
size_t size = ret.size();
for (size_t i=size+1; i < size+10; i++) {
v *= 10;
buff[i] = ((int)v)+'0';
v -= (int)v;
}
prints(buff);
});
intrinsics::set_intrinsic<intrinsics::printdf>([](double v) {
char buff[512] = {0};
std::string ret = std::to_string((long)v);
memcpy(buff, ret.c_str(), ret.size());
v -= (long)v;
buff[ret.size()] = '.';
size_t size = ret.size();
for (size_t i=size+1; i < size+10; i++) {
v *= 10;
buff[i] = ((int)v)+'0';
v -= (int)v;
}
prints(buff);
});
intrinsics::set_intrinsic<intrinsics::printqf>([](const long double* v) {
int* tmp = (int*)v;
printf("0x%04x%04x%04x%04x", tmp[0], tmp[1], tmp[2], tmp[3]);
});
intrinsics::set_intrinsic<intrinsics::printn>([](uint64_t nm) {
std::string s = eosio::name(nm).to_string();
prints_l(s.c_str(), s.length());
});
intrinsics::set_intrinsic<intrinsics::printhex>([](const void* data, uint32_t len) {
constexpr static uint32_t max_stack_buffer_size = 512;
const char* hex_characters = "0123456789abcdef";
uint32_t buffer_size = 2*len;
if(buffer_size < len) eosio_assert( false, "length passed into printhex is too large" );
void* buffer = (max_stack_buffer_size < buffer_size) ? malloc(buffer_size) : alloca(buffer_size);
char* b = reinterpret_cast<char*>(buffer);
const uint8_t* d = reinterpret_cast<const uint8_t*>(data);
for( uint32_t i = 0; i < len; ++i ) {
*b = hex_characters[d[i] >> 4];
++b;
*b = hex_characters[d[i] & 0x0f];
++b;
}
prints_l(reinterpret_cast<const char*>(buffer), buffer_size);
if(max_stack_buffer_size < buffer_size) free(buffer);
});
set_print_intrinsics();
jmp_ret = setjmp(env);
if (jmp_ret == 0) {
+23
View File
@@ -1,4 +1,5 @@
#include "native/eosio/crt.hpp"
#include "native/eosio/intrinsics.hpp"
#include <cstdio>
@@ -42,4 +43,26 @@ void _prints(const char* cstr, uint8_t which) {
if (!___disable_output)
std::putc(cstr[i], which == eosio::cdt::output_stream_kind::std_out ? stdout : stderr);
}
}
template<eosio::native::intrinsics::intrinsic_name ID, typename Fn, typename Ret, typename TupleArgs, size_t... Is>
void set_intrinsic(Fn fn, std::index_sequence<Is...>) {
eosio::native::intrinsics::set_intrinsic<ID>(reinterpret_cast<Ret(*)(typename std::tuple_element<Is, TupleArgs>::type...)>(fn));
}
#define REGISTER_INTRINSIC(ID) \
case eosio::native::intrinsics::ID: \
set_intrinsic<eosio::native::intrinsics::ID, \
decltype(fn), \
eosio::native::intrinsics::__ ## ID ## _types::res_t, \
eosio::native::intrinsics::__ ## ID ## _types::deduced_full_ts> \
(fn, eosio::native::intrinsics::__ ## ID ## _types::is); \
break;
void register_intrinsic(int64_t id, void(*fn)()) {
switch(id) {
INTRINSICS(REGISTER_INTRINSIC);
}
}
+85 -1
View File
@@ -8,6 +8,7 @@
#include <eosio/system.h>
#include <eosio/transaction.h>
#include <eosio/types.h>
#include <eosio/name.hpp>
#include "native/eosio/intrinsics.hpp"
#include "native/eosio/crt.hpp"
#include <softfloat.hpp>
@@ -905,9 +906,92 @@ extern "C" {
uint32_t get_active_security_group(char* data, uint32_t datalen){
return intrinsics::get().call<intrinsics::get_active_security_group>(data, datalen);
}
}
namespace eosio { namespace native {
void set_print_intrinsics() {
// preset the print functions
intrinsics::set_intrinsic<intrinsics::prints_l>([](const char* cs, uint32_t l) {
_prints_l(cs, l, eosio::cdt::output_stream_kind::std_out);
});
intrinsics::set_intrinsic<intrinsics::prints>([](const char* cs) {
_prints(cs, eosio::cdt::output_stream_kind::std_out);
});
intrinsics::set_intrinsic<intrinsics::printi>([](int64_t v) {
printf("%lli", v);
});
intrinsics::set_intrinsic<intrinsics::printui>([](uint64_t v) {
printf("%llu", v);
});
intrinsics::set_intrinsic<intrinsics::printi128>([](const int128_t* v) {
int* tmp = (int*)v;
printf("0x%04x%04x%04x%04x", tmp[0], tmp[1], tmp[2], tmp[3]);
});
intrinsics::set_intrinsic<intrinsics::printui128>([](const uint128_t* v) {
int* tmp = (int*)v;
printf("0x%04x%04x%04x%04x", tmp[0], tmp[1], tmp[2], tmp[3]);
});
intrinsics::set_intrinsic<intrinsics::printsf>([](float v) {
char buff[512] = {0};
std::string ret = std::to_string((int)v);
memcpy(buff, ret.c_str(), ret.size());
v -= (int)v;
buff[ret.size()] = '.';
size_t size = ret.size();
for (size_t i=size+1; i < size+10; i++) {
v *= 10;
buff[i] = ((int)v)+'0';
v -= (int)v;
}
prints(buff);
});
intrinsics::set_intrinsic<intrinsics::printdf>([](double v) {
char buff[512] = {0};
std::string ret = std::to_string((long)v);
memcpy(buff, ret.c_str(), ret.size());
v -= (long)v;
buff[ret.size()] = '.';
size_t size = ret.size();
for (size_t i=size+1; i < size+10; i++) {
v *= 10;
buff[i] = ((int)v)+'0';
v -= (int)v;
}
prints(buff);
});
intrinsics::set_intrinsic<intrinsics::printqf>([](const long double* v) {
int* tmp = (int*)v;
printf("0x%04x%04x%04x%04x", tmp[0], tmp[1], tmp[2], tmp[3]);
});
intrinsics::set_intrinsic<intrinsics::printn>([](uint64_t nm) {
std::string s = eosio::name(nm).to_string();
prints_l(s.c_str(), s.length());
});
intrinsics::set_intrinsic<intrinsics::printhex>([](const void* data, uint32_t len) {
constexpr static uint32_t max_stack_buffer_size = 512;
const char* hex_characters = "0123456789abcdef";
uint32_t buffer_size = 2*len;
if(buffer_size < len) eosio_assert( false, "length passed into printhex is too large" );
void* buffer = (max_stack_buffer_size < buffer_size) ? malloc(buffer_size) : alloca(buffer_size);
char* b = reinterpret_cast<char*>(buffer);
const uint8_t* d = reinterpret_cast<const uint8_t*>(data);
for( uint32_t i = 0; i < len; ++i ) {
*b = hex_characters[d[i] >> 4];
++b;
*b = hex_characters[d[i] & 0x0f];
++b;
}
prints_l(reinterpret_cast<const char*>(buffer), buffer_size);
if(max_stack_buffer_size < buffer_size) free(buffer);
});
}
}} // eosio::native
int32_t blake2_f( uint32_t rounds, const char* state, uint32_t state_len, const char* msg, uint32_t msg_len,
const char* t0_offset, uint32_t t0_len, const char* t1_offset, uint32_t t1_len, int32_t final, char* result, uint32_t result_len) {
return intrinsics::get().call<intrinsics::blake2_f>(rounds, state, state_len, msg, msg_len, t0_offset, t0_len, t1_offset, t1_len, final, result, result_len);
+8
View File
@@ -24,6 +24,10 @@ namespace eosio { namespace cdt {
};
}} //ns eosio::cdt
namespace eosio { namespace native {
void set_print_intrinsics();
}} //eosio::native
extern eosio::cdt::output_stream std_out;
extern eosio::cdt::output_stream std_err;
@@ -39,4 +43,8 @@ extern "C" {
void __reset_env();
void _prints_l(const char* cstr, uint32_t len, uint8_t which);
void _prints(const char* cstr, uint8_t which);
//used only in shared object
void register_intrinsic(int64_t, void(*)());
void initialize();
}
@@ -1,4 +1,3 @@
#include <eosio/action.hpp>
#include "intrinsics_def.hpp"
#pragma once
+10 -5
View File
@@ -1,22 +1,27 @@
project(antler-run)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/antler-run.cpp ${CMAKE_BINARY_DIR}/antler-run.cpp)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.hpp.in ${CMAKE_BINARY_DIR}/version.hpp)
find_package(eos-vm)
find_package(cdt_libraries)
find_package(Boost REQUIRED)
find_package(Protobuf REQUIRED)
set(TOOLS_DIR ${CMAKE_SOURCE_DIR})
set(LIB_DIR ${CMAKE_SOURCE_DIR}/../libraries)
set(CDT_NO_START TRUE)
include_directories(${LIB_DIR})
include_directories(${LIB_DIR} ${CMAKE_BINARY_DIR}/include ${Protobuf_INCLUDE_DIRS})
add_tool(antler-run)
target_compile_options(${PROJECT_NAME} PUBLIC -ldl)
target_compile_options(${PROJECT_NAME} PUBLIC -ldl -fpermissive -ftemplate-backtrace-limit=0)
set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-Wl,-rpath,\"\\$ORIGIN/../lib\"")
target_include_directories(${PROJECT_NAME} PUBLIC ${TOOLS_DIR}/external/eos-vm/include
${TOOLS_DIR}/external/spdlog/include
${LIB_DIR}/eosiolib/contracts
${LIB_DIR}/eosiolib/core
${LIB_DIR}/eosiolib/capi
${LIB_DIR}/eosiolib/native
${LIB_DIR}/native
${LIB_DIR}/meta_refl/include)
target_link_libraries(${PROJECT_NAME} native_eosio native)
${LIB_DIR}/meta_refl/include
)
target_link_libraries(${PROJECT_NAME} native_eosio native antler_interfaces ${Protobuf_LIBRARIES})
+18 -7
View File
@@ -1,5 +1,9 @@
#include "options.hpp"
#include "file-utils.hpp"
#include "run.hpp"
#include "native-runner.hpp"
#include "wasm-runner.hpp"
#include "version.hpp"
#include <cassert>
#include <string.h>
@@ -14,37 +18,44 @@
#include "llvm/Support/CommandLine.h"
using namespace eosio;
using namespace eosio::testing;
using namespace eosio::cdt;
using namespace eosio::native;
using namespace eosio::vm;
using namespace llvm;
int main(int argc, const char **argv) {
cl::SetVersionPrinter([](llvm::raw_ostream& os) {
os << "Antler-run version ${VERSION_FULL}" << "\n";
os << "Antler-run version " << app_version() << "\n";
});
cl::ParseCommandLineOptions(argc, argv);
const auto& contract_path = contract_path_opt.getValue();
auto contract_type = utils::get_file_type(contract_path.c_str());
if (test_so_opt) {
auto contract_type = utils::get_file_type(contract_path.c_str());
if ( contract_type != utils::file_type::elf_shared_object ) {
fprintf(stderr, "not a shared object file: %s\n", file_type_str(contract_type).c_str());
}
// TODO: add check for neccesary exports
//loads shared object and throws if any export function is missing
testing::native::runner runner(contract_path);
ANTLER_INFO("{} is a valid native contract shared library", std::filesystem::path(contract_path).filename());
// TODO: add unit test that generates shared object and executes runner with this flag
return 0;
}
const auto& node_url = nodeos_url_opt.getValue();
const auto& node_port = nodeos_port_opt.getValue();
const auto& action = action_name_opt.getValue();
const auto& account = register_opt.getValue();
const auto& action = eosio::name(action_name_opt.getValue());
const auto& account = eosio::name(register_opt.getValue());
//TODO add runner implementation here
if (contract_type == utils::file_type::elf_shared_object) {
run( testing::native::runner(contract_path), node_url, node_port, account );
} else if (contract_type == utils::file_type::wasm) {
run( testing::wasm::runner(contract_path), node_url, node_port, account );
}
return 0;
}
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include <eosio/eosio.hpp>
#include <type_traits>
#include <string>
#include <cstddef>
#include <variant>
namespace eosio { namespace testing {
enum class object_type {
shared_object,
wasm
};
template <typename Impl>
struct runner_interface {
inline void init() {
return static_cast<Impl*>(this)->init();
}
inline void apply(eosio::name receiver, eosio::name code, eosio::name action) {
return static_cast<Impl*>(this)->apply(receiver, code, action);
}
inline object_type get_type() {
return static_cast<Impl*>(this)->get_type();
}
};
}} // eosio::testing
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#include <native/eosio/intrinsics.hpp>
#include <native/eosio/crt.hpp>
#include "node-client.hpp"
namespace eosio { namespace testing { namespace native {
template<eosio::native::intrinsics::intrinsic_name ID, typename Ret, typename TupleArgs, size_t... Is>
inline auto get_intrinsic_target(std::index_sequence<Is...>) {
return *eosio::native::intrinsics::get_intrinsic<ID>().template target<Ret(*)(typename std::tuple_element<Is, TupleArgs>::type...)>();
}
template<int64_t ID, typename Ret, typename... Args>
inline auto redirect_to_node(Args... args) {
return testing::node_client::get().call_intrinsic<ID, Ret>(args...);
}
template<int64_t ID, typename Ret, typename TupleArgs, size_t... Is>
inline auto create_redirect_function(std::index_sequence<Is...>) {
return std::function<Ret(typename std::tuple_element<Is, TupleArgs>::type ...)>{
&redirect_to_node<ID, Ret, typename std::tuple_element<Is, TupleArgs>::type ...>
};
}
#define RPC_INTRINSIC(ID) \
eosio::native::intrinsics::set_intrinsic<eosio::native::intrinsics::ID>( \
create_redirect_function<eosio::native::intrinsics::ID, \
eosio::native::intrinsics::__ ## ID ## _types::res_t, \
eosio::native::intrinsics::__ ## ID ## _types::deduced_full_ts > \
(eosio::native::intrinsics::__ ## ID ## _types::is));
void setup_rpc_intrinsics() {
INTRINSICS(RPC_INTRINSIC);
//override print intrinsics to be non-rpc
eosio::native::set_print_intrinsics();
}
}}} //eosio::testing::native
+115
View File
@@ -0,0 +1,115 @@
#pragma once
#include "utils.hpp"
#include "file-utils.hpp"
#include "interface.hpp"
#include "intrinsics_setup.hpp"
#include <memory>
#include <dlfcn.h>
namespace eosio { namespace testing { namespace native {
using apply = std::add_pointer_t<void(uint64_t, uint64_t, uint64_t)>;
using generic_intrinsic = std::add_pointer_t<void()>;
using register_intrinsic = std::add_pointer_t<void (int64_t, const generic_intrinsic&)>;
using initialize = std::add_pointer_t<void()>;
#define REGISTER_LIB_INTRINSIC(ID) \
exports.register_intrinsic((int64_t)eosio::native::intrinsics::ID, \
reinterpret_cast<generic_intrinsic>(\
get_intrinsic_target<eosio::native::intrinsics::ID, \
eosio::native::intrinsics::__ ## ID ## _types::res_t, \
eosio::native::intrinsics::__ ## ID ## _types::deduced_full_ts>\
(eosio::native::intrinsics::__ ## ID ## _types::is)));
template <typename Fn>
struct function {
Fn f = nullptr;
inline function& operator = ( void* raw ) {
f = (Fn)raw;
return *this;
}
inline bool operator == (Fn other) {
return f == other;
}
inline bool operator != (Fn other) {
return !operator==(other);
}
template<typename... Args>
inline auto operator ()(Args... args) {
return f(args...);
}
};
/// @brief native runner, loads shared object with contract, performs intrinsics setup and exposes interface to execute apply
struct runner : testing::runner_interface<runner> {
runner(const std::string& path) {
load(path);
}
void apply(eosio::name receiver, eosio::name code, eosio::name action) {
exports.apply(receiver.value, code.value, action.value);
}
inline object_type get_type() {
return object_type::shared_object;
}
void init() {
// this call assigns rpc handlers for every intrinsic
setup_rpc_intrinsics();
// this macro executes exports.register_intrinsic for every intrinsic
// we need this because of shared object has its own native library internal variables
// so this call is to supply current intrinsics pointers to shared object
INTRINSICS(REGISTER_LIB_INTRINSIC);
//let library override neccesary intrinsics
exports.initialize();
}
private:
// if closing handle before main finishes, it gives segmentation fault
// function with destructor attribute will be executed after main finishes, when static objects destroyed
inline static void* so_handle = 0;
static void close_so_handle(void) __attribute__ ((destructor)) {
dlclose(so_handle);
}
struct exports {
native::function<native::apply> apply;
native::function<native::register_intrinsic> register_intrinsic;
native::function<native::initialize> initialize;
};
exports exports;
void load(const std::string& path) {
ANTLER_ASSERT(so_handle == nullptr, "object already loaded");
auto contract_type = utils::get_file_type(path.c_str());
if ( contract_type != utils::file_type::elf_shared_object ) {
ANTLER_THROW("not a shared object file: {}", file_type_str(contract_type));
}
so_handle = dlopen(path.c_str(), RTLD_LOCAL | RTLD_LAZY);
if (so_handle == nullptr) {
ANTLER_THROW("failed to load {} : {}", path, dlerror());
}
exports.apply = dlsym(so_handle, "apply");
if (exports.apply == nullptr) {
ANTLER_THROW("failed to find `apply`: ", dlerror());
}
exports.register_intrinsic = dlsym(so_handle, "register_intrinsic");
if (exports.register_intrinsic == nullptr) {
ANTLER_THROW("failed to find `register_intrinsic`: ", dlerror());
}
exports.initialize = dlsym(so_handle, "initialize");
if (exports.apply == nullptr) {
ANTLER_THROW("failed to find `initialize`: ", dlerror());
}
}
};
}}} // eosio::testing::native
+475
View File
@@ -0,0 +1,475 @@
#pragma once
#include "version.hpp"
#include "utils.hpp"
#include "rpc-utils.hpp"
#include <native/eosio/intrinsics.hpp>
#include <eosio/datastream.hpp>
#include <bluegrass/meta/for_each.hpp>
#include <pb/antler-run.pb.h>
// macro defined in eosiolib collides with enum value in boost::beast
#ifdef TABLE
#pragma push_macro("TABLE")
#undef TABLE
#endif
#include <boost/beast.hpp>
#include <boost/asio.hpp>
#include <chrono>
#include <memory>
#include <utility>
#include <future>
#include <functional>
#include <thread>
#include <unordered_map>
#include <atomic>
#include <mutex>
#include <type_traits>
#include <condition_variable>
#include <optional>
// restoring eosiolib macro
#ifndef TABLE
#pragma pop_macro("TABLE")
#endif
namespace eosio { namespace testing {
class sync_call_traits {
public:
template<typename Callback>
using callback = std::packaged_task<Callback>;
};
/// @brief connection class that connects to nodeos and processes debug execution messages
/// interface is synchroneous, actual reads and writes are done in a separate thread
/// class designed to always listen for incoming messages and write them to read queue
/// every request and response must have same request_id
/// in case server sends response for a different request, class leaves it in the read queue
/// @tparam CallbackTraits
template<typename CallbackTraits = sync_call_traits>
class connection : public std::enable_shared_from_this<connection<CallbackTraits>>{
using io_context = boost::asio::io_context;
using asio_strand = boost::asio::strand<boost::asio::io_context::executor_type>;
using tcp_stream = boost::beast::websocket::stream<boost::beast::tcp_stream>;
using ip_resolver = boost::asio::ip::tcp::resolver;
using error_code = boost::beast::error_code;
using stream_base = boost::beast::websocket::stream_base;
using data_buffer = boost::beast::flat_buffer;
using request_type = boost::beast::websocket::request_type;
using read_queue_type = std::unordered_map<uint64_t, antler_rpc::antler_run_response>;
using on_event = typename CallbackTraits::callback<void()>;
//order is important as fields are using one another in constructor
io_context context;
ip_resolver resolver;
tcp_stream stream;
data_buffer write_buffer;
data_buffer read_buffer;
std::string host;
std::string port;
on_event connected;
std::mutex reading;
std::condition_variable new_response;
std::mutex writing;
read_queue_type read_queue;
std::mutex read_queue_mutex;
public:
using on_connected = on_event;
connection(const std::string& host_, const std::string& port_, on_connected&& connected_)
: resolver(context.get_executor())
, stream(context.get_executor())
, host(host_)
, port(port_)
, connected(std::move(connected_)) {}
template<typename ReqType, typename = std::enable_if_t<std::is_base_of_v<google::protobuf::Message, ReqType>> >
auto sync_request(ReqType&& req) {
ANTLER_ASSERT( !in_context_thread(), "code must be called outside connection thread" );
auto req_id = req.req_id();
boost::asio::post(context.get_executor(),
[r = std::move(req), c = shared_from_this()](){
c->write(r);
});
return get_response(req_id);
}
std::optional<antler_rpc::antler_run_response> wait_for_divert_flow() {
using namespace std::chrono_literals;
ANTLER_ASSERT( !in_context_thread(), "code must be called outside connection thread" );
while (!closing()) {
{
std::lock_guard<decltype(read_queue_mutex)> lock(read_queue_mutex);
for (auto it = read_queue.begin(); it != read_queue.end(); ++it) {
if (it->second.has_divert_flow_result()) {
antler_rpc::antler_run_response ret = std::move(it->second);
read_queue.erase(it);
return std::optional(std::move(ret));
}
}
}
std::unique_lock<decltype(read_queue_mutex)> wait_lock(read_queue_mutex);
new_response.wait_for(wait_lock, 1s, [&](){ return !read_queue.empty(); });
}
// we are here if connection was closed
return {};
}
inline bool closing() {
return stream.is_open();
}
void run() {
// blocking until closed
context.run();
}
void start_connection() {
boost::asio::post(context.get_executor(),
[c = shared_from_this()](){
c->connect();
});
}
private:
template<typename Handler>
inline auto bind_front_this(Handler&& f) {
return boost::beast::bind_front_handler(std::forward<Handler>(f), shared_from_this());
}
void on_resolve(error_code ec, ip_resolver::results_type results){
ANTLER_ASSERT(!ec, "{}", ec.message());
// Set the timeout for the operation
boost::beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(30));
// Make the connection on the IP address we get from a lookup
boost::beast::get_lowest_layer(stream).async_connect(results,
bind_front_this(&connection::on_connect));
}
void on_connect(error_code ec, ip_resolver::results_type::endpoint_type ep){
ANTLER_ASSERT(!ec, "{}", ec.message());
// Turn off the timeout on the tcp_stream, because
// the websocket stream has its own timeout system.
boost::beast::get_lowest_layer(stream).expires_never();
// Set suggested timeout settings for the websocket
stream.set_option(stream_base::timeout::suggested(boost::beast::role_type::client));
// Set a decorator to change the User-Agent of the handshake
stream.set_option(stream_base::decorator(
[](request_type& req)
{
req.set(boost::beast::http::field::user_agent,
eosio::utils::log_to_str("antler-run client {}", app_version()));
}));
// Perform the websocket handshake
stream.async_handshake(eosio::utils::log_to_str("{}:{}", host, ep.port()),
"/",
bind_front_this(&connection::on_handshake));
}
void on_handshake(error_code ec) {
ANTLER_ASSERT(!ec, "{}", ec.message());
start_read();
connected();
}
template <typename ReqType>
void write(const ReqType& req) {
ANTLER_ASSERT( in_context_thread(), "code must be called inside connection thread" );
ANTLER_ASSERT( writing.try_lock(), "current implementation is single-threaded so \
only one write operation is permitted at the same time. \
If you need this functionality you should implement write queue \
or multi-socket connection (new thread = new socket)" );
const auto msg_size = req.ByteSizeLong();
{
zero_copy_output_stream<decltype(write_buffer)> ostream(write_buffer, msg_size);
req.SerializeToZeroCopyStream(&ostream);
}
stream.async_write(write_buffer.data(), bind_front_this(&connection::on_write));
}
void on_write(error_code ec, size_t) {
std::unique_lock<decltype(writing)>(writing, std::adopt_lock);
ANTLER_ASSERT(!ec, "{}", ec.message());
write_buffer.consume(write_buffer.size());
}
void on_read(error_code ec, std::size_t) {
antler_rpc::antler_run_response response;
{
std::unique_lock<decltype(reading)>(reading, std::adopt_lock);
ANTLER_ASSERT(!ec, "{}", ec.message());
zero_copy_input_stream<decltype(read_buffer)> istream(read_buffer);
response.ParseFromZeroCopyStream(&istream);
}
{
std::lock_guard<decltype(read_queue_mutex)> lock(read_queue_mutex);
read_queue[response.req_id()] = std::move(response);
}
new_response.notify_all();
start_read();
}
void start_read() {
ANTLER_ASSERT( in_context_thread(), "code must be called inside connection thread" );
ANTLER_ASSERT( reading.try_lock(), "current implementation is single-threaded so \
only one read operation is permitted at the same time. \
If you need this functionality you should implement read queue \
or multi-socket connection (new thread = new socket)" );
stream.async_read(read_buffer, bind_front_this(&connection::on_read));
}
template<typename ReqType>
auto post_request(ReqType&& req) {
return boost::asio::post(context.get_executor(),
[r = std::move(req), c = shared_from_this()](){
c->write(r);
});
}
void connect() {
ANTLER_ASSERT( !stream.is_open(), "already connected" );
ANTLER_ASSERT( in_context_thread(), "code must be called inside connection thread" );
// Look up the domain name
resolver.async_resolve(
host,
port,
bind_front_this(&connection::on_resolve));
}
inline bool in_context_thread() {
return context.get_executor().running_in_this_thread();
}
std::optional<antler_rpc::antler_run_response> get_response(uint64_t id) {
using namespace std::chrono_literals;
ANTLER_ASSERT( !in_context_thread(), "code must be called outside connection thread" );
while (!closing()) {
{
std::lock_guard<decltype(read_queue_mutex)> lock(read_queue_mutex);
auto it = read_queue.find(id);
if (it != read_queue.end()) {
antler_rpc::antler_run_response ret = std::move(it->second);
read_queue.erase(it);
return std::optional(std::move(ret));
}
}
std::unique_lock<decltype(read_queue_mutex)> wait_lock(read_queue_mutex);
new_response.wait_for(wait_lock, 1s, [&](){ return !read_queue.empty(); });
}
// we are here if connection was closed
return {};
}
};
/// @brief client that exposes debug RPC.
/// it maintains single thread for network operations where connection class is running
class node_client {
private:
using connection = connection<>;
std::shared_ptr<connection> node_connection;
std::thread context_thread;
node_client() {}
static inline uint64_t generate_req_id() {
static uint64_t counter = 0;
return ++counter;
}
void run() {
context_thread = std::thread([&](){
// blocks until connection is closed
node_connection->run();
});
}
public:
void init(const std::string& host, const std::string& port) {
connection::on_connected connected;
auto future = connected.get_future();
node_connection.reset( new connection(host, port, std::move(connected)) );
run();
node_connection->start_connection();
future.get();
}
static inline node_client& get() {
static node_client client;
return client;
}
void register_account(eosio::name acc_name) {
auto request = create_generic_request();
request.mutable_register_message()->set_account(acc_name.value);
//sync_request will check return code, as long as it doesn't throw we good to return
std::ignore = sync_request(std::move(request));
}
void unregister_account(eosio::name acc_name) {
auto request = create_generic_request();
request.mutable_unregister_message()->set_account(acc_name.value);
//sync_request will check return code, as long as it doesn't throw we good to return
std::ignore = sync_request(std::move(request));
}
inline std::optional<antler_rpc::antler_run_response> wait_for_divert_flow() {
return node_connection->wait_for_divert_flow();
}
void set_time(int64_t new_time) {
auto request = create_generic_request();
request.mutable_set_time_message()->set_time(new_time);
//sync_request will check return code, as long as it doesn't throw we good to return
std::ignore = sync_request(std::move(request));
}
int64_t get_time() {
auto request = create_generic_request();
std::ignore = request.mutable_get_time_message();
auto response = sync_request(std::move(request));
ANTLER_ASSERT(response.has_get_time_result(), "get time result should be in place");
return response.get_time_result().time();
}
template<int64_t ID, typename Ret, typename... Args>
Ret call_intrinsic(Args... args) {
auto request = create_generic_request();
request.set_req_id(generate_req_id());
request.mutable_call_intrinsic_message()->set_id(ID);
request.mutable_call_intrinsic_message()->set_data(serialize_args(args...));
//call CallHostFunction RPC synchronously and get response
auto response = sync_request(std::move(request));
ANTLER_ASSERT(response.has_call_intrinsic_result(), "call intrinsic result is absent");
const auto& intrinsic_result = response.call_intrinsic_result();
const auto& output_params = intrinsic_result.data();
if (!output_params.empty()) {
//deserialize response back into args if any
deserialize_output_parameters(output_params.data(), std::make_tuple(std::forward<Args>(args)...));
}
if constexpr (!std::is_same_v<void, Ret>) {
//deserialize intrinsic return value if any
return deserialize_return_value<Ret>(intrinsic_result.ret_val());
}
}
template<typename Ret, typename... Args>
Ret call_action(Args... args) {
auto request = create_generic_request();
request.mutable_call_action_message()->set_data(serialize_args(args...));
//call CallAction RPC synchronously and get response
auto response = sync_request(std::move(request));
ANTLER_ASSERT(response.has_call_action_result(), "call action result is absent");
const auto& action_result = response.call_action_result();
const auto& output_params = action_result.data();
if (!output_params.empty()) {
//deserialize response back into args if any
deserialize_output_parameters(output_params.data(), std::make_tuple(std::forward<Args>(args)...));
}
if constexpr (!std::is_same_v<void, Ret>) {
//deserialize intrinsic return value if any
return deserialize_return_value<Ret>(action_result.ret_val());
}
}
void return_control_flow(uint64_t id) {
antler_rpc::antler_run_request request;
// id should match req_id we got from divert_flow
request.set_req_id(id);
std::ignore = sync_request(std::move(request));
}
private:
template<typename ReqType>
antler_rpc::antler_run_response sync_request(ReqType&& request) {
auto response = node_connection->sync_request(std::move(request));
ANTLER_QUIT(!node_connection->closing(), "connection to node was closed, exiting");
ANTLER_ASSERT(response, "error getting response from node");
ANTLER_ASSERT(response->ret_code() == 0, "request {} call has failed", response->DebugString());
return *response;
}
antler_rpc::antler_run_request create_generic_request() const {
antler_rpc::antler_run_request request;
request.set_req_id(generate_req_id());
return request;
}
template<typename... Args>
std::string serialize_args(Args... args) {
std::string tmp_buf;
size_t buf_size = 0;
if constexpr (std::index_sequence_for<Args...>::size()) {
buf_size = (eosio::pack_size(args) + ...);
tmp_buf.resize(buf_size);
eosio::datastream<const char*> ds(tmp_buf.data(), tmp_buf.size());
//serialize args into vector of bytes
(ds << ... << args);
}
return tmp_buf;
}
template<typename... Args>
void deserialize_output_parameters(const std::string& data, std::tuple<Args...>&& t) {
eosio::datastream<const char*> ds(data.data(), data.size());
bluegrass::meta::for_each(t, [&](auto v){
if constexpr (std::is_pointer_v<decltype(v)> && !std::is_const_v<std::remove_pointer_t<decltype(v)>>) {
//void* and char* are exceptions, everything else is serialized via datastream
// if you'll encounter errors here, update datastream to deserialize type you have issues with
if constexpr (std::is_same_v<void*, decltype(v)> || std::is_same_v<char*, decltype(v)>) {
std::vector<char> tmp;
ds >> tmp;
memcpy((void*)v, tmp.data(), tmp.size());
} else {
ds >> *v;
}
}
});
}
template<typename Ret>
Ret deserialize_return_value(const std::string& serialized_data) {
eosio::datastream<const char*> ds(serialized_data.data(), serialized_data.size());
Ret ret_val;
ds >> ret_val;
return std::move(ret_val);
}
};
}} //eosio::testing
+122
View File
@@ -0,0 +1,122 @@
#pragma once
#include "utils.hpp"
#include <google/protobuf/io/zero_copy_stream.h>
#include <boost/asio/buffer.hpp>
#include <boost/beast/core/buffer_traits.hpp>
namespace eosio { namespace testing {
/**
* idea taken from https://github.com/boostorg/beast/issues/1180
* Implements ZeroCopyOutputStream around a AsioBuf.
* AsioBuf matches the public interface defined by boost::asio::streambuf.
* @tparam AsioBuf A type meeting the requirements of AsioBuf.
*/
template <class AsioBuf>
class zero_copy_output_stream : public google::protobuf::io::ZeroCopyOutputStream {
private:
using mutable_buffers_type = typename AsioBuf::mutable_buffers_type;
using iterator = typename boost::beast::buffers_iterator_type<mutable_buffers_type>;
AsioBuf& streambuf;
std::size_t mutable_buf_size;
google::protobuf::int64 commited = 0;
std::size_t bytes_to_commit = 0;
iterator cur_pos = nullptr;
inline void commit() {
if (bytes_to_commit != 0) {
streambuf.commit(bytes_to_commit);
commited += bytes_to_commit;
bytes_to_commit = 0;
}
}
public:
explicit zero_copy_output_stream(AsioBuf& streambuf, std::size_t blockSize):
streambuf(streambuf)
, mutable_buf_size(blockSize) {}
~zero_copy_output_stream() { commit(); }
bool Next(void** data, int* size) override {
ANTLER_ASSERT(data, "data pointer is null");
ANTLER_ASSERT(size, "{}: size pointer is null");
commit();
auto cur_pos = boost::asio::buffer_sequence_begin(streambuf.prepare(mutable_buf_size));
*data = boost::asio::buffer_cast<void*>(*cur_pos);
*size = static_cast<int>(boost::asio::buffer_size(*cur_pos));
bytes_to_commit = *size;
return true;
}
void BackUp(int count) override {
ANTLER_ASSERT(count <= bytes_to_commit, "count greater than bytes to commit");
bytes_to_commit -= count;
commit();
}
google::protobuf::int64 ByteCount() const override {
return commited;
}
};
template <class AsioBuf>
class zero_copy_input_stream : public google::protobuf::io::ZeroCopyInputStream {
private:
using mutable_buffers_type = typename AsioBuf::mutable_buffers_type;
using iterator = typename boost::beast::buffers_iterator_type<mutable_buffers_type>;
AsioBuf& streambuf;
size_t bytes_to_consume = 0;
google::protobuf::int64 total_consumed = 0;
inline void consume(bool skip = false) {
if (bytes_to_consume) {
streambuf.consume(bytes_to_consume);
if (!skip)
total_consumed += bytes_to_consume;
bytes_to_consume = 0;
}
}
public:
explicit zero_copy_input_stream(AsioBuf& buf) : streambuf(buf) {}
~zero_copy_input_stream() { consume(); }
bool Next(const void** data, int* size) override {
ANTLER_ASSERT(data, "data pointer is null");
ANTLER_ASSERT(size, "{}: size pointer is null");
consume();
auto cur_pos = boost::asio::buffer_sequence_begin(streambuf.data());
*data = boost::asio::buffer_cast<void*>(*cur_pos);
*size = static_cast<int>(boost::asio::buffer_size(*cur_pos));
bytes_to_consume = *size;
if (++cur_pos == boost::asio::buffer_sequence_end(streambuf.data())) {
return false;
}
return true;
}
void BackUp(int count) override {
ANTLER_ASSERT(count <= bytes_to_consume, "count greater than bytes to consume");
bytes_to_consume -= count;
consume();
}
bool Skip(int count) override {
consume();
bytes_to_consume = count;
consume(true);
}
int64_t ByteCount() const override {
return total_consumed;
}
};
}} // eosio::testing
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include "utils.hpp"
#include "options.hpp"
#include "interface.hpp"
#include "node-client.hpp"
#include <filesystem>
namespace eosio { namespace testing {
/// @brief function that perform entire debugging lifecycle
/// @tparam Impl contract execution implementation, currently can be either native or wasm contract runner.
/// passed as r-value as it is not supposed to live outside run function
/// @param runner contract runner
/// @param host nodeos debug plugin host name
/// @param port nodeos debug plugin port number
/// @param register_account_name account for debugging
template <typename Impl>
void run(runner_interface<Impl>&& runner,
const std::string& host = "",
const std::string& port = "",
eosio::name register_account_name = {}) {
runner.init();
// all calls to node_client are synchroneous, if error occurs exception is raised
// init starts separate connection thread and returns after successful handshake with nodeos
node_client::get().init(host, port);
node_client::get().register_account(register_account_name);
// TODO: add signal interruption processing
//if response is empty that means connection was closed, exiting
while (auto response = node_client::get().wait_for_divert_flow()) {
eosio::name receiver( response->divert_flow_result().receiver() );
ANTLER_ASSERT( receiver, "receiver name must be set" );
eosio::name code( response->divert_flow_result().code() );
ANTLER_ASSERT( code, "code name must be set" );
eosio::name action( response->divert_flow_result().action() );
ANTLER_ASSERT( action, "action name must be set" );
runner.apply(receiver, code, action);
// letting nodeos know that action debugging was done
node_client::get().return_control_flow(response->req_id());
}
}
}} //eosio::testing
+80
View File
@@ -0,0 +1,80 @@
#pragma once
#include <string>
#include <sstream>
#include <exception>
#include <filesystem>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/ostream_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/fmt/ostr.h>
namespace eosio { namespace utils {
// log to str and return it
template<typename... Args>
std::string log_to_str(const Args &... args)
{
std::ostringstream oss;
static auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss);
static spdlog::logger oss_logger("ostringstream", oss_sink);
static bool initialized = false;
if (!initialized) {
//this means simply use string itself
oss_logger.set_pattern("%v");
initialized = true;
}
oss_logger.info(args...);
auto ret_val = oss.str();
oss.str("");
return ret_val;
}
std::shared_ptr<spdlog::logger> get_std_logger() {
static auto logger = spdlog::stdout_color_mt("antler logger");
bool initialized = false;
if (!initialized) {
//format is <color>message level</color> message
logger->set_pattern("%^%l%$ %v");
}
return logger;
}
#define ANTLER_ERROR(...) \
SPDLOG_LOGGER_ERROR(eosio::utils::get_std_logger(), __VA_ARGS__)
#define ANTLER_INFO(...) \
SPDLOG_LOGGER_INFO(eosio::utils::get_std_logger(), __VA_ARGS__)
#define ANTLER_MULTILINE_MACRO_BEGIN do {
#define ANTLER_MULTILINE_MACRO_END } while (0)
#define ANTLER_EXCEPTION std::runtime_error
#define ANTLER_THROW(...) \
ANTLER_MULTILINE_MACRO_BEGIN \
throw ANTLER_EXCEPTION( eosio::utils::log_to_str(__VA_ARGS__) ); \
ANTLER_MULTILINE_MACRO_END
#define ANTLER_ASSERT(cond, ...) \
ANTLER_MULTILINE_MACRO_BEGIN \
if (!(cond)) \
ANTLER_THROW("{}: {}", __func__, eosio::utils::log_to_str(__VA_ARGS__)); \
ANTLER_MULTILINE_MACRO_END
#define ANTLER_QUIT(cond, ...) \
ANTLER_MULTILINE_MACRO_BEGIN \
if (!(cond)) \
ANTLER_INFO("{}: {}", __func__, eosio::utils::log_to_str(__VA_ARGS__)); \
ANTLER_MULTILINE_MACRO_END
std::ostream &operator<<(std::ostream &ost, const std::filesystem::path& p)
{
std::string tmp = fmt::format("{}", p.native());
ost << tmp;
return ost;
}
}} // eosio::utils
+5
View File
@@ -0,0 +1,5 @@
#pragma once
inline const char* app_version() {
return "${VERSION_FULL}";
}
+108
View File
@@ -0,0 +1,108 @@
#pragma once
#include "utils.hpp"
#include "file-utils.hpp"
#include "interface.hpp"
#include "intrinsics_setup.hpp"
#include <eosio/action.h>
#include <eosio/chain.h>
#include <eosio/crypto.h>
#include <eosio/db.h>
#include <eosio/permission.h>
#include <eosio/print.h>
#include <eosio/privileged.h>
#include <eosio/system.h>
#include <eosio/transaction.h>
#include <eosio/vm/host_function.hpp>
#include <eosio/vm/backend.hpp>
#include <fstream>
#include <memory>
namespace eosio { namespace testing { namespace wasm {
struct intrinsics_type_converter : eosio::vm::type_converter<eosio::vm::standalone_function_t> {
using type_converter::type_converter;
using type_converter::from_wasm;
EOS_VM_FROM_WASM(int*, (void* ptr)) { return static_cast<int*>(ptr); }
EOS_VM_FROM_WASM(long int*, (void* ptr)) { return static_cast<long int*>(ptr); }
EOS_VM_FROM_WASM(long unsigned int*, (void* ptr)) { return static_cast<long unsigned int*>(ptr); }
EOS_VM_FROM_WASM(const long unsigned int*, (void* ptr)) { return static_cast<long unsigned int*>(ptr); }
EOS_VM_FROM_WASM(const __int128 unsigned*, (void* ptr)) { return static_cast<__int128 unsigned*>(ptr); }
EOS_VM_FROM_WASM(__int128 unsigned*, (void* ptr)) { return static_cast<__int128 unsigned*>(ptr); }
EOS_VM_FROM_WASM(const __int128*, (void* ptr)) { return static_cast<__int128*>(ptr); }
EOS_VM_FROM_WASM(__int128*, (void* ptr)) { return static_cast<__int128*>(ptr); }
EOS_VM_FROM_WASM(const double*, (void* ptr)) { return static_cast<double*>(ptr); }
EOS_VM_FROM_WASM(double*, (void* ptr)) { return static_cast<double*>(ptr); }
EOS_VM_FROM_WASM(const long double*, (void* ptr)) { return static_cast<long double*>(ptr); }
EOS_VM_FROM_WASM(long double*, (void* ptr)) { return static_cast<long double*>(ptr); }
EOS_VM_FROM_WASM(const capi_checksum512*, (void* ptr)) { return static_cast<capi_checksum512*>(ptr); }
EOS_VM_FROM_WASM(capi_checksum512*, (void* ptr)) { return static_cast<capi_checksum512*>(ptr); }
EOS_VM_FROM_WASM(const capi_checksum256*, (void* ptr)) { return static_cast<capi_checksum256*>(ptr); }
EOS_VM_FROM_WASM(capi_checksum256*, (void* ptr)) { return static_cast<capi_checksum256*>(ptr); }
EOS_VM_FROM_WASM(const capi_checksum160*, (void* ptr)) { return static_cast<capi_checksum160*>(ptr); }
EOS_VM_FROM_WASM(capi_checksum160*, (void* ptr)) { return static_cast<capi_checksum160*>(ptr); }
EOS_VM_FROM_WASM(bool, (uint32_t value)) { return value ? 1 : 0; }
EOS_VM_FROM_WASM(char*, (void* ptr)) { return static_cast<char*>(ptr); }
EOS_VM_FROM_WASM(const char*, (void* ptr)) { return static_cast<char*>(ptr); }
};
/// @brief wasm runner, loads wasm object, performs intrinsics setup and exposes interface to execute apply
struct runner : testing::runner_interface<runner> {
using rhf_t = eosio::vm::registered_host_functions<eosio::vm::standalone_function_t, eosio::vm::execution_interface, intrinsics_type_converter>;
using backend_t = eosio::vm::backend<rhf_t, eosio::vm::interpreter>;
#define REGISTER_WASM_INTRINSIC(ID) \
rhf_t::add<&::ID>("host", #ID);
runner(const std::string& path) {
load(path);
}
void apply(eosio::name receiver, eosio::name code, eosio::name action) {
backend->call("env", "apply", receiver.value, code.value, action.value);
}
inline object_type get_type() {
return object_type::wasm;
}
void init() {
eosio::testing::native::setup_rpc_intrinsics();
INTRINSICS(REGISTER_WASM_INTRINSIC);
}
private:
eosio::vm::wasm_allocator wasm_allocator;
std::unique_ptr<backend_t> backend;
void load(const std::string& path) {
auto contract_type = utils::get_file_type(path.c_str());
if ( contract_type != utils::file_type::wasm ) {
ANTLER_THROW("not a wasm file: {}", file_type_str(contract_type));
}
auto wasm_data = read_wasm(path);
backend.reset( new backend_t(wasm_data, &wasm_allocator) );
}
std::vector<uint8_t> read_wasm( const std::string& fname ) {
std::ifstream wasm_file(fname, std::ios::binary);
ANTLER_ASSERT( wasm_file.is_open(), "error opening file: {}", fname );
wasm_file.seekg(0, std::ios::end);
int len = wasm_file.tellg();
ANTLER_ASSERT( len, "wasm file is empty", fname );
ANTLER_ASSERT( len != -1, "file error: {}", wasm_file.rdstate() );
std::vector<uint8_t> wasm;
wasm.resize(len);
wasm_file.seekg(0, std::ios::beg);
wasm_file.read((char*)wasm.data(), wasm.size());
wasm_file.close();
return std::move(wasm);
}
};
}}} // eosio::testing::wasm
+2 -1
View File
@@ -1,2 +1,3 @@
add_subdirectory(wabt)
add_subdirectory(eos-vm)
add_subdirectory(eos-vm)
add_subdirectory(antler-interfaces)