Skip to content

Commit

Permalink
Merge pull request #1364 from AntelopeIO/gh-1189-3.2
Browse files Browse the repository at this point in the history
[3.2] Fix incorrect serializing of std::optional when value is not provided - (GH #1189)
  • Loading branch information
greg7mdp authored Jul 5, 2023
2 parents 2a13774 + f41ca84 commit c456d17
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 9 deletions.
5 changes: 3 additions & 2 deletions libraries/chain/abi_serializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -535,14 +535,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;
Expand Down
129 changes: 122 additions & 7 deletions unittests/abi_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,17 @@ 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() {
if (max_serialization_time == fc::microseconds::maximum())
return fc::time_point(fc::microseconds::maximum());
return fc::time_point::now() + 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 )
Expand All @@ -49,8 +59,6 @@ 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 ));

std::string r = fc::json::to_string(var2, fc::time_point::now() + max_serialization_time);

auto bytes2 = abis.variant_to_binary(type, var2, abi_serializer::create_yield_function( max_serialization_time ));

BOOST_TEST( fc::to_hex(bytes) == fc::to_hex(bytes2) );
Expand All @@ -64,7 +72,7 @@ void verify_round_trip_conversion( const abi_serializer& abis, const type_name&
auto bytes = abis.variant_to_binary(type, var, abi_serializer::create_yield_function( max_serialization_time ));
BOOST_REQUIRE_EQUAL(fc::to_hex(bytes), 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 bytes2 = abis.variant_to_binary(type, var2, abi_serializer::create_yield_function( max_serialization_time ));
BOOST_REQUIRE_EQUAL(fc::to_hex(bytes2), hex);
}
Expand Down Expand Up @@ -94,7 +102,7 @@ fc::variant verify_type_round_trip_conversion( const abi_serializer& abis, const
fc::variant var2;
abi_serializer::to_variant(obj, var2, get_resolver(), abi_serializer::create_yield_function( max_serialization_time ));

std::string r = fc::json::to_string(var2, fc::time_point::now() + max_serialization_time);
std::string r = fc::json::to_string(var2, get_deadline());


auto bytes2 = abis.variant_to_binary(type, var2, abi_serializer::create_yield_function( max_serialization_time ));
Expand Down Expand Up @@ -1929,6 +1937,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_def>(), 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
Expand Down Expand Up @@ -2976,7 +3091,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));
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);
}
Expand All @@ -3001,7 +3116,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));
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);
}
Expand Down Expand Up @@ -3036,7 +3151,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));
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);
}
Expand Down

0 comments on commit c456d17

Please sign in to comment.