diff --git a/CMakeLists.txt b/CMakeLists.txt index 90c0e89..4cddc95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ endif() set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_EXTENSIONS ON) if(NOT DEFINED SKIP_SUBMODULE_CHECK) execute_process(COMMAND git submodule status --recursive @@ -31,24 +31,30 @@ Please run the command 'git submodule update --init --recursive'.") endif() endif() -add_library(abieos MODULE src/abieos.cpp src/abi.cpp src/crypto.cpp include/eosio/fpconv.c) -target_include_directories(abieos PRIVATE include external/outcome/single-header external/rapidjson/include external/date/include) +add_library(abieos STATIC src/abi.cpp src/crypto.cpp include/eosio/fpconv.c) +target_include_directories(abieos PUBLIC include external/outcome/single-header external/rapidjson/include external/date/include) +set_target_properties(abieos PROPERTIES POSITION_INDEPENDENT_CODE ON) -add_library(abieos_headers INTERFACE) -target_include_directories(abieos_headers INTERFACE include external/outcome/single-header external/date/include external/rapidjson/include) +add_library(abieos_module MODULE src/abieos.cpp) +target_link_libraries(abieos_module abieos) +set_target_properties(abieos_module PROPERTIES OUTPUT_NAME "abieos") -add_executable(test src/test.cpp src/abieos.cpp src/abi.cpp src/crypto.cpp include/eosio/fpconv.c) -target_include_directories(test PRIVATE include external/outcome/single-header external/rapidjson/include external/date/include) +add_executable(test src/test.cpp src/abieos.cpp) +target_link_libraries(test abieos) -add_executable(template_test src/template_test.cpp src/abieos.cpp src/abi.cpp src/crypto.cpp include/eosio/fpconv.c) -target_include_directories(template_test PRIVATE include external/outcome/single-header external/rapidjson/include external/date/include) +add_executable(template_test src/template_test.cpp src/abieos.cpp) +target_link_libraries(template_test abieos) -add_executable(key_test src/key_test.cpp src/abieos.cpp src/abi.cpp src/crypto.cpp include/eosio/fpconv.c) -target_include_directories(key_test PRIVATE include external/outcome/single-header external/rapidjson/include external/date/include) +add_executable(key_test src/key_test.cpp src/abieos.cpp) +target_link_libraries(key_test abieos) + +add_executable(reflect_test src/reflect_test.cpp) +target_include_directories(reflect_test PRIVATE include) add_executable(test-sanitize src/test.cpp src/abieos.cpp src/abi.cpp src/crypto.cpp include/eosio/fpconv.c) + target_include_directories(test-sanitize PRIVATE include external/outcome/single-header external/rapidjson/include external/date/include) -target_link_libraries(test-sanitize -fno-omit-frame-pointer -fsanitize=address,undefined) +target_link_libraries(test-sanitize abieos -fno-omit-frame-pointer -fsanitize=address,undefined) target_compile_options(test-sanitize PUBLIC -fno-omit-frame-pointer -fsanitize=address,undefined) # add_executable(fuzzer src/fuzzer.cpp src/abieos.cpp) diff --git a/include/eosio/abi.hpp b/include/eosio/abi.hpp index c8b8301..744b2a3 100644 --- a/include/eosio/abi.hpp +++ b/include/eosio/abi.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include diff --git a/include/eosio/asset.hpp b/include/eosio/asset.hpp index 52a610e..6c809f7 100644 --- a/include/eosio/asset.hpp +++ b/include/eosio/asset.hpp @@ -32,7 +32,7 @@ struct asset { /** * The symbol name of the asset */ - symbol symbol; + eosio::symbol symbol; /** * Maximum amount possible for this asset. It's capped to 2^62 - 1 diff --git a/include/eosio/crypto.hpp b/include/eosio/crypto.hpp index 8b143be..a3d471d 100644 --- a/include/eosio/crypto.hpp +++ b/include/eosio/crypto.hpp @@ -152,4 +152,7 @@ result from_json(signature& obj, S& stream) { obj = std::move(result); return outcome::success(); } + + std::string to_base58(const char* d, size_t s ); + } // namespace eosio diff --git a/include/eosio/eosio_outcome.hpp b/include/eosio/eosio_outcome.hpp index 1d5e8b3..71c0643 100644 --- a/include/eosio/eosio_outcome.hpp +++ b/include/eosio/eosio_outcome.hpp @@ -1,6 +1,6 @@ #pragma once -#if defined(EOSIO_NATIVE) || defined(EOSIO_CDT_COMPILATION) +#if defined(__eosio_cdt__) # define OUTCOME_DISABLE_EXECINFO # include #else @@ -16,7 +16,7 @@ namespace outcome = OUTCOME_V2_NAMESPACE; template using result = outcome::basic_result; -#if defined(EOSIO_NATIVE) || defined(EOSIO_CDT_COMPILATION) +#if defined(__eosio_cdt__) [[noreturn]] inline void check(std::error_code ec) { check(false, ec.message()); __builtin_unreachable(); diff --git a/include/eosio/fixed_bytes.hpp b/include/eosio/fixed_bytes.hpp index 97c0c5a..59ea6c6 100644 --- a/include/eosio/fixed_bytes.hpp +++ b/include/eosio/fixed_bytes.hpp @@ -195,13 +195,14 @@ class fixed_bytes { // Everything else should be using one of the typedefs below. template void eosio_for_each_field(fixed_bytes*, F&& f) { - f("value", [](auto* p) -> decltype((p->value)) { return p->value; }); + f("value", + [](auto* p) -> decltype(&std::decay_t::value) { return &std::decay_t::value; }); } template EOSIO_COMPARE(fixed_bytes); -using checksum160 = fixed_bytes<20>; +using checksum160 = fixed_bytes<20,uint32_t>; using checksum256 = fixed_bytes<32>; using checksum512 = fixed_bytes<64>; diff --git a/include/eosio/float.hpp b/include/eosio/float.hpp index ee4851e..0362b15 100644 --- a/include/eosio/float.hpp +++ b/include/eosio/float.hpp @@ -1,6 +1,6 @@ #pragma once -#ifdef EOSIO_CDT_COMPILATION +#ifdef __eosio_cdt__ namespace eosio { diff --git a/include/eosio/for_each_field.hpp b/include/eosio/for_each_field.hpp index c44e506..f518a9e 100644 --- a/include/eosio/for_each_field.hpp +++ b/include/eosio/for_each_field.hpp @@ -20,12 +20,30 @@ namespace eosio { template constexpr auto for_each_field(T&& t, F&& f) -> std::enable_if_t>> { - eosio_for_each_field((std::decay_t*)nullptr, [&](const char*, auto member) { f(member(&t)); }); + eosio_for_each_field((std::decay_t*)nullptr, [&](const char*, auto member) { + if constexpr (std::is_member_object_pointer_v) { + f(t.*member(&t)); + } + }); } template constexpr void for_each_field(F&& f) { - eosio_for_each_field((T*)nullptr, f); + eosio_for_each_field((T*)nullptr, [&f](const char* name, auto member) { + if constexpr (std::is_member_object_pointer_v) { + f(name, [member](auto p) -> decltype((p->*member(p))) { return p->*member(p); }); + } + }); +} + +// Calls f(#fn_name, &T::fn_name) for every reflected member function of T. +template +constexpr void for_each_method(F&& f) { + eosio_for_each_field((T*)nullptr, [&f](const char* name, auto member) { + if constexpr (std::is_member_function_pointer_v) { + f(name, member((T*)nullptr)); + } + }); } } // namespace eosio diff --git a/include/eosio/from_bin.hpp b/include/eosio/from_bin.hpp index 92f9842..e141c8d 100644 --- a/include/eosio/from_bin.hpp +++ b/include/eosio/from_bin.hpp @@ -307,10 +307,8 @@ result from_bin(T& obj, S& stream) { template result from_bin(S& stream) { T obj; - auto r = from_bin(obj, stream); - if (!r) - return r.error(); - return obj; + OUTCOME_TRY( from_bin(obj, stream) ); + return outcome::success(obj); } template @@ -322,10 +320,8 @@ result convert_from_bin(T& obj, const std::vector& bin) { template result convert_from_bin(const std::vector& bin) { T obj; - auto r = convert_from_bin(obj, bin); - if (!r) - return r.error(); - return obj; + OUTCOME_TRY( convert_from_bin(obj, bin) ); + return outcome::success(obj); } } // namespace eosio diff --git a/include/eosio/from_json.hpp b/include/eosio/from_json.hpp index 842cb3f..83e7e20 100644 --- a/include/eosio/from_json.hpp +++ b/include/eosio/from_json.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include diff --git a/include/eosio/reflection.hpp b/include/eosio/reflection.hpp index f081e9b..20678ba 100644 --- a/include/eosio/reflection.hpp +++ b/include/eosio/reflection.hpp @@ -14,7 +14,7 @@ namespace eosio { namespace reflection { }; template - static char test(decltype(eosio_for_each_field((C*)nullptr, F{}))*); + static char test(decltype(eosio_for_each_field((C*)nullptr, std::declval()))*); template static long test(...); @@ -26,13 +26,13 @@ namespace eosio { namespace reflection { template inline constexpr bool has_for_each_field_v = has_for_each_field::value; -#define EOSIO_REFLECT_MEMBER(STRUCT, FIELD) f(#FIELD, [](auto p) -> decltype((p->FIELD)) { return (p->FIELD); }); +#define EOSIO_REFLECT_MEMBER(STRUCT, FIELD) \ + f(#FIELD, [](auto p) -> decltype(&std::decay_t::FIELD) { return &std::decay_t::FIELD; }); #define EOSIO_REFLECT_STRIP_BASEbase #define EOSIO_REFLECT_BASE(STRUCT, BASE) \ - static_assert(std::is_base_of_v, \ - #BASE " is not a base class of " #STRUCT); \ - eosio_for_each_field((EOSIO_REFLECT_STRIP_BASE ## BASE*)nullptr, f); + static_assert(std::is_base_of_v, #BASE " is not a base class of " #STRUCT); \ + eosio_for_each_field((EOSIO_REFLECT_STRIP_BASE##BASE*)nullptr, f); #define EOSIO_REFLECT_SIGNATURE(STRUCT, ...) \ [[maybe_unused]] inline const char* get_type_name(STRUCT*) { return #STRUCT; } \ @@ -45,17 +45,17 @@ namespace eosio { namespace reflection { * an identifier which names a non-static data member of the struct. */ #define EOSIO_REFLECT(...) \ - EOSIO_REFLECT_SIGNATURE(__VA_ARGS__) { \ - EOSIO_MAP_REUSE_ARG0(EOSIO_REFLECT_INTERNAL, __VA_ARGS__) \ - } + EOSIO_REFLECT_SIGNATURE(__VA_ARGS__) { EOSIO_MAP_REUSE_ARG0(EOSIO_REFLECT_INTERNAL, __VA_ARGS__) } // Identity the keyword 'base' followed by at least one token -#define EOSIO_REFLECT_SELECT_I(a, b, c, d, ...) EOSIO_REFLECT_ ## d -#define EOSIO_REFLECT_IS_BASE() ~,~ -#define EOSIO_REFLECT_IS_BASE_TESTbase ~,EOSIO_REFLECT_IS_BASE +#define EOSIO_REFLECT_SELECT_I(a, b, c, d, ...) EOSIO_REFLECT_##d +#define EOSIO_REFLECT_IS_BASE() ~, ~ +#define EOSIO_REFLECT_IS_BASE_TESTbase ~, EOSIO_REFLECT_IS_BASE #define EOSIO_APPLY(m, x) m x -#define EOSIO_CAT(x, y) x ## y -#define EOSIO_REFLECT_INTERNAL(STRUCT, FIELD) EOSIO_APPLY(EOSIO_REFLECT_SELECT_I, (EOSIO_CAT(EOSIO_REFLECT_IS_BASE_TEST, FIELD()), MEMBER, BASE, MEMBER))(STRUCT, FIELD) +#define EOSIO_CAT(x, y) x##y +#define EOSIO_REFLECT_INTERNAL(STRUCT, FIELD) \ + EOSIO_APPLY(EOSIO_REFLECT_SELECT_I, (EOSIO_CAT(EOSIO_REFLECT_IS_BASE_TEST, FIELD()), MEMBER, BASE, MEMBER)) \ + (STRUCT, FIELD) }} // namespace eosio::reflection diff --git a/include/eosio/ship_protocol.hpp b/include/eosio/ship_protocol.hpp new file mode 100644 index 0000000..b4875b0 --- /dev/null +++ b/include/eosio/ship_protocol.hpp @@ -0,0 +1,761 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// todo: move +namespace eosio { +template +result to_json(const input_stream& data, S& stream) { + return to_json_hex(data.pos, data.end - data.pos, stream); +} +} // namespace eosio + +namespace eosio { namespace ship_protocol { + + typedef __uint128_t uint128_t; + +#ifdef __eosio_cdt__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Winvalid-noreturn" + [[noreturn]] inline void report_error(const std::string& s) { eosio::check(false, s); } +# pragma clang diagnostic pop +#else + [[noreturn]] inline void report_error(const std::string& s) { throw std::runtime_error(s); } +#endif + + struct extension { + uint16_t type = {}; + eosio::input_stream data = {}; + }; + + EOSIO_REFLECT(extension, type, data) + + enum class transaction_status : uint8_t { + executed = 0, // succeed, no error handler executed + soft_fail = 1, // objectively failed (not executed), error handler executed + hard_fail = 2, // objectively failed and error handler objectively failed thus no state change + delayed = 3, // transaction delayed/deferred/scheduled for future execution + expired = 4, // transaction expired and storage space refunded to user + }; + + // todo: switch to eosio::result. switch to new serializer string support. + inline std::string to_string(transaction_status status) { + switch (status) { + case transaction_status::executed: return "executed"; + case transaction_status::soft_fail: return "soft_fail"; + case transaction_status::hard_fail: return "hard_fail"; + case transaction_status::delayed: return "delayed"; + case transaction_status::expired: return "expired"; + } + report_error("unknown status: " + std::to_string((uint8_t)status)); + } + + // todo: switch to eosio::result. switch to new serializer string support. + inline transaction_status get_transaction_status(const std::string& s) { + if (s == "executed") + return transaction_status::executed; + if (s == "soft_fail") + return transaction_status::soft_fail; + if (s == "hard_fail") + return transaction_status::hard_fail; + if (s == "delayed") + return transaction_status::delayed; + if (s == "expired") + return transaction_status::expired; + report_error("unknown status: " + s); + } + + template + eosio::result to_json(const transaction_status& status, S& stream) { + // todo: switch to new serializer string support. + return eosio::to_json(to_string(status), stream); + } + + struct get_status_request_v0 {}; + + EOSIO_REFLECT(get_status_request_v0) + + struct block_position { + uint32_t block_num = {}; + eosio::checksum256 block_id = {}; + }; + + EOSIO_REFLECT(block_position, block_num, block_id) + + struct get_status_result_v0 { + block_position head = {}; + block_position last_irreversible = {}; + uint32_t trace_begin_block = {}; + uint32_t trace_end_block = {}; + uint32_t chain_state_begin_block = {}; + uint32_t chain_state_end_block = {}; + eosio::checksum256 chain_id = {}; // todo: switch to binary extension + }; + + EOSIO_REFLECT(get_status_result_v0, head, last_irreversible, trace_begin_block, trace_end_block, + chain_state_begin_block, chain_state_end_block, chain_id) + + struct get_blocks_request_v0 { + uint32_t start_block_num = {}; + uint32_t end_block_num = {}; + uint32_t max_messages_in_flight = {}; + std::vector have_positions = {}; + bool irreversible_only = {}; + bool fetch_block = {}; + bool fetch_traces = {}; + bool fetch_deltas = {}; + }; + + EOSIO_REFLECT(get_blocks_request_v0, start_block_num, end_block_num, max_messages_in_flight, have_positions, + irreversible_only, fetch_block, fetch_traces, fetch_deltas) + + struct get_blocks_ack_request_v0 { + uint32_t num_messages = {}; + }; + + EOSIO_REFLECT(get_blocks_ack_request_v0, num_messages) + + using request = std::variant; + + struct get_blocks_result_v0 { + block_position head = {}; + block_position last_irreversible = {}; + std::optional this_block = {}; + std::optional prev_block = {}; + std::optional block = {}; + std::optional traces = {}; + std::optional deltas = {}; + }; + + EOSIO_REFLECT(get_blocks_result_v0, head, last_irreversible, this_block, prev_block, block, traces, deltas) + + using result = std::variant; + + struct row { + bool present = {}; + eosio::input_stream data = {}; + }; + + EOSIO_REFLECT(row, present, data) + + struct table_delta_v0 { + std::string name = {}; + std::vector rows = {}; + }; + + EOSIO_REFLECT(table_delta_v0, name, rows) + + using table_delta = std::variant; + + struct permission_level { + eosio::name actor = {}; + eosio::name permission = {}; + }; + + EOSIO_REFLECT(permission_level, actor, permission) + + struct action { + eosio::name account = {}; + eosio::name name = {}; + std::vector authorization = {}; + eosio::input_stream data = {}; + }; + + EOSIO_REFLECT(action, account, name, authorization, data) + + struct account_auth_sequence { + eosio::name account = {}; + uint64_t sequence = {}; + }; + + EOSIO_REFLECT(account_auth_sequence, account, sequence) + + struct action_receipt_v0 { + eosio::name receiver = {}; + eosio::checksum256 act_digest = {}; + uint64_t global_sequence = {}; + uint64_t recv_sequence = {}; + std::vector auth_sequence = {}; + eosio::varuint32 code_sequence = {}; + eosio::varuint32 abi_sequence = {}; + }; + + EOSIO_REFLECT(action_receipt_v0, receiver, act_digest, global_sequence, recv_sequence, auth_sequence, code_sequence, + abi_sequence) + + using action_receipt = std::variant; + + struct account_delta { + eosio::name account = {}; + int64_t delta = {}; + }; + + EOSIO_REFLECT(account_delta, account, delta) + + struct action_trace_v0 { + eosio::varuint32 action_ordinal = {}; + eosio::varuint32 creator_action_ordinal = {}; + std::optional receipt = {}; + eosio::name receiver = {}; + action act = {}; + bool context_free = {}; + int64_t elapsed = {}; + std::string console = {}; + std::vector account_ram_deltas = {}; + std::optional except = {}; + std::optional error_code = {}; + }; + + EOSIO_REFLECT(action_trace_v0, action_ordinal, creator_action_ordinal, receipt, receiver, act, context_free, elapsed, + console, account_ram_deltas, except, error_code) + + struct action_trace_v1 { + eosio::varuint32 action_ordinal = {}; + eosio::varuint32 creator_action_ordinal = {}; + std::optional receipt = {}; + eosio::name receiver = {}; + action act = {}; + bool context_free = {}; + int64_t elapsed = {}; + std::string console = {}; + std::vector account_ram_deltas = {}; + std::vector account_disk_deltas = {}; + std::optional except = {}; + std::optional error_code = {}; + eosio::input_stream return_value = {}; + }; + + EOSIO_REFLECT(action_trace_v1, action_ordinal, creator_action_ordinal, receipt, receiver, act, context_free, elapsed, + console, account_ram_deltas, account_disk_deltas, except, error_code, return_value) + + using action_trace = std::variant; + + struct partial_transaction_v0 { + eosio::time_point_sec expiration = {}; + uint16_t ref_block_num = {}; + uint32_t ref_block_prefix = {}; + eosio::varuint32 max_net_usage_words = {}; + uint8_t max_cpu_usage_ms = {}; + eosio::varuint32 delay_sec = {}; + std::vector transaction_extensions = {}; + std::vector signatures = {}; + std::vector context_free_data = {}; + }; + + EOSIO_REFLECT(partial_transaction_v0, expiration, ref_block_num, ref_block_prefix, max_net_usage_words, + max_cpu_usage_ms, delay_sec, transaction_extensions, signatures, context_free_data) + + using partial_transaction = std::variant; + + struct recurse_transaction_trace; + + struct transaction_trace_v0 { + eosio::checksum256 id = {}; + transaction_status status = {}; + uint32_t cpu_usage_us = {}; + eosio::varuint32 net_usage_words = {}; + int64_t elapsed = {}; + uint64_t net_usage = {}; + bool scheduled = {}; + std::vector action_traces = {}; + std::optional account_ram_delta = {}; + std::optional except = {}; + std::optional error_code = {}; + std::vector failed_dtrx_trace = {}; + std::optional partial = {}; + }; + + EOSIO_REFLECT(transaction_trace_v0, id, status, cpu_usage_us, net_usage_words, elapsed, net_usage, scheduled, + action_traces, account_ram_delta, except, error_code, failed_dtrx_trace, partial) + + using transaction_trace = std::variant; + + struct recurse_transaction_trace { + transaction_trace recurse = {}; + }; + + template + eosio::result to_bin(const recurse_transaction_trace& obj, S& stream) { + return to_bin(obj.recurse, stream); + } + + template + eosio::result from_bin(recurse_transaction_trace& obj, S& stream) { + return from_bin(obj.recurse, stream); + } + + template + eosio::result to_json(const recurse_transaction_trace& obj, S& stream) { + return to_json(obj.recurse, stream); + } + + struct producer_key { + eosio::name producer_name = {}; + eosio::public_key block_signing_key = {}; + }; + + EOSIO_REFLECT(producer_key, producer_name, block_signing_key) + + struct producer_schedule { + uint32_t version = {}; + std::vector producers = {}; + }; + + EOSIO_REFLECT(producer_schedule, version, producers) + + struct transaction_receipt_header { + transaction_status status = {}; + uint32_t cpu_usage_us = {}; + eosio::varuint32 net_usage_words = {}; + }; + + EOSIO_REFLECT(transaction_receipt_header, status, cpu_usage_us, net_usage_words) + + struct packed_transaction { + std::vector signatures = {}; + uint8_t compression = {}; + eosio::input_stream packed_context_free_data = {}; + eosio::input_stream packed_trx = {}; + }; + + EOSIO_REFLECT(packed_transaction, signatures, compression, packed_context_free_data, packed_trx) + + using transaction_variant = std::variant; + + struct transaction_receipt : transaction_receipt_header { + transaction_variant trx = {}; + }; + + EOSIO_REFLECT(transaction_receipt, base transaction_receipt_header, trx) + + struct block_header { + eosio::block_timestamp timestamp{}; + eosio::name producer = {}; + uint16_t confirmed = {}; + eosio::checksum256 previous = {}; + eosio::checksum256 transaction_mroot = {}; + eosio::checksum256 action_mroot = {}; + uint32_t schedule_version = {}; + std::optional new_producers = {}; + std::vector header_extensions = {}; + }; + + EOSIO_REFLECT(block_header, timestamp, producer, confirmed, previous, transaction_mroot, action_mroot, + schedule_version, new_producers, header_extensions) + + struct signed_block_header : block_header { + eosio::signature producer_signature = {}; + }; + + EOSIO_REFLECT(signed_block_header, base block_header, producer_signature) + + struct signed_block : signed_block_header { + std::vector transactions = {}; + std::vector block_extensions = {}; + }; + + EOSIO_REFLECT(signed_block, base signed_block_header, transactions, block_extensions) + + struct transaction_header { + eosio::time_point_sec expiration = {}; + uint16_t ref_block_num = {}; + uint32_t ref_block_prefix = {}; + eosio::varuint32 max_net_usage_words = {}; + uint8_t max_cpu_usage_ms = {}; + eosio::varuint32 delay_sec = {}; + }; + + EOSIO_REFLECT(transaction_header, expiration, ref_block_num, ref_block_prefix, max_net_usage_words, max_cpu_usage_ms, + delay_sec) + + struct transaction : transaction_header { + std::vector context_free_actions = {}; + std::vector actions = {}; + std::vector transaction_extensions = {}; + }; + + EOSIO_REFLECT(transaction, base transaction_header, context_free_actions, actions, transaction_extensions) + + struct code_id { + uint8_t vm_type = {}; + uint8_t vm_version = {}; + eosio::checksum256 code_hash = {}; + }; + + EOSIO_REFLECT(code_id, vm_type, vm_version, code_hash) + + struct account_v0 { + eosio::name name = {}; + eosio::block_timestamp creation_date = {}; + eosio::input_stream abi = {}; + }; + + EOSIO_REFLECT(account_v0, name, creation_date, abi) + + using account = std::variant; + + struct account_metadata_v0 { + eosio::name name = {}; + bool privileged = {}; + eosio::time_point last_code_update = {}; + std::optional code = {}; + }; + + EOSIO_REFLECT(account_metadata_v0, name, privileged, last_code_update, code) + + using account_metadata = std::variant; + + struct code_v0 { + uint8_t vm_type = {}; + uint8_t vm_version = {}; + eosio::checksum256 code_hash = {}; + eosio::input_stream code = {}; + }; + + EOSIO_REFLECT(code_v0, vm_type, vm_version, code_hash, code) + + using code = std::variant; + + struct contract_table_v0 { + eosio::name code = {}; + eosio::name scope = {}; + eosio::name table = {}; + eosio::name payer = {}; + }; + + EOSIO_REFLECT(contract_table_v0, code, scope, table, payer) + + using contract_table = std::variant; + + struct contract_row_v0 { + eosio::name code = {}; + eosio::name scope = {}; + eosio::name table = {}; + uint64_t primary_key = {}; + eosio::name payer = {}; + eosio::input_stream value = {}; + }; + + EOSIO_REFLECT(contract_row_v0, code, scope, table, primary_key, payer, value) + + using contract_row = std::variant; + + struct contract_index64_v0 { + eosio::name code = {}; + eosio::name scope = {}; + eosio::name table = {}; + uint64_t primary_key = {}; + eosio::name payer = {}; + uint64_t secondary_key = {}; + }; + + EOSIO_REFLECT(contract_index64_v0, code, scope, table, primary_key, payer, secondary_key) + + using contract_index64 = std::variant; + + struct contract_index128_v0 { + eosio::name code = {}; + eosio::name scope = {}; + eosio::name table = {}; + uint64_t primary_key = {}; + eosio::name payer = {}; + uint128_t secondary_key = {}; + }; + + EOSIO_REFLECT(contract_index128_v0, code, scope, table, primary_key, payer, secondary_key) + + using contract_index128 = std::variant; + + struct contract_index256_v0 { + eosio::name code = {}; + eosio::name scope = {}; + eosio::name table = {}; + uint64_t primary_key = {}; + eosio::name payer = {}; + eosio::checksum256 secondary_key = {}; + }; + + EOSIO_REFLECT(contract_index256_v0, code, scope, table, primary_key, payer, secondary_key) + + using contract_index256 = std::variant; + + struct contract_index_double_v0 { + eosio::name code = {}; + eosio::name scope = {}; + eosio::name table = {}; + uint64_t primary_key = {}; + eosio::name payer = {}; + double secondary_key = {}; + }; + + EOSIO_REFLECT(contract_index_double_v0, code, scope, table, primary_key, payer, secondary_key) + + using contract_index_double = std::variant; + + struct contract_index_long_double_v0 { + eosio::name code = {}; + eosio::name scope = {}; + eosio::name table = {}; + uint64_t primary_key = {}; + eosio::name payer = {}; + eosio::float128 secondary_key = {}; + }; + + EOSIO_REFLECT(contract_index_long_double_v0, code, scope, table, primary_key, payer, secondary_key) + + using contract_index_long_double = std::variant; + + struct key_value_v0 { + eosio::name database = {}; + eosio::name contract = {}; + eosio::input_stream key = {}; + eosio::input_stream value = {}; + }; + + EOSIO_REFLECT(key_value_v0, database, contract, key, value) + + using key_value = std::variant; + + struct key_weight { + eosio::public_key key = {}; + uint16_t weight = {}; + }; + + EOSIO_REFLECT(key_weight, key, weight) + + struct block_signing_authority_v0 { + uint32_t threshold = {}; + std::vector keys = {}; + }; + + EOSIO_REFLECT(block_signing_authority_v0, threshold, keys) + + using block_signing_authority = std::variant; + + struct producer_authority { + eosio::name producer_name = {}; + block_signing_authority authority = {}; + }; + + EOSIO_REFLECT(producer_authority, producer_name, authority) + + struct producer_authority_schedule { + uint32_t version = {}; + std::vector producers = {}; + }; + + EOSIO_REFLECT(producer_authority_schedule, version, producers) + + struct chain_config_v0 { + uint64_t max_block_net_usage = {}; + uint32_t target_block_net_usage_pct = {}; + uint32_t max_transaction_net_usage = {}; + uint32_t base_per_transaction_net_usage = {}; + uint32_t net_usage_leeway = {}; + uint32_t context_free_discount_net_usage_num = {}; + uint32_t context_free_discount_net_usage_den = {}; + uint32_t max_block_cpu_usage = {}; + uint32_t target_block_cpu_usage_pct = {}; + uint32_t max_transaction_cpu_usage = {}; + uint32_t min_transaction_cpu_usage = {}; + uint32_t max_transaction_lifetime = {}; + uint32_t deferred_trx_expiration_window = {}; + uint32_t max_transaction_delay = {}; + uint32_t max_inline_action_size = {}; + uint16_t max_inline_action_depth = {}; + uint16_t max_authority_depth = {}; + }; + + EOSIO_REFLECT(chain_config_v0, max_block_net_usage, target_block_net_usage_pct, max_transaction_net_usage, + base_per_transaction_net_usage, net_usage_leeway, context_free_discount_net_usage_num, + context_free_discount_net_usage_den, max_block_cpu_usage, target_block_cpu_usage_pct, + max_transaction_cpu_usage, min_transaction_cpu_usage, max_transaction_lifetime, + deferred_trx_expiration_window, max_transaction_delay, max_inline_action_size, max_inline_action_depth, + max_authority_depth) + + using chain_config = std::variant; + + struct global_property_v0 { + std::optional proposed_schedule_block_num = {}; + producer_schedule proposed_schedule = {}; + chain_config configuration = {}; + }; + + EOSIO_REFLECT(global_property_v0, proposed_schedule_block_num, proposed_schedule, configuration) + + struct global_property_v1 { + std::optional proposed_schedule_block_num = {}; + producer_authority_schedule proposed_schedule = {}; + chain_config configuration = {}; + eosio::checksum256 chain_id = {}; + }; + + EOSIO_REFLECT(global_property_v1, proposed_schedule_block_num, proposed_schedule, configuration, chain_id) + + using global_property = std::variant; + + struct generated_transaction_v0 { + eosio::name sender = {}; + uint128_t sender_id = {}; + eosio::name payer = {}; + eosio::checksum256 trx_id = {}; + eosio::input_stream packed_trx = {}; + }; + + EOSIO_REFLECT(generated_transaction_v0, sender, sender_id, payer, trx_id, packed_trx) + + using generated_transaction = std::variant; + + struct activated_protocol_feature_v0 { + eosio::checksum256 feature_digest = {}; + uint32_t activation_block_num = {}; + }; + + EOSIO_REFLECT(activated_protocol_feature_v0, feature_digest, activation_block_num) + + using activated_protocol_feature = std::variant; + + struct protocol_state_v0 { + std::vector activated_protocol_features = {}; + }; + + EOSIO_REFLECT(protocol_state_v0, activated_protocol_features) + + using protocol_state = std::variant; + + struct permission_level_weight { + permission_level permission = {}; + uint16_t weight = {}; + }; + + EOSIO_REFLECT(permission_level_weight, permission, weight) + + struct wait_weight { + uint32_t wait_sec = {}; + uint16_t weight = {}; + }; + + EOSIO_REFLECT(wait_weight, wait_sec, weight) + + struct authority { + uint32_t threshold = {}; + std::vector keys = {}; + std::vector accounts = {}; + std::vector waits = {}; + }; + + EOSIO_REFLECT(authority, threshold, keys, accounts, waits) + + struct permission_v0 { + eosio::name owner = {}; + eosio::name name = {}; + eosio::name parent = {}; + eosio::time_point last_updated = {}; + authority auth = {}; + }; + + EOSIO_REFLECT(permission_v0, owner, name, parent, last_updated, auth) + + using permission = std::variant; + + struct permission_link_v0 { + eosio::name account = {}; + eosio::name code = {}; + eosio::name message_type = {}; + eosio::name required_permission = {}; + }; + + EOSIO_REFLECT(permission_link_v0, account, code, message_type, required_permission) + + using permission_link = std::variant; + + struct resource_limits_v0 { + eosio::name owner = {}; + int64_t net_weight = {}; + int64_t cpu_weight = {}; + int64_t ram_bytes = {}; + }; + + EOSIO_REFLECT(resource_limits_v0, owner, net_weight, cpu_weight, ram_bytes) + + using resource_limits = std::variant; + + struct usage_accumulator_v0 { + uint32_t last_ordinal = {}; + uint64_t value_ex = {}; + uint64_t consumed = {}; + }; + + EOSIO_REFLECT(usage_accumulator_v0, last_ordinal, value_ex, consumed) + + using usage_accumulator = std::variant; + + struct resource_usage_v0 { + eosio::name owner = {}; + usage_accumulator net_usage = {}; + usage_accumulator cpu_usage = {}; + uint64_t ram_usage = {}; + }; + + EOSIO_REFLECT(resource_usage_v0, owner, net_usage, cpu_usage, ram_usage) + + using resource_usage = std::variant; + + struct resource_limits_state_v0 { + usage_accumulator average_block_net_usage = {}; + usage_accumulator average_block_cpu_usage = {}; + uint64_t total_net_weight = {}; + uint64_t total_cpu_weight = {}; + uint64_t total_ram_bytes = {}; + uint64_t virtual_net_limit = {}; + uint64_t virtual_cpu_limit = {}; + }; + + EOSIO_REFLECT(resource_limits_state_v0, average_block_net_usage, average_block_cpu_usage, total_net_weight, + total_cpu_weight, total_ram_bytes, virtual_net_limit, virtual_cpu_limit) + + using resource_limits_state = std::variant; + + struct resource_limits_ratio_v0 { + uint64_t numerator = {}; + uint64_t denominator = {}; + }; + + EOSIO_REFLECT(resource_limits_ratio_v0, numerator, denominator) + + using resource_limits_ratio = std::variant; + + struct elastic_limit_parameters_v0 { + uint64_t target = {}; + uint64_t max = {}; + uint32_t periods = {}; + uint32_t max_multiplier = {}; + resource_limits_ratio contract_rate = {}; + resource_limits_ratio expand_rate = {}; + }; + + EOSIO_REFLECT(elastic_limit_parameters_v0, target, max, periods, max_multiplier, contract_rate, expand_rate) + + using elastic_limit_parameters = std::variant; + + struct resource_limits_config_v0 { + elastic_limit_parameters cpu_limit_parameters = {}; + elastic_limit_parameters net_limit_parameters = {}; + uint32_t account_cpu_usage_average_window = {}; + uint32_t account_net_usage_average_window = {}; + }; + + EOSIO_REFLECT(resource_limits_config_v0, cpu_limit_parameters, net_limit_parameters, + account_cpu_usage_average_window, account_net_usage_average_window) + + using resource_limits_config = std::variant; + +}} // namespace eosio::ship_protocol diff --git a/include/eosio/stream.hpp b/include/eosio/stream.hpp index 1a09f73..d4a1c60 100644 --- a/include/eosio/stream.hpp +++ b/include/eosio/stream.hpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace eosio { enum class stream_error { @@ -166,6 +167,58 @@ struct size_stream { } }; +template +result increase_indent(S&) { + return outcome::success(); +} + +template +result decrease_indent(S&) { + return outcome::success(); +} + +template +result write_colon(S& s) { + return s.write(':'); +} + +template +result write_newline(S&) { + return outcome::success(); +} + +template +struct pretty_stream : Base { + using Base::Base; + int indent_size = 4; + std::vector current_indent; +}; + +template +result increase_indent(pretty_stream& s) { + s.current_indent.resize(s.current_indent.size() + s.indent_size, ' '); + return outcome::success(); +} + +template +result decrease_indent(pretty_stream& s) { + if (s.current_indent.size() < s.indent_size) + return stream_error::overrun; + s.current_indent.resize(s.current_indent.size() - s.indent_size); + return outcome::success(); +} + +template +result write_colon(pretty_stream& s) { + return s.write(": ", 2); +} + +template +result write_newline(pretty_stream& s) { + OUTCOME_TRY(s.write('\n')); + return s.write(s.current_indent.data(), s.current_indent.size()); +} + struct input_stream { const char* pos; const char* end; diff --git a/include/eosio/time.hpp b/include/eosio/time.hpp index 35a3aef..e50f2c2 100644 --- a/include/eosio/time.hpp +++ b/include/eosio/time.hpp @@ -63,6 +63,8 @@ class time_point { explicit time_point(microseconds e) : elapsed(e) {} const microseconds& time_since_epoch() const { return elapsed; } uint32_t sec_since_epoch() const { return uint32_t(elapsed.count() / 1000000); } + + static time_point max() { return time_point( microseconds::maximum() ); } /// @cond INTERNAL time_point& operator+=(const microseconds& m) { diff --git a/include/eosio/to_json.hpp b/include/eosio/to_json.hpp index c63af0b..4017464 100644 --- a/include/eosio/to_json.hpp +++ b/include/eosio/to_json.hpp @@ -38,16 +38,14 @@ result to_json(std::string_view sv, S& stream) { while (begin != end) { auto pos = begin; while (pos != end && *pos != '"' && *pos != '\\' && (unsigned char)(*pos) >= 32 && *pos != 127) ++pos; - if (begin != pos) { - while (begin != end) { - stream_adaptor s2(begin, static_cast(pos - begin)); - if (rapidjson::UTF8<>::Validate(s2, s2)) { - OUTCOME_TRY(stream.write(begin, s2.idx)); - begin += s2.idx; - } else { - ++begin; - OUTCOME_TRY(stream.write('?')); - } + while (begin != pos) { + stream_adaptor s2(begin, static_cast(pos - begin)); + if (rapidjson::UTF8<>::Validate(s2, s2)) { + OUTCOME_TRY(stream.write(begin, s2.idx)); + begin += s2.idx; + } else { + ++begin; + OUTCOME_TRY(stream.write('?')); } } if (begin != end) { @@ -135,8 +133,8 @@ result fp_to_json(double value, S& stream) { } else if (std::isnan(value)) { return stream.write("\"NaN\"", 5); } - small_buffer::digits10 + 20> b; - int n = fpconv_dtoa(value, b.pos); + small_buffer<24> b; // fpconv_dtoa generates at most 24 characters + int n = fpconv_dtoa(value, b.pos); if (n <= 0) return stream_error::float_error; b.pos += n; @@ -160,24 +158,23 @@ template result to_json(float value, S& stream) { return template result to_json(const std::vector& obj, S& stream) { - auto r = stream.write('['); - if (!r) - return r.error(); + OUTCOME_TRY(stream.write('[')); bool first = true; for (auto& v : obj) { - if (!first) { - r = stream.write(','); - if (!r) - return r.error(); + if (first) { + OUTCOME_TRY(increase_indent(stream)); + } else { + OUTCOME_TRY(stream.write(',')); } + OUTCOME_TRY(write_newline(stream)); first = false; - r = to_json(v, stream); - if (!r) - return r.error(); + OUTCOME_TRY(to_json(v, stream)); } - r = stream.write(']'); - if (!r) - return r.error(); + if (!first) { + OUTCOME_TRY(decrease_indent(stream)); + OUTCOME_TRY(write_newline(stream)); + } + OUTCOME_TRY(stream.write(']')); return outcome::success(); } @@ -193,13 +190,26 @@ result to_json(const std::optional& obj, S& stream) { template result to_json(const std::variant& obj, S& stream) { OUTCOME_TRY(stream.write('[')); + OUTCOME_TRY(increase_indent(stream)); + OUTCOME_TRY(write_newline(stream)); OUTCOME_TRY(std::visit( [&](const auto& t) { return to_json(get_type_name((std::decay_t*)nullptr), stream); }, obj)); OUTCOME_TRY(stream.write(',')); + OUTCOME_TRY(write_newline(stream)); OUTCOME_TRY(std::visit([&](auto& x) { return to_json(x, stream); }, obj)); + OUTCOME_TRY(decrease_indent(stream)); + OUTCOME_TRY(write_newline(stream)); return stream.write(']'); } + template + struct is_std_optional : std::false_type {}; + + template + struct is_std_optional> : std::true_type { + using value_type = T; + }; + template result to_json(const T& t, S& stream) { result ok = outcome::success(); @@ -207,33 +217,58 @@ result to_json(const T& t, S& stream) { OUTCOME_TRY(stream.write('{')); eosio::for_each_field([&](const char* name, auto&& member) { if (ok) { - if (first) { - first = false; - } else { - auto r = stream.write(','); + auto addfield = [&]() { + if (first) { + auto r = increase_indent(stream); + if (!r) { + ok = r; + return; + } + first = false; + } else { + auto r = stream.write(','); + if (!r) { + ok = r; + return; + } + } + auto r = write_newline(stream); if (!r) { ok = r; return; } - } - auto r = to_json(name, stream); - if (!r) { - ok = r; - return; - } - r = stream.write(':'); - if (!r) { - ok = r; - return; - } - r = to_json(member(&t), stream); - if (!r) { - ok = r; - return; + r = to_json(name, stream); + if (!r) { + ok = r; + return; + } + r = write_colon(stream); + if (!r) { + ok = r; + return; + } + r = to_json(member(&t), stream); + if (!r) { + ok = r; + return; + } + }; + + auto m = member(&t); + using member_type = std::decay_t; + if constexpr ( not is_std_optional::value ) { + addfield(); + } else { + if( !!m ) + addfield(); } } }); OUTCOME_TRY(ok); + if (!first) { + OUTCOME_TRY(decrease_indent(stream)); + OUTCOME_TRY(write_newline(stream)); + } return stream.write('}'); } @@ -274,4 +309,21 @@ result convert_to_json(const T& t) { return stream_error::underrun; } +template +result format_json(const T& t) { + pretty_stream ss; + auto r = to_json(t, ss); + if (!r) + return r.error(); + std::string result(ss.size, 0); + pretty_stream fbs(result.data(), result.size()); + r = to_json(t, fbs); + if (!r) + return r.error(); + if (fbs.pos == fbs.end) + return std::move(result); + else + return stream_error::underrun; +} + } // namespace eosio diff --git a/include/eosio/to_key.hpp b/include/eosio/to_key.hpp index 394884c..0b38348 100644 --- a/include/eosio/to_key.hpp +++ b/include/eosio/to_key.hpp @@ -1,21 +1,51 @@ #pragma once +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace eosio { template result to_key(const std::tuple& obj, S& stream); +// to_key defines a conversion from a type to a sequence of bytes whose lexicograpical +// ordering is the same as the ordering of the original type. +// +// For any two objects of type T, a and b: +// +// - key(a) < key(b) iff a < b +// - key(a) is not a prefix of key(b) +// +// Overloads of to_key for user-defined types can be found by Koenig lookup. +// +// Abieos provides specializations of to_key for the following types +// - std::string and std::string_view +// - std::vector, std::list, std::deque +// - std::tuple +// - std::array +// - std::optional +// - std::variant +// - Arithmetic types +// - Scoped enumeration types +// - Reflected structs +// - All smart-contract related types defined by abieos template result to_key(const T& obj, S& stream); template result to_key_tuple(const T& obj, S& stream) { if constexpr (i < std::tuple_size_v) { - auto r = to_key(std::get(obj), stream); - if (!r) - return r.error(); + OUTCOME_TRY(to_key(std::get(obj), stream)); return to_key_tuple(obj, stream); } return outcome::success(); @@ -26,6 +56,190 @@ result to_key(const std::tuple& obj, S& stream) { return to_key_tuple<0>(obj, stream); } +template +result to_key(const std::array& obj, S& stream) { + for (const T& elem : obj) { OUTCOME_TRY(to_key(elem, stream)); } + return outcome::success(); +} + +template +result to_key_optional(const bool* obj, S& stream) { + if (obj == nullptr) + return stream.write('\0'); + else if (!*obj) + return stream.write('\1'); + else + return stream.write('\2'); +} + +template +result to_key_optional(const T* obj, S& stream) { + if constexpr (has_bitwise_serialization() && sizeof(T) == 1) { + if (obj == nullptr) + return stream.write("\0", 2); + else { + char buf[1]; + fixed_buf_stream tmp_stream(buf, 1); + OUTCOME_TRY(to_key(*obj, tmp_stream)); + OUTCOME_TRY(stream.write(buf[0])); + if (buf[0] == '\0') + return stream.write('\1'); + else + return outcome::success(); + } + } else { + if (obj) { + OUTCOME_TRY(stream.write('\1')); + return to_key(*obj, stream); + } else { + return stream.write('\0'); + } + } +} + +template +result to_key(const std::pair& obj, S& stream) { + OUTCOME_TRY(to_key(obj.first, stream)); + return to_key(obj.second, stream); +} + +template +result to_key_range(const T& obj, S& stream) { + for (const auto& elem : obj) { OUTCOME_TRY(to_key_optional(&elem, stream)); } + return to_key_optional((decltype(&*std::begin(obj))) nullptr, stream); +} + +template +result to_key(const std::vector& obj, S& stream) { + for (const T& elem : obj) { OUTCOME_TRY(to_key_optional(&elem, stream)); } + return to_key_optional((const T*)nullptr, stream); +} + +template +result to_key(const std::list& obj, S& stream) { + return to_key_range(obj, stream); +} + +template +result to_key(const std::deque& obj, S& stream) { + return to_key_range(obj, stream); +} + +template +result to_key(const std::set& obj, S& stream) { + return to_key_range(obj, stream); +} + +template +result to_key(const std::map& obj, S& stream) { + return to_key_range(obj, stream); +} + +template +result to_key(const std::optional& obj, S& stream) { + return to_key_optional(obj ? &*obj : nullptr, stream); +} + +// The first byte holds: +// 0-4 1's (number of additional bytes) 0 (terminator) bits +// +// The number is represented as big-endian using the low order +// bits of the first byte and all of the remaining bytes. +// +// Notes: +// - values must be encoded using the minimum number of bytes, +// as non-canonical representations will break the sort order. +template +result to_key_varuint32(std::uint32_t obj, S& stream) { + int num_bytes; + if (obj < 0x80u) { + num_bytes = 1; + } else if (obj < 0x4000u) { + num_bytes = 2; + } else if (obj < 0x200000u) { + num_bytes = 3; + } else if (obj < 0x10000000u) { + num_bytes = 4; + } else { + num_bytes = 5; + } + + OUTCOME_TRY(stream.write( + static_cast(~(0xFFu >> (num_bytes - 1)) | (num_bytes == 5 ? 0 : (obj >> ((num_bytes - 1) * 8)))))); + for (int i = num_bytes - 2; i >= 0; --i) { OUTCOME_TRY(stream.write(static_cast((obj >> i * 8) & 0xFFu))); } + return outcome::success(); +} + +// for non-negative values +// The first byte holds: +// 1 (signbit) 0-4 1's (number of additional bytes) 0 (terminator) bits +// The value is represented as big endian +// for negative values +// The first byte holds: +// 0 (signbit) 0-4 0's (number of additional bytes) 1 (terminator) bits +// The value is adjusted to be positive based on the range that can +// be represented with this number of bytes and then encoded as big endian. +// +// Notes: +// - negative values must sort before positive values +// - For negative value, numbers that need more bytes are smaller, hence +// the encoding of the width must be opposite the encoding used for +// non-negative values. +// - A 5-byte varint can represent values in $[-2^34, 2^34)$. In this case, +// the argument will be sign-extended. +template +result to_key_varint32(std::int32_t obj, S& stream) { + static_assert(std::is_same_v, "to_key for varint32 has been temporarily disabled"); + int num_bytes; + bool sign = (obj < 0); + if (obj < 0x40 && obj >= -0x40) { + num_bytes = 1; + } else if (obj < 0x2000 && obj >= -0x2000) { + num_bytes = 2; + } else if (obj < 0x100000 && obj >= -0x100000) { + num_bytes = 3; + } else if (obj < 0x08000000 && obj >= -0x08000000) { + num_bytes = 4; + } else { + num_bytes = 5; + } + + unsigned char width_field; + if (sign) { + width_field = 0x80u >> num_bytes; + } else { + width_field = 0x80u | ~(0xFFu >> num_bytes); + } + auto uobj = static_cast(obj); + unsigned char value_mask = (0xFFu >> (num_bytes + 1)); + unsigned char high_byte = (num_bytes == 5 ? (sign ? 0xFF : 0) : (uobj >> ((num_bytes - 1) * 8))); + OUTCOME_TRY(stream.write(width_field | (high_byte & value_mask))); + for (int i = num_bytes - 2; i >= 0; --i) { OUTCOME_TRY(stream.write(static_cast((uobj >> i * 8) & 0xFFu))); } + return outcome::success(); +} + +template +result to_key(const std::variant& obj, S& stream) { + OUTCOME_TRY(to_key_varuint32(static_cast(obj.index()), stream)); + return std::visit([&](const auto& item) { return to_key(item, stream); }, obj); +} + +template +result to_key(std::string_view obj, S& stream) { + for (char ch : obj) { + OUTCOME_TRY(stream.write(ch)); + if (ch == '\0') { + OUTCOME_TRY(stream.write('\1')); + } + } + return stream.write("\0", 2); +} + +template +result to_key(const std::string& obj, S& stream) { + return to_key(std::string_view(obj), stream); +} + template result to_key(bool obj, S& stream) { return stream.write(static_cast(obj ? 1 : 0)); @@ -59,9 +273,12 @@ result to_key(const T& obj, S& stream) { v -= static_cast>(std::numeric_limits::min()); std::reverse(reinterpret_cast(&v), reinterpret_cast(&v + 1)); return stream.write_raw(v); + } else if constexpr (std::is_enum_v) { + static_assert(!std::is_convertible_v>, "Serializing unscoped enum"); + return to_key(static_cast>(obj), stream); } else { result r = outcome::success(); - for_each_field(obj, [&](const auto& member) { + eosio::for_each_field(obj, [&](const auto& member) { if (r) r = to_key(member, stream); }); diff --git a/include/eosio/types.hpp b/include/eosio/types.hpp index 918477c..ac30773 100644 --- a/include/eosio/types.hpp +++ b/include/eosio/types.hpp @@ -24,7 +24,7 @@ constexpr const char* get_type_name(std::string*) { return "string"; } constexpr const char* get_type_name(__int128*) { return "int128"; } constexpr const char* get_type_name(unsigned __int128*) { return "uint128"; } -#ifdef EOSIO_CDT_COMPILATION +#ifdef __eosio_cdt__ constexpr const char* get_type_name(long double*) { return "float128"; } #endif diff --git a/include/eosio/varint.hpp b/include/eosio/varint.hpp index 291519f..7556578 100644 --- a/include/eosio/varint.hpp +++ b/include/eosio/varint.hpp @@ -229,6 +229,11 @@ result to_json(const varuint32& obj, S& stream) { return to_json(obj.value, stream); } +template +result to_key(const varuint32& obj, S& stream) { + return to_key_varuint32(obj.value, stream); +} + /** * Variable Length Signed Integer. This provides more efficient serialization of 32-bit signed int. * It serializes a 32-bit signed integer in as few bytes as possible. @@ -439,4 +444,9 @@ result to_json(const varint32& obj, S& stream) { return to_json(obj.value, stream); } +template +result to_key(const varint32& obj, S& stream) { + return to_key_varint32(obj.value, stream); +} + } // namespace eosio diff --git a/src/abieos.hpp b/src/abieos.hpp index 03063c5..4d5a588 100644 --- a/src/abieos.hpp +++ b/src/abieos.hpp @@ -19,7 +19,7 @@ #include #include -#ifdef EOSIO_CDT_COMPILATION +#ifdef __eosio_cdt__ #include #pragma clang diagnostic push @@ -32,7 +32,7 @@ #include #include -#ifdef EOSIO_CDT_COMPILATION +#ifdef __eosio_cdt__ #pragma clang diagnostic pop #endif diff --git a/src/crypto.cpp b/src/crypto.cpp index bdb75ef..4ac4fc1 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -96,6 +96,7 @@ result> digest_suffix_ripemd160(const Container&.. return digest; } + template result string_to_key(std::string_view s, key_type type, std::string_view suffix) { std::vector whole; @@ -194,3 +195,9 @@ result eosio::signature_from_string(std::string_view s) { else return eosio::from_json_error::expected_signature; } + +namespace eosio { + std::string to_base58(const char* d, size_t s ) { + return binary_to_base58( std::string_view(d,s) ); + } +} diff --git a/src/key_test.cpp b/src/key_test.cpp index 7af1de2..98e2376 100644 --- a/src/key_test.cpp +++ b/src/key_test.cpp @@ -20,6 +20,7 @@ using abieos::float128; using abieos::time_point; using abieos::time_point_sec; using abieos::block_timestamp; +using eosio::name; using abieos::bytes; using abieos::checksum160; using abieos::checksum256; @@ -40,6 +41,7 @@ struct struct_type { EOSIO_REFLECT(struct_type, v, o, va); EOSIO_COMPARE(struct_type); +// Verifies that the ordering of keys is the same as the ordering of the original objects template void test_key(const T& x, const T& y) { auto keyx = eosio::convert_to_key(x).value(); @@ -48,6 +50,35 @@ void test_key(const T& x, const T& y) { CHECK(std::lexicographical_compare(keyy.begin(), keyy.end(), keyx.begin(), keyx.end(), std::less()) == (y < x)); } +enum class enum_u8 : unsigned char { + v0, + v1, + v2 = 255, +}; +enum class enum_s8 : signed char { + v0, + v1, + v2 = -1, +}; + +enum class enum_u16 : std::uint16_t { + v0, + v1, + v2 = 65535, +}; +enum class enum_s16 : std::int16_t { + v0, + v1, + v2 = -1, +}; + +template +std::size_t key_size(const T& obj) { + eosio::size_stream ss; + eosio::check_discard(to_key(obj, ss)); + return ss.size; +} + void test_compare() { test_key(true, true); test_key(false, false); @@ -75,6 +106,110 @@ void test_compare() { test_key(-std::numeric_limits::infinity(), 0.); test_key(std::numeric_limits::infinity(), 0.); test_key(-std::numeric_limits::infinity(), std::numeric_limits::infinity()); + using namespace eosio::literals; + test_key("a"_n, "a"_n); + test_key(name(), name()); + test_key("a"_n, "b"_n); + test_key("ab"_n, "a"_n); + test_key(checksum256(), checksum256()); + test_key(checksum256(), checksum256{std::array{0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}}); + test_key(checksum256(std::array{0x00ffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}), + checksum256(std::array{0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffff00ull})); + test_key(checksum256(std::array{0xffffffffffffffffull, 0xffffffffffffff00ull, 0xffffffffffffffffull, 0xffffffffffffffffull}), + checksum256(std::array{0xffffffffffffffffull, 0x00ffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull})); + test_key(public_key(), public_key()); + test_key(public_key(std::in_place_index<0>, eosio::ecc_public_key{1}), public_key(std::in_place_index<1>)); + test_key(public_key(eosio::webauthn_public_key{{}, eosio::webauthn_public_key::user_presence_t::USER_PRESENCE_NONE, "b"}), + public_key(eosio::webauthn_public_key{{}, eosio::webauthn_public_key::user_presence_t::USER_PRESENCE_PRESENT, "a"})); + + using namespace std::literals; + test_key(""s, ""s); + test_key(""s, "a"s); + test_key("a"s, "b"s); + test_key("aaaaa"s, "aaaaa"s); + test_key("\0"s, "\xFF"s); + test_key("\0"s, ""s); + test_key("\0\0\0"s, "\0\0"s); + + test_key(std::vector{}, std::vector{}); + test_key(std::vector{}, std::vector{0}); + test_key(std::vector{0}, std::vector{1}); + + test_key(std::vector{}, std::vector{'\0'}); + test_key(std::vector{'\0'}, std::vector{'\xFF'}); + test_key(std::vector{'\1'}, std::vector{'\xFF'}); + test_key(std::vector{'b'}, std::vector{'a'}); + + test_key(std::vector{}, std::vector{'\0'}); + test_key(std::vector{'\0'}, std::vector{'\xFF'}); + test_key(std::vector{'\1'}, std::vector{'\xFF'}); + test_key(std::vector{'b'}, std::vector{'a'}); + + test_key(std::vector{}, std::vector{'\0'}); + test_key(std::vector{'\0'}, std::vector{255}); + test_key(std::vector{'\1'}, std::vector{255}); + test_key(std::vector{'b'}, std::vector{'a'}); + + test_key(std::vector{}, std::vector{true}); + test_key(std::vector{false}, std::vector{true}); + test_key(std::vector{false}, std::vector{false, true}); + + test_key(std::list{}, std::list{1}); + test_key(std::list{0}, std::list{1}); + test_key(std::deque{}, std::deque{1}); + test_key(std::deque{0}, std::deque{1}); + test_key(std::set{}, std::set{1}); + test_key(std::set{0}, std::set{1}); + test_key(std::map{}, std::map{{1, 0}}); + test_key(std::map{{0, 0}}, std::map{{1, 0}}); + + test_key(enum_u8::v0, enum_u8::v1); + test_key(enum_u8::v0, enum_u8::v2); + test_key(enum_u8::v1, enum_u8::v2); + + test_key(enum_s8::v0, enum_s8::v1); + test_key(enum_s8::v0, enum_s8::v2); + test_key(enum_s8::v1, enum_s8::v2); + + test_key(enum_u16::v0, enum_u16::v1); + test_key(enum_u16::v0, enum_u16::v2); + test_key(enum_u16::v1, enum_u16::v2); + + test_key(enum_s16::v0, enum_s16::v1); + test_key(enum_s16::v0, enum_s16::v2); + test_key(enum_s16::v1, enum_s16::v2); + + test_key(varuint32(0), varuint32(0)); + test_key(varuint32(0), varuint32(1)); + test_key(varuint32(1), varuint32(0xFF)); + test_key(varuint32(1), varuint32(0xFFFF)); + test_key(varuint32(1), varuint32(0xFFFFFF)); + test_key(varuint32(1), varuint32(0x7FFFFFFF)); + test_key(varuint32(0x7FFFFF00), varuint32(0x7FFF00FF)); + CHECK(key_size(varuint32(0)) == 1); + CHECK(key_size(varuint32(0xFF)) == 2); + +#if 0 + test_key(varint32(0), varint32(0)); + test_key(varint32(0), varint32(1)); + test_key(varint32(1), varint32(0xFF)); + test_key(varint32(1), varint32(0xFFFF)); + test_key(varint32(1), varint32(0xFFFFFF)); + test_key(varint32(1), varint32(-1)); + test_key(varint32(1), varint32(0x7FFFFFFF)); + test_key(varint32(0x7FFFFF00), varint32(0x7FFF00FF)); + test_key(varint32(-0x7FFFF), varint32(-0x7FFFFFFF)); + test_key(varint32(-0x80000000), varint32(-0x7FFFFFFF)); + test_key(varint32(-0x7F00FF01), varint32(0x7FFF0100)); + CHECK(key_size(varint32(-1)) == 1); + CHECK(key_size(varint32(0)) == 1); + CHECK(key_size(varint32(0xFF)) == 2); +#endif + + test_key(struct_type{{}, {}, {0}}, struct_type{{}, {}, {0}}); + test_key(struct_type{{0, 1, 2}, {}, {0}}, struct_type{{}, {}, {0.0}}); + test_key(struct_type{{0, 1, 2}, {}, {0}}, struct_type{{0, 1, 2}, 0, {0}}); + test_key(struct_type{{0, 1, 2}, 0, {0}}, struct_type{{0, 1, 2}, 0, {0.0}}); } int main() { diff --git a/src/reflect_test.cpp b/src/reflect_test.cpp new file mode 100644 index 0000000..d447fc0 --- /dev/null +++ b/src/reflect_test.cpp @@ -0,0 +1,28 @@ +#include +#include +#include + +int error_count; + +void report_error(const char* assertion, const char* file, int line) { + if(error_count <= 20) { + std::printf("%s:%d: failed %s\n", file, line, assertion); + } + ++error_count; +} + +#define CHECK(...) do { if(__VA_ARGS__) {} else { report_error(#__VA_ARGS__, __FILE__, __LINE__); } } while(0) + +struct fn { + int test(int i) { return i * 2; } +}; +EOSIO_REFLECT(fn, test); + +int main() { + int counter = 0; + eosio::for_each_field([&](const char* name, auto method) { ++counter; }); + CHECK(counter == 0); + eosio::for_each_method([&](const char* name, int (fn::*m)(int)) { CHECK(m == &fn::test); ++counter; }); + CHECK(counter == 1); + if(error_count) return 1; +} diff --git a/src/test.cpp b/src/test.cpp index 13b2986..c30e3d5 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -569,6 +569,7 @@ void check_types() { check_type(context, 0, "string", R"("' + '*'.repeat(128) + '")"); check_type(context, 0, "string", R"("\u0000 这是一个测试 Это тест هذا اختبار 👍")"); check(abieos_bin_to_json(context, 0, "string", "\x11invalid utf8: \xff\xfe\xfd", 18) == std::string(R"("invalid utf8: ???")"), "invalid utf8"); + check(abieos_bin_to_json(context, 0, "string", "\4\xe8\xbf\x99\n", 5) == std::string("\"\xe8\xbf\x99\\u000A\""), "escaping"); check_error(context, "Stream overrun", [&] { return abieos_hex_to_json(context, 0, "string", "01"); }); check_type(context, 0, "checksum160", R"("0000000000000000000000000000000000000000")"); check_type(context, 0, "checksum160", R"("123456789ABCDEF01234567890ABCDEF70123456")"); diff --git a/test/yarn.lock b/test/yarn.lock index 65aa131..1ac1651 100644 --- a/test/yarn.lock +++ b/test/yarn.lock @@ -154,8 +154,8 @@ acorn-dynamic-import@^3.0.0: acorn "^5.0.0" acorn@^5.0.0, acorn@^5.6.2: - version "5.7.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" + version "5.7.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" ajv-keywords@^3.1.0: version "3.2.0" @@ -364,11 +364,11 @@ bluebird@3, bluebird@^3.5.1: bluebird@^2.9.15: version "2.11.0" - resolved "http://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" + resolved "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" bluebird@~3.4.1: version "3.4.7" - resolved "http://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" @@ -471,7 +471,7 @@ buffer-xor@^1.0.3: buffer@^4.3.0: version "4.9.1" - resolved "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" + resolved "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" @@ -673,7 +673,7 @@ combined-stream@1.0.6, combined-stream@~1.0.6: commander@2.9.x: version "2.9.0" - resolved "http://registry.npmjs.org/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" + resolved "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" dependencies: graceful-readlink ">= 1.0.0" @@ -1700,7 +1700,7 @@ lodash@4, lodash@^4.17.10: lodash@^3.6.0: version "3.10.1" - resolved "http://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" + resolved "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" lru-cache@^4.1.1: version "4.1.3" @@ -1818,11 +1818,11 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: minimist@0.0.8: version "0.0.8" - resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + resolved "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" minimist@^1.2.0: version "1.2.0" - resolved "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" minipass@^2.0.2, minipass@^2.2.1, minipass@^2.3.3: version "2.3.4" @@ -1861,7 +1861,7 @@ mixin-deep@^1.2.0: "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: version "0.5.1" - resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: minimist "0.0.8" @@ -2068,7 +2068,7 @@ os-homedir@^1.0.0: os-locale@^1.4.0: version "1.4.0" - resolved "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + resolved "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" dependencies: lcid "^1.0.0" @@ -2310,7 +2310,7 @@ rc@^1.2.7: "readable-stream@1 || 2", readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6: version "2.3.6" - resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -2322,7 +2322,7 @@ rc@^1.2.7: readable-stream@~1.0.26-2: version "1.0.34" - resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" dependencies: core-util-is "~1.0.0" inherits "~2.0.1" @@ -2331,7 +2331,7 @@ readable-stream@~1.0.26-2: readable-stream@~2.1.5: version "2.1.5" - resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" dependencies: buffer-shims "^1.0.0" core-util-is "~1.0.0" @@ -3097,7 +3097,7 @@ worker-farm@^1.5.2: wrap-ansi@^2.0.0: version "2.1.0" - resolved "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" dependencies: string-width "^1.0.1" strip-ansi "^3.0.1" @@ -3155,7 +3155,7 @@ yargs@^12.0.1: yargs@^3.6.0: version "3.32.0" - resolved "http://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" + resolved "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" dependencies: camelcase "^2.0.1" cliui "^3.0.3" diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 4adbe45..63ec466 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,7 +1,7 @@ # copyright defined in abieos/LICENSE.txt add_executable(name name.cpp) -target_link_libraries(name abieos_headers) +target_link_libraries(name abieos) add_custom_command( TARGET name POST_BUILD COMMAND ${CMAKE_COMMAND} -E create_symlink $ ${CMAKE_CURRENT_BINARY_DIR}/name2num ) add_custom_command( TARGET name POST_BUILD COMMAND ${CMAKE_COMMAND} -E create_symlink $ ${CMAKE_CURRENT_BINARY_DIR}/num2name )