diff --git a/libraries/chain/abi_serializer.cpp b/libraries/chain/abi_serializer.cpp index c06c6a4fb4..0e6232b8af 100644 --- a/libraries/chain/abi_serializer.cpp +++ b/libraries/chain/abi_serializer.cpp @@ -548,14 +548,15 @@ namespace eosio { namespace chain { bool disallow_additional_fields = false; for( uint32_t i = 0; i < st.fields.size(); ++i ) { const auto& field = st.fields[i]; - if( vo.contains( string(field.name).c_str() ) ) { + bool present = vo.contains(string(field.name).c_str()); + if( present || is_optional(field.type) ) { if( disallow_additional_fields ) EOS_THROW( pack_exception, "Unexpected field '${f}' found in input object while processing struct '${p}'", ("f", ctx.maybe_shorten(field.name))("p", ctx.get_path_string()) ); { auto h1 = ctx.push_to_path( impl::field_path_item{ .parent_struct_itr = s_itr, .field_ordinal = i } ); auto h2 = ctx.disallow_extensions_unless( &field == &st.fields.back() ); - _variant_to_binary(_remove_bin_extension(field.type), vo[field.name], ds, ctx); + _variant_to_binary(_remove_bin_extension(field.type), present ? vo[field.name] : fc::variant(nullptr), ds, ctx); } } else if( ends_with(field.type, "$") && ctx.extensions_allowed() ) { disallow_additional_fields = true; diff --git a/libraries/libfc/include/fc/time.hpp b/libraries/libfc/include/fc/time.hpp index 53a284d788..96b7156781 100644 --- a/libraries/libfc/include/fc/time.hpp +++ b/libraries/libfc/include/fc/time.hpp @@ -12,7 +12,8 @@ namespace fc { class microseconds { public: constexpr explicit microseconds( int64_t c = 0) :_count(c){} - static constexpr microseconds maximum() { return microseconds(0x7fffffffffffffffll); } + static constexpr microseconds maximum() { return microseconds(std::numeric_limits::max()); } + static constexpr microseconds minimum() { return microseconds(std::numeric_limits::min()); } friend constexpr microseconds operator + (const microseconds& l, const microseconds& r ) { return microseconds(l._count+r._count); } friend constexpr microseconds operator - (const microseconds& l, const microseconds& r ) { return microseconds(l._count-r._count); } @@ -52,12 +53,12 @@ namespace fc { // protect against overflow/underflow constexpr time_point& safe_add( const microseconds& m ) { - if (m.count() > 0 && elapsed > fc::microseconds::maximum() - m) { + if (m.count() > 0 && elapsed > microseconds::maximum() - m) { elapsed = microseconds::maximum(); - } else if (m.count() < 0 && elapsed.count() < std::numeric_limits::min() - m.count()) { - elapsed = microseconds(std::numeric_limits::min()); + } else if (m.count() < 0 && elapsed < microseconds::minimum() - m) { + elapsed = microseconds::minimum(); } else { - elapsed += m; + elapsed += m; } return *this; } @@ -94,7 +95,7 @@ namespace fc { constexpr explicit time_point_sec( const time_point& t ) :utc_seconds( t.time_since_epoch().count() / 1000000ll ){} - static constexpr time_point_sec maximum() { return time_point_sec(0xffffffff); } + static constexpr time_point_sec maximum() { return time_point_sec(std::numeric_limits::max()); } static constexpr time_point_sec min() { return time_point_sec(0); } constexpr time_point to_time_point()const { return time_point( fc::seconds( utc_seconds) ); } diff --git a/unittests/abi_tests.cpp b/unittests/abi_tests.cpp index ddf2ca5661..3506096632 100644 --- a/unittests/abi_tests.cpp +++ b/unittests/abi_tests.cpp @@ -41,7 +41,15 @@ FC_REFLECT(act_sig, (sig) ) BOOST_AUTO_TEST_SUITE(abi_tests) +#ifdef NDEBUG fc::microseconds max_serialization_time = fc::seconds(1); // some test machines are very slow +#else +fc::microseconds max_serialization_time = fc::microseconds::maximum(); // don't check in debug builds +#endif + +static fc::time_point get_deadline() { + return fc::time_point::now().safe_add(max_serialization_time); +} // verify that round trip conversion, via bytes, reproduces the exact same data fc::variant verify_byte_round_trip_conversion( const abi_serializer& abis, const type_name& type, const fc::variant& var ) @@ -53,8 +61,8 @@ fc::variant verify_byte_round_trip_conversion( const abi_serializer& abis, const auto var2 = abis.binary_to_variant(type, bytes, abi_serializer::create_yield_function( max_serialization_time )); auto var3 = abis.binary_to_variant(type, b, max_serialization_time); - std::string r2 = fc::json::to_string(var2, fc::time_point::now() + max_serialization_time); - std::string r3 = fc::json::to_string(var3, fc::time_point::now() + max_serialization_time); + std::string r2 = fc::json::to_string(var2, get_deadline()); + std::string r3 = fc::json::to_string(var3, get_deadline()); BOOST_TEST( r2 == r3 ); auto bytes2 = abis.variant_to_binary(type, var2, abi_serializer::create_yield_function( max_serialization_time )); @@ -74,9 +82,9 @@ void verify_round_trip_conversion( const abi_serializer& abis, const type_name& auto b = abis.variant_to_binary(type, var, max_serialization_time); BOOST_REQUIRE_EQUAL(fc::to_hex(b), hex); auto var2 = abis.binary_to_variant(type, bytes, abi_serializer::create_yield_function( max_serialization_time )); - BOOST_REQUIRE_EQUAL(fc::json::to_string(var2, fc::time_point::now() + max_serialization_time), expected_json); + BOOST_REQUIRE_EQUAL(fc::json::to_string(var2, get_deadline()), expected_json); auto var3 = abis.binary_to_variant(type, b, max_serialization_time ); - BOOST_REQUIRE_EQUAL(fc::json::to_string(var3, fc::time_point::now() + max_serialization_time), expected_json); + BOOST_REQUIRE_EQUAL(fc::json::to_string(var3, get_deadline()), expected_json); auto bytes2 = abis.variant_to_binary(type, var2, abi_serializer::create_yield_function( max_serialization_time )); BOOST_REQUIRE_EQUAL(fc::to_hex(bytes2), hex); auto b2 = abis.variant_to_binary(type, var3, max_serialization_time); @@ -115,8 +123,8 @@ fc::variant verify_type_round_trip_conversion( const abi_serializer& abis, const fc::variant var3; abi_serializer::to_variant(obj2, var3, get_resolver(), max_serialization_time); - std::string r2 = fc::json::to_string(var2, fc::time_point::now() + max_serialization_time); - std::string r3 = fc::json::to_string(var3, fc::time_point::now() + max_serialization_time); + std::string r2 = fc::json::to_string(var2, get_deadline()); + std::string r3 = fc::json::to_string(var3, get_deadline()); BOOST_TEST( r2 == r3 ); auto bytes2 = abis.variant_to_binary(type, var2, abi_serializer::create_yield_function( max_serialization_time )); @@ -1972,6 +1980,113 @@ BOOST_AUTO_TEST_CASE(abi_type_loop) } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE(abi_std_optional) +{ try { + const char* repeat_abi = R"=====( + { + "version": "eosio::abi/1.2", + "types": [], + "structs": [ + { + "name": "fees", + "base": "", + "fields": [ + { + "name": "gas_price", + "type": "uint64?" + }, + { + "name": "miner_cut", + "type": "uint32?" + }, + { + "name": "bridge_fee", + "type": "uint32?" + } + ] + } + ], + "actions": [ + { + "name": "fees", + "type": "fees", + "ricardian_contract": "" + } + ], + "tables": [], + "ricardian_clauses": [], + "variants": [], + "action_results": [] + } + )====="; + + abi_serializer abis(fc::json::from_string(repeat_abi).as(), abi_serializer::create_yield_function( max_serialization_time )); + { + // check conversion when all optional members are provided + std::string test_data = R"=====( + { + "gas_price" : "42", + "miner_cut" : "2", + "bridge_fee" : "2" + } + )====="; + + auto var = fc::json::from_string(test_data); + verify_byte_round_trip_conversion(abis, "fees", var); + } + + { + // check conversion when the first optional member is missing + std::string test_data = R"=====( + { + "miner_cut" : "2", + "bridge_fee" : "2" + } + )====="; + + auto var = fc::json::from_string(test_data); + verify_byte_round_trip_conversion(abis, "fees", var); + } + + { + // check conversion when the second optional member is missing + std::string test_data = R"=====( + { + "gas_price" : "42", + "bridge_fee" : "2" + } + )====="; + + auto var = fc::json::from_string(test_data); + verify_byte_round_trip_conversion(abis, "fees", var); + } + + { + // check conversion when the last optional member is missing + std::string test_data = R"=====( + { + "gas_price" : "42", + "miner_cut" : "2", + } + )====="; + + auto var = fc::json::from_string(test_data); + verify_byte_round_trip_conversion(abis, "fees", var); + } + + { + // check conversion when all optional members are missing + std::string test_data = R"=====( + { + } + )====="; + + auto var = fc::json::from_string(test_data); + verify_byte_round_trip_conversion(abis, "fees", var); + } + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE(abi_type_redefine) { try { // inifinite loop in types @@ -3127,7 +3242,7 @@ BOOST_AUTO_TEST_CASE(abi_to_variant__add_action__good_return_value) mutable_variant_object mvo; eosio::chain::impl::abi_traverse_context ctx(abi_serializer::create_yield_function(max_serialization_time), fc::microseconds{}); eosio::chain::impl::abi_to_variant::add(mvo, "action_traces", at, get_resolver(abidef), ctx); - std::string res = fc::json::to_string(mvo, fc::time_point::now() + max_serialization_time); + std::string res = fc::json::to_string(mvo, get_deadline()); BOOST_CHECK_EQUAL(res, expected_json); } @@ -3135,7 +3250,7 @@ BOOST_AUTO_TEST_CASE(abi_to_variant__add_action__good_return_value) mutable_variant_object mvo; eosio::chain::impl::abi_traverse_context ctx(abi_serializer::create_depth_yield_function(), max_serialization_time); eosio::chain::impl::abi_to_variant::add(mvo, "action_traces", at, get_resolver(abidef), ctx); - std::string res = fc::json::to_string(mvo, fc::time_point::now() + max_serialization_time); + std::string res = fc::json::to_string(mvo, get_deadline()); BOOST_CHECK_EQUAL(res, expected_json); } @@ -3162,7 +3277,7 @@ BOOST_AUTO_TEST_CASE(abi_to_variant__add_action__bad_return_value) mutable_variant_object mvo; eosio::chain::impl::abi_traverse_context ctx(abi_serializer::create_yield_function(max_serialization_time), fc::microseconds{}); eosio::chain::impl::abi_to_variant::add(mvo, "action_traces", at, get_resolver(abidef), ctx); - std::string res = fc::json::to_string(mvo, fc::time_point::now() + max_serialization_time); + std::string res = fc::json::to_string(mvo, get_deadline()); BOOST_CHECK_EQUAL(res, expected_json); } @@ -3170,7 +3285,7 @@ BOOST_AUTO_TEST_CASE(abi_to_variant__add_action__bad_return_value) mutable_variant_object mvo; eosio::chain::impl::abi_traverse_context ctx(abi_serializer::create_depth_yield_function(), max_serialization_time); eosio::chain::impl::abi_to_variant::add(mvo, "action_traces", at, get_resolver(abidef), ctx); - std::string res = fc::json::to_string(mvo, fc::time_point::now() + max_serialization_time); + std::string res = fc::json::to_string(mvo, get_deadline()); BOOST_CHECK_EQUAL(res, expected_json); } @@ -3207,7 +3322,7 @@ BOOST_AUTO_TEST_CASE(abi_to_variant__add_action__no_return_value) mutable_variant_object mvo; eosio::chain::impl::abi_traverse_context ctx(abi_serializer::create_yield_function(max_serialization_time), fc::microseconds{}); eosio::chain::impl::abi_to_variant::add(mvo, "action_traces", at, get_resolver(abidef), ctx); - std::string res = fc::json::to_string(mvo, fc::time_point::now() + max_serialization_time); + std::string res = fc::json::to_string(mvo, get_deadline()); BOOST_CHECK_EQUAL(res, expected_json); } @@ -3215,7 +3330,7 @@ BOOST_AUTO_TEST_CASE(abi_to_variant__add_action__no_return_value) mutable_variant_object mvo; eosio::chain::impl::abi_traverse_context ctx(abi_serializer::create_depth_yield_function(), max_serialization_time); eosio::chain::impl::abi_to_variant::add(mvo, "action_traces", at, get_resolver(abidef), ctx); - std::string res = fc::json::to_string(mvo, fc::time_point::now() + max_serialization_time); + std::string res = fc::json::to_string(mvo, get_deadline()); BOOST_CHECK_EQUAL(res, expected_json); }