diff --git a/libraries/chain/abi_serializer.cpp b/libraries/chain/abi_serializer.cpp index 6c6752dad6f063c8c6441c1c2feb7f14eb62a620..59817288928764929080d6d8280626763cfbbf0c 100644 --- a/libraries/chain/abi_serializer.cpp +++ b/libraries/chain/abi_serializer.cpp @@ -346,12 +346,12 @@ namespace eosio { namespace chain { } fc::variant abi_serializer::binary_to_variant(const type_name& type, const bytes& binary, const fc::microseconds& max_serialization_time)const { - impl::binary_to_variant_context ctx(max_serialization_time); + impl::binary_to_variant_context ctx(*this, max_serialization_time, type); return _binary_to_variant(type, binary, ctx); } fc::variant abi_serializer::binary_to_variant(const type_name& type, fc::datastream& binary, const fc::microseconds& max_serialization_time)const { - impl::binary_to_variant_context ctx(max_serialization_time); + impl::binary_to_variant_context ctx(*this, max_serialization_time, type); return _binary_to_variant(type, binary, ctx); } @@ -360,23 +360,29 @@ namespace eosio { namespace chain { auto h = ctx.enter_scope(); auto rtype = resolve_type(type); + auto v_itr = variants.end(); + auto s_itr = structs.end(); + auto btype = built_in_types.find(fundamental_type(rtype)); if( btype != built_in_types.end() ) { btype->second.second(var, ds, is_array(rtype), is_optional(rtype)); } else if ( is_array(rtype) ) { + ctx.hint_array_type_if_in_array(); vector vars = var.get_array(); fc::raw::pack(ds, (fc::unsigned_int)vars.size()); - auto h1 = ctx.push_to_path( fundamental_type(rtype), ctx.is_path_empty() ); + + auto h1 = ctx.push_to_path( impl::array_index_path_item{} ); + auto h2 = ctx.disallow_extensions_unless(false); + int64_t i = 0; for (const auto& var : vars) { ctx.set_array_index_of_path_back(i); - auto h2 = ctx.disallow_extensions_unless(false); _variant_to_binary(fundamental_type(rtype), var, ds, ctx); ++i; } - } else if ( variants.find(rtype) != variants.end() ) { - auto& v = variants.find(rtype)->second; - auto h1 = ctx.push_to_path( v.name, ctx.is_path_empty() ); + } else if( (v_itr = variants.find(rtype)) != variants.end() ) { + ctx.hint_variant_type_if_in_array( v_itr ); + auto& v = v_itr->second; EOS_ASSERT( var.is_array() && var.size() == 2, pack_exception, "Expected input to be an array of two items while processing variant '${p}'", ("p", ctx.get_path_string()) ); EOS_ASSERT( var[size_t(0)].is_string(), pack_exception, @@ -385,16 +391,13 @@ namespace eosio { namespace chain { auto it = find(v.types.begin(), v.types.end(), variant_type_str); EOS_ASSERT( it != v.types.end(), pack_exception, "Specified type '${t}' in input array is not valid within the variant '${p}'", - ("t", variant_type_str)("p", ctx.get_path_string()) ); + ("t", ctx.maybe_shorten(variant_type_str))("p", ctx.get_path_string()) ); fc::raw::pack(ds, fc::unsigned_int(it - v.types.begin())); - std::stringstream s; - s << ""; - auto h3 = ctx.push_to_path(s.str()); + auto h1 = ctx.push_to_path( impl::variant_path_item{ .variant_itr = v_itr, .variant_ordinal = static_cast(it - v.types.begin()) } ); _variant_to_binary( *it, var[size_t(1)], ds, ctx ); - } else { - const auto& st = get_struct(rtype); - - auto h1 = ctx.push_to_path( st.name, ctx.is_path_empty() ); + } else if( (s_itr = structs.find(rtype)) != structs.end() ) { + ctx.hint_struct_type_if_in_array( s_itr ); + const auto& st = s_itr->second; if( var.is_object() ) { const auto& vo = var.get_object(); @@ -404,22 +407,27 @@ namespace eosio { namespace chain { _variant_to_binary(resolve_type(st.base), var, ds, ctx); } bool extension_encountered = false; + uint32_t i = 0; for( const auto& field : st.fields ) { if( vo.contains( string(field.name).c_str() ) ) { if( extension_encountered ) - EOS_THROW( pack_exception, "Unexpected field '${f}' found in input object while processing struct '${p}'", ("f",field.name)("p",ctx.get_path_string()) ); + 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() ); - auto h3 = ctx.push_to_path( field.name ); _variant_to_binary(_remove_bin_extension(field.type), vo[field.name], ds, ctx); } } else if( ends_with(field.type, "$") && ctx.extensions_allowed() ) { extension_encountered = true; } else if( extension_encountered ) { - EOS_THROW( pack_exception, "Encountered field '${f}' without binary extension designation while processing struct '${p}'", ("f",field.name)("p",ctx.get_path_string()) ); + EOS_THROW( pack_exception, "Encountered field '${f}' without binary extension designation while processing struct '${p}'", + ("f", ctx.maybe_shorten(field.name))("p", ctx.get_path_string()) ); } else { - EOS_THROW( pack_exception, "Missing field '${f}' in input object while processing struct '${p}'", ("f",field.name)("p",ctx.get_path_string()) ); + EOS_THROW( pack_exception, "Missing field '${f}' in input object while processing struct '${p}'", + ("f", ctx.maybe_shorten(field.name))("p", ctx.get_path_string()) ); } + ++i; } } else if( var.is_array() ) { const auto& va = var.get_array(); @@ -429,20 +437,22 @@ namespace eosio { namespace chain { uint32_t i = 0; for( const auto& field : st.fields ) { if( va.size() > i ) { + 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() ); - auto h3 = ctx.push_to_path( field.name ); _variant_to_binary(_remove_bin_extension(field.type), va[i], ds, ctx); } else if( ends_with(field.type, "$") && ctx.extensions_allowed() ) { break; } else { EOS_THROW( pack_exception, "Early end to input array specifying the fields of struct '${p}'; require input for field '${f}'", - ("p", ctx.get_path_string())("f", field.name) ); + ("p", ctx.get_path_string())("f", ctx.maybe_shorten(field.name)) ); } ++i; } } else { EOS_THROW( pack_exception, "Unexpected input encountered while processing struct '${p}'", ("p",ctx.get_path_string()) ); } + } else { + EOS_THROW( invalid_type_inside_abi, "Unknown type ${type}", ("type",type) ); } } FC_CAPTURE_AND_RETHROW( (type)(var) ) } @@ -460,13 +470,15 @@ namespace eosio { namespace chain { return temp; } FC_CAPTURE_AND_RETHROW( (type)(var) ) } - bytes abi_serializer::variant_to_binary(const type_name& type, const fc::variant& var, const fc::microseconds& max_serialization_time)const { - impl::variant_to_binary_context ctx(max_serialization_time); + bytes abi_serializer::variant_to_binary(const type_name& type, const fc::variant& var, const fc::microseconds& max_serialization_time, bool short_path)const { + impl::variant_to_binary_context ctx(*this, max_serialization_time, type); + ctx.short_path = short_path; return _variant_to_binary(type, var, ctx); } - void abi_serializer::variant_to_binary(const type_name& type, const fc::variant& var, fc::datastream& ds, const fc::microseconds& max_serialization_time)const { - impl::variant_to_binary_context ctx(max_serialization_time); + void abi_serializer::variant_to_binary(const type_name& type, const fc::variant& var, fc::datastream& ds, const fc::microseconds& max_serialization_time, bool short_path)const { + impl::variant_to_binary_context ctx(*this, max_serialization_time, type); + ctx.short_path = short_path; _variant_to_binary(type, var, ds, ctx); } @@ -489,4 +501,276 @@ namespace eosio { namespace chain { return itr->second; } + namespace impl { + + void abi_traverse_context::check_deadline()const { + EOS_ASSERT( fc::time_point::now() < deadline, abi_serialization_deadline_exception, "serialization time limit ${t}us exceeded", ("t", max_serialization_time) ); + } + + fc::scoped_exit> abi_traverse_context::enter_scope() { + std::function callback = [old_recursion_depth=recursion_depth, this](){ + recursion_depth = old_recursion_depth; + }; + + ++recursion_depth; + EOS_ASSERT( recursion_depth < abi_serializer::max_recursion_depth, abi_recursion_depth_exception, + "recursive definition, max_recursion_depth ${r} ", ("r", abi_serializer::max_recursion_depth) ); + + EOS_ASSERT( fc::time_point::now() < deadline, abi_serialization_deadline_exception, + "serialization time limit ${t}us exceeded", ("t", max_serialization_time) ); + + return {std::move(callback)}; + } + + void abi_traverse_context_with_path::set_path_root( const type_name& type ) { + auto rtype = abis.resolve_type(type); + + if( abis.is_array(rtype) ) { + root_of_path = array_type_path_root{}; + } else { + auto itr1 = abis.structs.find(rtype); + if( itr1 != abis.structs.end() ) { + root_of_path = struct_type_path_root{ .struct_itr = itr1 }; + } else { + auto itr2 = abis.variants.find(rtype); + if( itr2 != abis.variants.end() ) { + root_of_path = variant_type_path_root{ .variant_itr = itr2 }; + } + } + } + } + + fc::scoped_exit> abi_traverse_context_with_path::push_to_path( const path_item& item ) { + std::function callback = [this](){ + EOS_ASSERT( path.size() > 0, abi_exception, + "invariant failure in variant_to_binary_context: path is empty on scope exit" ); + path.pop_back(); + }; + + path.push_back( item ); + + return {std::move(callback)}; + } + + void abi_traverse_context_with_path::set_array_index_of_path_back( uint32_t i ) { + EOS_ASSERT( path.size() > 0, abi_exception, "path is empty" ); + + auto& b = path.back(); + + EOS_ASSERT( b.contains(), abi_exception, "trying to set array index without first pushing new array index item" ); + + b.get().array_index = i; + } + + void abi_traverse_context_with_path::hint_array_type_if_in_array() { + if( path.size() == 0 || !path.back().contains() ) + return; + + path.back().get().type_hint = array_type_path_root{}; + } + + void abi_traverse_context_with_path::hint_struct_type_if_in_array( const map::const_iterator& itr ) { + if( path.size() == 0 || !path.back().contains() ) + return; + + path.back().get().type_hint = struct_type_path_root{ .struct_itr = itr }; + } + + void abi_traverse_context_with_path::hint_variant_type_if_in_array( const map::const_iterator& itr ) { + if( path.size() == 0 || !path.back().contains() ) + return; + + path.back().get().type_hint = variant_type_path_root{ .variant_itr = itr }; + } + + constexpr size_t const_strlen( const char* str ) + { + return (*str == 0) ? 0 : const_strlen(str + 1) + 1; + } + + void output_name( std::ostream& s, const string& str, bool shorten, size_t max_length = 64 ) { + constexpr size_t min_num_characters_at_ends = 4; + constexpr size_t preferred_num_tail_end_characters = 6; + constexpr const char* fill_in = "..."; + + static_assert( min_num_characters_at_ends <= preferred_num_tail_end_characters, + "preferred number of tail end characters cannot be less than the imposed absolute minimum" ); + + constexpr size_t fill_in_length = const_strlen( fill_in ); + constexpr size_t min_length = fill_in_length + 2*min_num_characters_at_ends; + constexpr size_t preferred_min_length = fill_in_length + 2*preferred_num_tail_end_characters; + + max_length = std::max( max_length, min_length ); + + if( !shorten || str.size() <= max_length ) { + s << str; + return; + } + + size_t actual_num_tail_end_characters = preferred_num_tail_end_characters; + if( max_length < preferred_min_length ) { + actual_num_tail_end_characters = min_num_characters_at_ends + (max_length - min_length)/2; + } + + s.write( str.data(), max_length - fill_in_length - actual_num_tail_end_characters ); + s.write( fill_in, fill_in_length ); + s.write( str.data() + (str.size() - actual_num_tail_end_characters), actual_num_tail_end_characters ); + } + + struct generate_path_string_visitor { + using result_type = void; + + generate_path_string_visitor( bool shorten_names, bool track_only ) + : shorten_names(shorten_names), track_only( track_only ) + {} + + std::stringstream s; + bool shorten_names = false; + bool track_only = false; + path_item last_non_array_path_item; + + void add_dot() { + s << "."; + } + + void operator()( const empty_path_item& item ) { + } + + void operator()( const array_index_path_item& item ) { + if( track_only ) { + last_non_array_path_item = item; // ????? + return; + } + + s << "[" << item.array_index << "]"; + } + + void operator()( const field_path_item& item ) { + if( track_only ) { + last_non_array_path_item = item; + return; + } + + const auto& str = item.parent_struct_itr->second.fields.at(item.field_ordinal).name; + output_name( s, str, shorten_names ); + } + + void operator()( const variant_path_item& item ) { + if( track_only ) { + last_non_array_path_item = item; + return; + } + + s << ""; + } + + void operator()( const empty_path_root& item ) { + } + + void operator()( const array_type_path_root& item ) { + s << "ARRAY"; + } + + void operator()( const struct_type_path_root& item ) { + const auto& str = item.struct_itr->first; + output_name( s, str, shorten_names ); + } + + void operator()( const variant_type_path_root& item ) { + const auto& str = item.variant_itr->first; + output_name( s, str, shorten_names ); + } + }; + + struct path_item_type_visitor { + using result_type = void; + + path_item_type_visitor( std::stringstream& s, bool shorten_names ) + : s(s), shorten_names(shorten_names) + {} + + std::stringstream& s; + bool shorten_names = false; + + void operator()( const empty_path_item& item ) { + } + + void operator()( const array_index_path_item& item ) { + const auto& th = item.type_hint; + if( th.contains() ) { + const auto& str = th.get().struct_itr->first; + output_name( s, str, shorten_names ); + } else if( th.contains() ) { + const auto& str = th.get().variant_itr->first; + output_name( s, str, shorten_names ); + } else if( th.contains() ) { + s << "ARRAY"; + } else { + s << "UNKNOWN"; + } + } + + void operator()( const field_path_item& item ) { + const auto& str = item.parent_struct_itr->second.fields.at(item.field_ordinal).type; + output_name( s, str, shorten_names ); + } + + void operator()( const variant_path_item& item ) { + const auto& str = item.variant_itr->second.types.at(item.variant_ordinal); + output_name( s, str, shorten_names ); + } + }; + + string abi_traverse_context_with_path::get_path_string()const { + bool full_path = !short_path; + bool shorten_names = short_path; + + generate_path_string_visitor visitor(shorten_names, !full_path); + if( full_path ) + root_of_path.visit( visitor ); + for( size_t i = 0, n = path.size(); i < n; ++i ) { + if( full_path && !path[i].contains() ) + visitor.add_dot(); + + path[i].visit( visitor ); + + } + + if( !full_path ) { + if( visitor.last_non_array_path_item.contains() ) { + root_of_path.visit( visitor ); + } else { + path_item_type_visitor vis2(visitor.s, shorten_names); + visitor.last_non_array_path_item.visit(vis2); + } + } + + return visitor.s.str(); + } + + string abi_traverse_context_with_path::maybe_shorten( const string& str ) { + if( !short_path ) + return str; + + std::stringstream s; + output_name( s, str, true ); + return s.str(); + } + + fc::scoped_exit> variant_to_binary_context::disallow_extensions_unless( bool condition ) { + std::function callback = [old_recursion_depth=recursion_depth, old_allow_extensions=allow_extensions, this](){ + allow_extensions = old_allow_extensions; + }; + + if( !condition ) { + allow_extensions = false; + } + + return {std::move(callback)}; + } + } + } } diff --git a/libraries/chain/include/eosio/chain/abi_serializer.hpp b/libraries/chain/include/eosio/chain/abi_serializer.hpp index 9682f0a150675de63be77bffb3bf5b950408f20e..c3e1669fb20a0c0db755492ad3331946a5149b0a 100644 --- a/libraries/chain/include/eosio/chain/abi_serializer.hpp +++ b/libraries/chain/include/eosio/chain/abi_serializer.hpp @@ -18,12 +18,13 @@ using std::pair; using namespace fc; namespace impl { - struct abi_from_variant; - struct abi_to_variant; + struct abi_from_variant; + struct abi_to_variant; - struct abi_traverse_context; - struct binary_to_variant_context; - struct variant_to_binary_context; + struct abi_traverse_context; + struct abi_traverse_context_with_path; + struct binary_to_variant_context; + struct variant_to_binary_context; } /** @@ -52,11 +53,11 @@ struct abi_serializer { optional get_error_message( uint64_t error_code )const; - fc::variant binary_to_variant(const type_name& type, const bytes& binary, const fc::microseconds& max_serialization_time)const; - fc::variant binary_to_variant(const type_name& type, fc::datastream& binary, const fc::microseconds& max_serialization_time)const; + fc::variant binary_to_variant( const type_name& type, const bytes& binary, const fc::microseconds& max_serialization_time )const; + fc::variant binary_to_variant( const type_name& type, fc::datastream& binary, const fc::microseconds& max_serialization_time )const; - bytes variant_to_binary(const type_name& type, const fc::variant& var, const fc::microseconds& max_serialization_time)const; - void variant_to_binary(const type_name& type, const fc::variant& var, fc::datastream& ds, const fc::microseconds& max_serialization_time)const; + bytes variant_to_binary( const type_name& type, const fc::variant& var, const fc::microseconds& max_serialization_time, bool short_path = false )const; + void variant_to_binary( const type_name& type, const fc::variant& var, fc::datastream& ds, const fc::microseconds& max_serialization_time, bool short_path = false )const; template static void to_variant( const T& o, fc::variant& vo, Resolver resolver, const fc::microseconds& max_serialization_time ); @@ -116,6 +117,7 @@ private: friend struct impl::abi_from_variant; friend struct impl::abi_to_variant; + friend struct impl::abi_traverse_context_with_path; }; namespace impl { @@ -129,24 +131,9 @@ namespace impl { : max_serialization_time( max_serialization_time ), deadline( deadline ), recursion_depth(0) {} - void check_deadline()const { - EOS_ASSERT( fc::time_point::now() < deadline, abi_serialization_deadline_exception, "serialization time limit ${t}us exceeded", ("t", max_serialization_time) ); - } - - fc::scoped_exit> enter_scope() { - std::function callback = [old_recursion_depth=recursion_depth, this](){ - recursion_depth = old_recursion_depth; - }; - - ++recursion_depth; - EOS_ASSERT( recursion_depth < abi_serializer::max_recursion_depth, abi_recursion_depth_exception, - "recursive definition, max_recursion_depth ${r} ", ("r", abi_serializer::max_recursion_depth) ); + void check_deadline()const; - EOS_ASSERT( fc::time_point::now() < deadline, abi_serialization_deadline_exception, - "serialization time limit ${t}us exceeded", ("t", max_serialization_time) ); - - return {std::move(callback)}; - } + fc::scoped_exit> enter_scope(); protected: fc::microseconds max_serialization_time; @@ -154,85 +141,93 @@ namespace impl { size_t recursion_depth; }; - struct binary_to_variant_context : public abi_traverse_context { - using abi_traverse_context::abi_traverse_context; + struct empty_path_root {}; - binary_to_variant_context( const abi_traverse_context& ctx ) - : abi_traverse_context(ctx) - {} + struct array_type_path_root { }; - struct variant_to_binary_context : public abi_traverse_context { - using abi_traverse_context::abi_traverse_context; + struct struct_type_path_root { + map::const_iterator struct_itr; + }; - variant_to_binary_context( const abi_traverse_context& ctx ) - : abi_traverse_context(ctx) - {} + struct variant_type_path_root { + map::const_iterator variant_itr; + }; - fc::scoped_exit> disallow_extensions_unless( bool condition ) { - std::function callback = [old_recursion_depth=recursion_depth, old_allow_extensions=allow_extensions, this](){ - allow_extensions = old_allow_extensions; - }; + using path_root = static_variant; - if( !condition ) { - allow_extensions = false; - } + struct empty_path_item {}; - return {std::move(callback)}; - } + struct array_index_path_item { + path_root type_hint; + uint32_t array_index = 0; + }; - fc::scoped_exit> push_to_path( const string& n, bool condition = true ) { + struct field_path_item { + map::const_iterator parent_struct_itr; + uint32_t field_ordinal = 0; + }; - if( !condition ) { - fc::scoped_exit> h([](){}); - h.cancel(); - return h; - } + struct variant_path_item { + map::const_iterator variant_itr; + uint32_t variant_ordinal = 0; + }; - std::function callback = [this](){ - EOS_ASSERT( path.size() > 0 && path_array_index.size() > 0, abi_exception, - "invariant failure in variant_to_binary_context: path is empty on scope exit" ); - path.pop_back(); - path_array_index.pop_back(); - }; + using path_item = static_variant; - path.push_back( n ); - path_array_index.push_back( -1 ); + struct abi_traverse_context_with_path : public abi_traverse_context { + abi_traverse_context_with_path( const abi_serializer& abis, fc::microseconds max_serialization_time, const type_name& type ) + : abi_traverse_context( max_serialization_time ), abis(abis) + { + set_path_root(type); + } - return {std::move(callback)}; + abi_traverse_context_with_path( const abi_serializer& abis, fc::microseconds max_serialization_time, fc::time_point deadline, const type_name& type ) + : abi_traverse_context( max_serialization_time, deadline ), abis(abis) + { + set_path_root(type); } - void set_array_index_of_path_back( int64_t i ) { - EOS_ASSERT( path_array_index.size() > 0, abi_exception, "path is empty" ); - path_array_index.back() = i; + abi_traverse_context_with_path( const abi_serializer& abis, const abi_traverse_context& ctx, const type_name& type ) + : abi_traverse_context(ctx), abis(abis) + { + set_path_root(type); } - bool extensions_allowed()const { return allow_extensions; } + void set_path_root( const type_name& type ); - bool is_path_empty()const { return path.size() == 0; } + fc::scoped_exit> push_to_path( const path_item& item ); - string get_path_string()const { - EOS_ASSERT( path.size() == path_array_index.size(), abi_exception, - "invariant failure in variant_to_binary_context: mismatch in path vector sizes" ); + void set_array_index_of_path_back( uint32_t i ); + void hint_array_type_if_in_array(); + void hint_struct_type_if_in_array( const map::const_iterator& itr ); + void hint_variant_type_if_in_array( const map::const_iterator& itr ); - std::stringstream s; - for( size_t i = 0, n = path.size(); i < n; ++i ) { - s << path[i]; - if( path_array_index[i] >= 0 ) { - s << "[" << path_array_index[i] << "]"; - } - if( (i + 1) != n ) { // if not the last element in the path - s << "."; - } - } + string get_path_string()const; - return s.str(); - } + string maybe_shorten( const string& str ); + + protected: + const abi_serializer& abis; + path_root root_of_path; + vector path; + public: + bool short_path = false; + }; + + struct binary_to_variant_context : public abi_traverse_context_with_path { + using abi_traverse_context_with_path::abi_traverse_context_with_path; + }; + + struct variant_to_binary_context : public abi_traverse_context_with_path { + using abi_traverse_context_with_path::abi_traverse_context_with_path; + + fc::scoped_exit> disallow_extensions_unless( bool condition ); + + bool extensions_allowed()const { return allow_extensions; } protected: - bool allow_extensions = true; - vector path; - vector path_array_index; + bool allow_extensions = true; }; /** @@ -389,7 +384,7 @@ namespace impl { auto type = abi->get_action_type(act.name); if (!type.empty()) { try { - binary_to_variant_context _ctx(ctx); + binary_to_variant_context _ctx(*abi, ctx, type); mvo( "data", abi->_binary_to_variant( type, act.data, _ctx )); mvo("hex_data", act.data); } catch(...) { @@ -552,7 +547,7 @@ namespace impl { if (abi.valid()) { auto type = abi->get_action_type(act.name); if (!type.empty()) { - variant_to_binary_context _ctx(ctx); + variant_to_binary_context _ctx(*abi, ctx, type); act.data = std::move( abi->_variant_to_binary( type, data, _ctx )); valid_empty_data = act.data.empty(); } diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 183e7a48ecc25a591cb34c511cc5de9c9bad4cfa..cff24929a6d151d02d094b4591a46e6b1f1a41a6 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1727,7 +1727,7 @@ read_only::abi_json_to_bin_result read_only::abi_json_to_bin( const read_only::a auto action_type = abis.get_action_type(params.action); EOS_ASSERT(!action_type.empty(), action_validate_exception, "Unknown action ${action} in contract ${contract}", ("action", params.action)("contract", params.code)); try { - result.binargs = abis.variant_to_binary(action_type, params.args, abi_serializer_max_time); + result.binargs = abis.variant_to_binary(action_type, params.args, abi_serializer_max_time, true); // TODO: Allow configuration to output verbose error messages } EOS_RETHROW_EXCEPTIONS(chain::invalid_action_args_exception, "'${args}' is invalid args for action '${action}' code '${code}'. expected '${proto}'", ("args", params.args)("action", params.action)("code", params.code)("proto", action_abi_to_variant(abi, action_type))) diff --git a/unittests/abi_tests.cpp b/unittests/abi_tests.cpp index f2f415825c97241fe1118aaa3177467068f8d948..dacce90cc744b65596552a1ab58ff6efa6005747 100644 --- a/unittests/abi_tests.cpp +++ b/unittests/abi_tests.cpp @@ -3761,6 +3761,8 @@ BOOST_AUTO_TEST_CASE(abi_serialize_detailed_error_messages) "types": [ {"new_type_name": "foo", "type": "s2"}, {"new_type_name": "bar", "type": "foo"}, + {"new_type_name": "s1array", "type": "s1[]"}, + {"new_type_name": "s1arrayarray", "type": "s1array[]"} ], "structs": [ {"name": "s1", "base": "", "fields": [ @@ -3781,6 +3783,9 @@ BOOST_AUTO_TEST_CASE(abi_serialize_detailed_error_messages) {"name": "f0", "type": "int8[]"}, {"name": "f1", "type": "s1[]"} ]}, + {"name": "s5", "base": "", "fields": [ + {"name": "f0", "type": "v2[]"}, + ]}, ], "variants": [ {"name": "v1", "types": ["s3", "int8", "s4"]}, @@ -3812,7 +3817,95 @@ BOOST_AUTO_TEST_CASE(abi_serialize_detailed_error_messages) pack_exception, fc_exception_message_is("Missing field 'i0' in input object while processing struct 'v1..f1[1]'") ); BOOST_CHECK_EXCEPTION( abis.variant_to_binary("s2[]", fc::json::from_string(R"([{"f0":{"i0":1,"i1":2},"i2":3},{"f0":{"i0":4},"i2":6}])"), max_serialization_time), - pack_exception, fc_exception_message_is("Missing field 'i1' in input object while processing struct 's2[1].f0'") ); + pack_exception, fc_exception_message_is("Missing field 'i1' in input object while processing struct 'ARRAY[1].f0'") ); + + BOOST_CHECK_EXCEPTION( abis.variant_to_binary("s5", fc::json::from_string(R"({"f0":[["bar",{"f0":{"i0":1,"i1":2},"i2":3}],["foo",{"f0":{"i0":4},"i2":6}]]})"), max_serialization_time), + pack_exception, fc_exception_message_is("Missing field 'i1' in input object while processing struct 's5.f0[1]..f0'") ); + + verify_round_trip_conversion( abis, "s1arrayarray", R"([[{"i0":1,"i1":2},{"i0":3,"i1":4}],[{"i0":5,"i1":6},{"i0":7,"i1":8},{"i0":9,"i1":10}]])", "0202010203040305060708090a"); + + BOOST_CHECK_EXCEPTION( abis.variant_to_binary("s1arrayarray", fc::json::from_string(R"([[{"i0":1,"i1":2},{"i0":3,"i1":4}],[{"i0":6,"i1":6},{"i0":7,"i1":8},{"i1":10}]])"), max_serialization_time), + pack_exception, fc_exception_message_is("Missing field 'i0' in input object while processing struct 'ARRAY[1][2]'") ); + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(abi_serialize_short_error_messages) +{ + using eosio::testing::fc_exception_message_is; + + auto abi = R"({ + "version": "eosio::abi/1.1", + "types": [ + {"new_type_name": "foo", "type": "s2"}, + {"new_type_name": "bar", "type": "foo"}, + {"new_type_name": "s1array", "type": "s1[]"}, + {"new_type_name": "s1arrayarray", "type": "s1array[]"} + ], + "structs": [ + {"name": "s1", "base": "", "fields": [ + {"name": "i0", "type": "int8"}, + {"name": "i1", "type": "int8"} + ]}, + {"name": "s2", "base": "", "fields": [ + {"name": "f0", "type": "s1"}, + {"name": "i2", "type": "int8"} + ]}, + {"name": "very_very_very_very_very_very_very_very_very_very_long_struct_name_s3", "base": "s1", "fields": [ + {"name": "i2", "type": "int8"}, + {"name": "f3", "type": "v2"}, + {"name": "f4", "type": "foo$"}, + {"name": "very_very_very_very_very_very_very_very_very_very_long_field_name_f5", "type": "s1$"} + ]}, + {"name": "s4", "base": "", "fields": [ + {"name": "f0", "type": "int8[]"}, + {"name": "f1", "type": "s1[]"} + ]}, + {"name": "s5", "base": "", "fields": [ + {"name": "f0", "type": "v2[]"}, + ]}, + ], + "variants": [ + {"name": "v1", "types": ["very_very_very_very_very_very_very_very_very_very_long_struct_name_s3", "int8", "s4"]}, + {"name": "v2", "types": ["foo", "bar"]}, + ], + })"; + + try { + abi_serializer abis( fc::json::from_string(abi).as(), max_serialization_time ); + + BOOST_CHECK_EXCEPTION( abis.variant_to_binary("bar", fc::json::from_string(R"({"f0":{"i0":1},"i2":3})"), max_serialization_time, true), + pack_exception, fc_exception_message_is("Missing field 'i1' in input object while processing struct 's1'") ); + + BOOST_CHECK_EXCEPTION( abis.variant_to_binary( "very_very_very_very_very_very_very_very_very_very_long_struct_name_s3", + fc::json::from_string(R"({"i0":1,"i2":3})"), max_serialization_time, true ), + pack_exception, + fc_exception_message_is("Missing field 'i1' in input object while processing struct 'very_very_very_very_very_very_very_very_very_very_long_...ame_s3'") ); + + BOOST_CHECK_EXCEPTION( abis.variant_to_binary( "very_very_very_very_very_very_very_very_very_very_long_struct_name_s3", + fc::json::from_string(R"({"i0":1,"i1":2,"i2":3,"f3":["s2",{}]})"), max_serialization_time, true ), + pack_exception, fc_exception_message_is("Specified type 's2' in input array is not valid within the variant 'v2'") ); + + BOOST_CHECK_EXCEPTION( abis.variant_to_binary( "very_very_very_very_very_very_very_very_very_very_long_struct_name_s3", + fc::json::from_string(R"({"i0":1,"i1":2,"i2":3,"f3":["bar",{"f0":{"i0":11},"i2":13}]})"), max_serialization_time, true ), + pack_exception, fc_exception_message_is("Missing field 'i1' in input object while processing struct 's1'") ); + + BOOST_CHECK_EXCEPTION( abis.variant_to_binary( "v1", + fc::json::from_string(R"(["very_very_very_very_very_very_very_very_very_very_long_struct_name_s3",{"i0":1,"i1":2,"i2":3,"f3":["bar",{"f0":{"i0":11,"i1":12},"i2":13}],"very_very_very_very_very_very_very_very_very_very_long_field_name_f5":0}])"), + max_serialization_time, true ), + pack_exception, + fc_exception_message_is("Unexpected field 'very_very_very_very_very_very_very_very_very_very_long_...ame_f5' found in input object while processing struct 'very_very_very_very_very_very_very_very_very_very_long_...ame_s3'") ); + + BOOST_CHECK_EXCEPTION( abis.variant_to_binary("v1", fc::json::from_string(R"(["s4",{"f0":[0,1],"f1":[{"i0":2,"i1":3},{"i1":5}]}])"), max_serialization_time, true), + pack_exception, fc_exception_message_is("Missing field 'i0' in input object while processing struct 's1'") ); + + BOOST_CHECK_EXCEPTION( abis.variant_to_binary("s2[]", fc::json::from_string(R"([{"f0":{"i0":1,"i1":2},"i2":3},{"f0":{"i0":4},"i2":6}])"), max_serialization_time, true), + pack_exception, fc_exception_message_is("Missing field 'i1' in input object while processing struct 's1'") ); + + BOOST_CHECK_EXCEPTION( abis.variant_to_binary("s5", fc::json::from_string(R"({"f0":[["bar",{"f0":{"i0":1,"i1":2},"i2":3}],["foo",{"f0":{"i0":4},"i2":6}]]})"), max_serialization_time, true), + pack_exception, fc_exception_message_is("Missing field 'i1' in input object while processing struct 's1'") ); + + BOOST_CHECK_EXCEPTION( abis.variant_to_binary("s1arrayarray", fc::json::from_string(R"([[{"i0":1,"i1":2},{"i0":3,"i1":4}],[{"i0":6,"i1":6},{"i0":7,"i1":8},{"i1":10}]])"), max_serialization_time, true), + pack_exception, fc_exception_message_is("Missing field 'i0' in input object while processing struct 's1'") ); } FC_LOG_AND_RETHROW() }