未验证 提交 905e7c85 编写于 作者: W wanderingbort 提交者: GitHub

Merge pull request #4339 from EOSIO/feature/1.0.7-security-omnibus

Consolidated Security Fixes for 1.0.7
...@@ -63,6 +63,7 @@ tests/plugin_test ...@@ -63,6 +63,7 @@ tests/plugin_test
unittests/unit_test unittests/unit_test
doxygen doxygen
eos.doxygen
wallet.json wallet.json
witness_node_data_dir witness_node_data_dir
......
...@@ -16,6 +16,9 @@ using namespace boost; ...@@ -16,6 +16,9 @@ using namespace boost;
namespace eosio { namespace chain { namespace eosio { namespace chain {
const size_t abi_serializer::max_recursion_depth;
fc::microseconds abi_serializer::max_serialization_time = fc::microseconds(15*1000); // 15 ms
using boost::algorithm::ends_with; using boost::algorithm::ends_with;
using std::string; using std::string;
...@@ -238,21 +241,26 @@ namespace eosio { namespace chain { ...@@ -238,21 +241,26 @@ namespace eosio { namespace chain {
return type; return type;
} }
void abi_serializer::_binary_to_variant(const type_name& type, fc::datastream<const char *>& stream, void abi_serializer::_binary_to_variant( const type_name& type, fc::datastream<const char *>& stream,
fc::mutable_variant_object& obj, size_t recursion_depth)const { fc::mutable_variant_object& obj, size_t recursion_depth,
FC_ASSERT( ++recursion_depth < max_recursion_depth, "recursive definition, max_recursion_depth" ); const fc::time_point& deadline )const
{
FC_ASSERT( ++recursion_depth < max_recursion_depth, "recursive definition, max_recursion_depth ${r} ", ("r", max_recursion_depth) );
FC_ASSERT( fc::time_point::now() < deadline, "serialization time limit ${t}us exceeded", ("t", max_serialization_time) );
const auto& st = get_struct(type); const auto& st = get_struct(type);
if( st.base != type_name() ) { if( st.base != type_name() ) {
_binary_to_variant(resolve_type(st.base), stream, obj, recursion_depth); _binary_to_variant(resolve_type(st.base), stream, obj, recursion_depth, deadline);
} }
for( const auto& field : st.fields ) { for( const auto& field : st.fields ) {
obj( field.name, _binary_to_variant(resolve_type(field.type), stream, recursion_depth) ); obj( field.name, _binary_to_variant(resolve_type(field.type), stream, recursion_depth, deadline) );
} }
} }
fc::variant abi_serializer::_binary_to_variant(const type_name& type, fc::datastream<const char *>& stream, size_t recursion_depth)const fc::variant abi_serializer::_binary_to_variant( const type_name& type, fc::datastream<const char *>& stream,
size_t recursion_depth, const fc::time_point& deadline )const
{ {
FC_ASSERT( ++recursion_depth < max_recursion_depth, "recursive definition, max_recursion_depth" ); FC_ASSERT( ++recursion_depth < max_recursion_depth, "recursive definition, max_recursion_depth ${r} ", ("r", max_recursion_depth) );
FC_ASSERT( fc::time_point::now() < deadline, "serialization time limit ${t}us exceeded", ("t", max_serialization_time) );
type_name rtype = resolve_type(type); type_name rtype = resolve_type(type);
auto ftype = fundamental_type(rtype); auto ftype = fundamental_type(rtype);
auto btype = built_in_types.find(ftype ); auto btype = built_in_types.find(ftype );
...@@ -263,31 +271,41 @@ namespace eosio { namespace chain { ...@@ -263,31 +271,41 @@ namespace eosio { namespace chain {
fc::unsigned_int size; fc::unsigned_int size;
fc::raw::unpack(stream, size); fc::raw::unpack(stream, size);
vector<fc::variant> vars; vector<fc::variant> vars;
vars.resize(size); for( decltype(size.value) i = 0; i < size; ++i ) {
for (auto& var : vars) { auto v = _binary_to_variant(ftype, stream, recursion_depth, deadline);
var = _binary_to_variant(ftype, stream, recursion_depth); FC_ASSERT( !v.is_null(), "Invalid packed array" );
vars.emplace_back(std::move(v));
} }
FC_ASSERT( vars.size() == size.value,
"packed size does not match unpacked array size, packed size ${p} actual size ${a}",
("p", size)("a", vars.size()) );
return fc::variant( std::move(vars) ); return fc::variant( std::move(vars) );
} else if ( is_optional(rtype) ) { } else if ( is_optional(rtype) ) {
char flag; char flag;
fc::raw::unpack(stream, flag); fc::raw::unpack(stream, flag);
return flag ? _binary_to_variant(ftype, stream, recursion_depth) : fc::variant(); return flag ? _binary_to_variant(ftype, stream, recursion_depth, deadline) : fc::variant();
} }
fc::mutable_variant_object mvo; fc::mutable_variant_object mvo;
_binary_to_variant(rtype, stream, mvo, recursion_depth); _binary_to_variant(rtype, stream, mvo, recursion_depth, deadline);
FC_ASSERT( mvo.size() > 0, "Unable to unpack stream ${type}", ("type", type) );
return fc::variant( std::move(mvo) ); return fc::variant( std::move(mvo) );
} }
fc::variant abi_serializer::_binary_to_variant(const type_name& type, const bytes& binary, size_t recursion_depth)const{ fc::variant abi_serializer::_binary_to_variant( const type_name& type, const bytes& binary,
FC_ASSERT( ++recursion_depth < max_recursion_depth, "recursive definition, max_recursion_depth" ); size_t recursion_depth, const fc::time_point& deadline )const
{
FC_ASSERT( ++recursion_depth < max_recursion_depth, "recursive definition, max_recursion_depth ${r} ", ("r", max_recursion_depth) );
FC_ASSERT( fc::time_point::now() < deadline, "serialization time limit ${t}us exceeded", ("t", max_serialization_time) );
fc::datastream<const char*> ds( binary.data(), binary.size() ); fc::datastream<const char*> ds( binary.data(), binary.size() );
return _binary_to_variant(type, ds, recursion_depth); return _binary_to_variant(type, ds, recursion_depth, deadline);
} }
void abi_serializer::_variant_to_binary(const type_name& type, const fc::variant& var, fc::datastream<char *>& ds, size_t recursion_depth)const void abi_serializer::_variant_to_binary( const type_name& type, const fc::variant& var, fc::datastream<char *>& ds,
size_t recursion_depth, const fc::time_point& deadline )const
{ try { { try {
FC_ASSERT( ++recursion_depth < max_recursion_depth, "recursive definition, max_recursion_depth" ); FC_ASSERT( ++recursion_depth < max_recursion_depth, "recursive definition, max_recursion_depth ${r} ", ("r", max_recursion_depth) );
FC_ASSERT( fc::time_point::now() < deadline, "serialization time limit ${t}us exceeded", ("t", max_serialization_time) );
auto rtype = resolve_type(type); auto rtype = resolve_type(type);
auto btype = built_in_types.find(fundamental_type(rtype)); auto btype = built_in_types.find(fundamental_type(rtype));
...@@ -297,7 +315,7 @@ namespace eosio { namespace chain { ...@@ -297,7 +315,7 @@ namespace eosio { namespace chain {
vector<fc::variant> vars = var.get_array(); vector<fc::variant> vars = var.get_array();
fc::raw::pack(ds, (fc::unsigned_int)vars.size()); fc::raw::pack(ds, (fc::unsigned_int)vars.size());
for (const auto& var : vars) { for (const auto& var : vars) {
_variant_to_binary(fundamental_type(rtype), var, ds, recursion_depth); _variant_to_binary(fundamental_type(rtype), var, ds, recursion_depth, deadline);
} }
} else { } else {
const auto& st = get_struct(rtype); const auto& st = get_struct(rtype);
...@@ -306,14 +324,14 @@ namespace eosio { namespace chain { ...@@ -306,14 +324,14 @@ namespace eosio { namespace chain {
const auto& vo = var.get_object(); const auto& vo = var.get_object();
if( st.base != type_name() ) { if( st.base != type_name() ) {
_variant_to_binary(resolve_type(st.base), var, ds, recursion_depth); _variant_to_binary(resolve_type(st.base), var, ds, recursion_depth, deadline);
} }
for( const auto& field : st.fields ) { for( const auto& field : st.fields ) {
if( vo.contains( string(field.name).c_str() ) ) { if( vo.contains( string(field.name).c_str() ) ) {
_variant_to_binary(field.type, vo[field.name], ds, recursion_depth); _variant_to_binary(field.type, vo[field.name], ds, recursion_depth, deadline);
} }
else { else {
_variant_to_binary(field.type, fc::variant(), ds, recursion_depth); _variant_to_binary(field.type, fc::variant(), ds, recursion_depth, deadline);
/// TODO: default construct field and write it out /// TODO: default construct field and write it out
FC_THROW( "Missing '${f}' in variant object", ("f",field.name) ); FC_THROW( "Missing '${f}' in variant object", ("f",field.name) );
} }
...@@ -330,9 +348,9 @@ namespace eosio { namespace chain { ...@@ -330,9 +348,9 @@ namespace eosio { namespace chain {
if (va.size() > 0) { if (va.size() > 0) {
for( const auto& field : st.fields ) { for( const auto& field : st.fields ) {
if( va.size() > i ) if( va.size() > i )
_variant_to_binary(field.type, va[i], ds, recursion_depth); _variant_to_binary(field.type, va[i], ds, recursion_depth, deadline);
else else
_variant_to_binary(field.type, fc::variant(), ds, recursion_depth); _variant_to_binary(field.type, fc::variant(), ds, recursion_depth, deadline);
++i; ++i;
} }
} }
...@@ -340,15 +358,18 @@ namespace eosio { namespace chain { ...@@ -340,15 +358,18 @@ namespace eosio { namespace chain {
} }
} FC_CAPTURE_AND_RETHROW( (type)(var) ) } } FC_CAPTURE_AND_RETHROW( (type)(var) ) }
bytes abi_serializer::_variant_to_binary(const type_name& type, const fc::variant& var, size_t recursion_depth)const { try { bytes abi_serializer::_variant_to_binary( const type_name& type, const fc::variant& var,
FC_ASSERT( ++recursion_depth < max_recursion_depth, "recursive definition, max_recursion_depth" ); size_t recursion_depth, const fc::time_point& deadline )const
{ try {
FC_ASSERT( ++recursion_depth < max_recursion_depth, "recursive definition, max_recursion_depth ${r} ", ("r", max_recursion_depth) );
FC_ASSERT( fc::time_point::now() < deadline, "serialization time limit ${t}us exceeded", ("t", max_serialization_time) );
if( !is_type(type) ) { if( !is_type(type) ) {
return var.as<bytes>(); return var.as<bytes>();
} }
bytes temp( 1024*1024 ); bytes temp( 1024*1024 );
fc::datastream<char*> ds(temp.data(), temp.size() ); fc::datastream<char*> ds(temp.data(), temp.size() );
_variant_to_binary(type, var, ds, recursion_depth); _variant_to_binary(type, var, ds, recursion_depth, deadline);
temp.resize(ds.tellp()); temp.resize(ds.tellp());
return temp; return temp;
} FC_CAPTURE_AND_RETHROW( (type)(var) ) } } FC_CAPTURE_AND_RETHROW( (type)(var) ) }
......
...@@ -57,6 +57,7 @@ struct controller_impl { ...@@ -57,6 +57,7 @@ struct controller_impl {
controller::config conf; controller::config conf;
chain_id_type chain_id; chain_id_type chain_id;
bool replaying = false; bool replaying = false;
bool in_trx_requiring_checks = false; ///< if true, checks that are normally skipped on replay (e.g. auth checks) cannot be skipped
typedef pair<scope_name,action_name> handler_key; typedef pair<scope_name,action_name> handler_key;
map< account_name, map<handler_key, apply_handler> > apply_handlers; map< account_name, map<handler_key, apply_handler> > apply_handlers;
...@@ -532,6 +533,11 @@ struct controller_impl { ...@@ -532,6 +533,11 @@ struct controller_impl {
signed_transaction dtrx; signed_transaction dtrx;
fc::raw::unpack(ds,static_cast<transaction&>(dtrx) ); fc::raw::unpack(ds,static_cast<transaction&>(dtrx) );
auto reset_in_trx_requiring_checks = fc::make_scoped_exit([old_value=in_trx_requiring_checks,this](){
in_trx_requiring_checks = old_value;
});
in_trx_requiring_checks = true;
transaction_context trx_context( self, dtrx, gto.trx_id ); transaction_context trx_context( self, dtrx, gto.trx_id );
trx_context.deadline = deadline; trx_context.deadline = deadline;
trx_context.billed_cpu_time_us = billed_cpu_time_us; trx_context.billed_cpu_time_us = billed_cpu_time_us;
...@@ -760,6 +766,10 @@ struct controller_impl { ...@@ -760,6 +766,10 @@ struct controller_impl {
try { try {
auto onbtrx = std::make_shared<transaction_metadata>( get_on_block_transaction() ); auto onbtrx = std::make_shared<transaction_metadata>( get_on_block_transaction() );
auto reset_in_trx_requiring_checks = fc::make_scoped_exit([old_value=in_trx_requiring_checks,this](){
in_trx_requiring_checks = old_value;
});
in_trx_requiring_checks = true;
push_transaction( onbtrx, fc::time_point::maximum(), true, self.get_global_properties().configuration.min_transaction_cpu_usage ); push_transaction( onbtrx, fc::time_point::maximum(), true, self.get_global_properties().configuration.min_transaction_cpu_usage );
} catch( const boost::interprocess::bad_alloc& e ) { } catch( const boost::interprocess::bad_alloc& e ) {
elog( "on block transaction failed due to a bad allocation" ); elog( "on block transaction failed due to a bad allocation" );
...@@ -1395,7 +1405,7 @@ optional<producer_schedule_type> controller::proposed_producers()const { ...@@ -1395,7 +1405,7 @@ optional<producer_schedule_type> controller::proposed_producers()const {
} }
bool controller::skip_auth_check()const { bool controller::skip_auth_check()const {
return my->replaying && !my->conf.force_all_checks; return my->replaying && !my->conf.force_all_checks && !my->in_trx_requiring_checks;
} }
bool controller::contracts_console()const { bool controller::contracts_console()const {
......
...@@ -46,6 +46,8 @@ namespace eosio { namespace testing { ...@@ -46,6 +46,8 @@ namespace eosio { namespace testing {
cfg.genesis.initial_timestamp = fc::time_point::from_iso_string("2020-01-01T00:00:00.000"); cfg.genesis.initial_timestamp = fc::time_point::from_iso_string("2020-01-01T00:00:00.000");
cfg.genesis.initial_key = get_public_key( config::system_account_name, "active" ); cfg.genesis.initial_key = get_public_key( config::system_account_name, "active" );
abi_serializer::set_max_serialization_time(fc::microseconds(100*1000)); // 100ms for slow test machines
for(int i = 0; i < boost::unit_test::framework::master_test_suite().argc; ++i) { for(int i = 0; i < boost::unit_test::framework::master_test_suite().argc; ++i) {
if(boost::unit_test::framework::master_test_suite().argv[i] == std::string("--binaryen")) if(boost::unit_test::framework::master_test_suite().argv[i] == std::string("--binaryen"))
cfg.wasm_runtime = chain::wasm_interface::vm_type::binaryen; cfg.wasm_runtime = chain::wasm_interface::vm_type::binaryen;
......
...@@ -493,15 +493,16 @@ namespace WASM ...@@ -493,15 +493,16 @@ namespace WASM
if (numBodyBytes >= max_size) if (numBodyBytes >= max_size)
throw FatalSerializationException(std::string("Function body too large")); throw FatalSerializationException(std::string("Function body too large"));
if (numLocalSets >= 1024) if (numLocalSets >= 1024)
throw FatalSerializationException(std::string("too many locals")); throw FatalSerializationException(std::string("too many local sets"));
size_t locals_accum = numBodyBytes;
for(Uptr setIndex = 0;setIndex < numLocalSets;++setIndex) for(Uptr setIndex = 0;setIndex < numLocalSets;++setIndex)
{ {
LocalSet localSet; LocalSet localSet;
serialize(bodyStream,localSet); serialize(bodyStream,localSet);
locals_accum += localSet.num*4;
if( localSet.num > 8024 ) if( locals_accum >= max_size )
throw FatalSerializationException( "localSet.num too large" ); throw FatalSerializationException( "too many locals" );
for(Uptr index = 0;index < localSet.num;++index) { functionDef.nonParameterLocalTypes.push_back(localSet.type); } for(Uptr index = 0;index < localSet.num;++index) { functionDef.nonParameterLocalTypes.push_back(localSet.type); }
} }
......
...@@ -128,6 +128,7 @@ void chain_plugin::set_program_options(options_description& cli, options_descrip ...@@ -128,6 +128,7 @@ void chain_plugin::set_program_options(options_description& cli, options_descrip
"the location of the blocks directory (absolute path or relative to application data dir)") "the location of the blocks directory (absolute path or relative to application data dir)")
("checkpoint", bpo::value<vector<string>>()->composing(), "Pairs of [BLOCK_NUM,BLOCK_ID] that should be enforced as checkpoints.") ("checkpoint", bpo::value<vector<string>>()->composing(), "Pairs of [BLOCK_NUM,BLOCK_ID] that should be enforced as checkpoints.")
("wasm-runtime", bpo::value<eosio::chain::wasm_interface::vm_type>()->value_name("wavm/binaryen"), "Override default WASM runtime") ("wasm-runtime", bpo::value<eosio::chain::wasm_interface::vm_type>()->value_name("wavm/binaryen"), "Override default WASM runtime")
("abi-serializer-max-time-ms", bpo::value<uint32_t>(), "Override default maximum ABI serialization time allowed in ms")
("chain-state-db-size-mb", bpo::value<uint64_t>()->default_value(config::default_state_size / (1024 * 1024)), "Maximum size (in MB) of the chain state database") ("chain-state-db-size-mb", bpo::value<uint64_t>()->default_value(config::default_state_size / (1024 * 1024)), "Maximum size (in MB) of the chain state database")
("reversible-blocks-db-size-mb", bpo::value<uint64_t>()->default_value(config::default_reversible_cache_size / (1024 * 1024)), "Maximum size (in MB) of the reversible blocks database") ("reversible-blocks-db-size-mb", bpo::value<uint64_t>()->default_value(config::default_reversible_cache_size / (1024 * 1024)), "Maximum size (in MB) of the reversible blocks database")
("contracts-console", bpo::bool_switch()->default_value(false), ("contracts-console", bpo::bool_switch()->default_value(false),
...@@ -265,6 +266,9 @@ void chain_plugin::plugin_initialize(const variables_map& options) { ...@@ -265,6 +266,9 @@ void chain_plugin::plugin_initialize(const variables_map& options) {
if(options.count("wasm-runtime")) if(options.count("wasm-runtime"))
my->wasm_runtime = options.at("wasm-runtime").as<vm_type>(); my->wasm_runtime = options.at("wasm-runtime").as<vm_type>();
if(options.count("abi-serializer-max-time-ms"))
abi_serializer::set_max_serialization_time(fc::microseconds(options.at("abi-serializer-max-time-ms").as<uint32_t>() * 1000));
my->chain_config->blocks_dir = my->blocks_dir; my->chain_config->blocks_dir = my->blocks_dir;
my->chain_config->state_dir = app().data_dir() / config::default_state_dir_name; my->chain_config->state_dir = app().data_dir() / config::default_state_dir_name;
my->chain_config->read_only = my->readonly; my->chain_config->read_only = my->readonly;
......
...@@ -1865,7 +1865,8 @@ namespace eosio { ...@@ -1865,7 +1865,8 @@ namespace eosio {
peer_block_state entry = {blkid,0,true,true,fc::time_point()}; peer_block_state entry = {blkid,0,true,true,fc::time_point()};
try { try {
b = cc.fetch_block_by_id(blkid); b = cc.fetch_block_by_id(blkid);
entry.block_num = b->block_num(); if(b)
entry.block_num = b->block_num();
} catch (const assert_exception &ex) { } catch (const assert_exception &ex) {
ilog( "caught assert on fetch_block_by_id, ${ex}",("ex",ex.what())); ilog( "caught assert on fetch_block_by_id, ${ex}",("ex",ex.what()));
// keep going, client can ask another peer // keep going, client can ask another peer
......
...@@ -354,7 +354,7 @@ struct txn_test_gen_plugin_impl { ...@@ -354,7 +354,7 @@ struct txn_test_gen_plugin_impl {
int32_t txn_reference_block_lag; int32_t txn_reference_block_lag;
abi_serializer eosio_token_serializer = fc::json::from_string(eosio_token_abi).as<abi_def>(); abi_serializer eosio_token_serializer{fc::json::from_string(eosio_token_abi).as<abi_def>()};
}; };
txn_test_gen_plugin::txn_test_gen_plugin() {} txn_test_gen_plugin::txn_test_gen_plugin() {}
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include <fc/io/json.hpp> #include <fc/io/json.hpp>
#include <fc/exception/exception.hpp> #include <fc/exception/exception.hpp>
#include <fc/log/logger.hpp> #include <fc/log/logger.hpp>
#include <fc/scoped_exit.hpp>
#include <eosio/chain/contract_types.hpp> #include <eosio/chain/contract_types.hpp>
#include <eosio/chain/abi_serializer.hpp> #include <eosio/chain/abi_serializer.hpp>
...@@ -512,7 +513,7 @@ BOOST_AUTO_TEST_CASE(uint_types) ...@@ -512,7 +513,7 @@ BOOST_AUTO_TEST_CASE(uint_types)
auto var = fc::json::from_string(test_data); auto var = fc::json::from_string(test_data);
verify_byte_round_trip_conversion(abi, "transfer", var); verify_byte_round_trip_conversion(abi_serializer{abi}, "transfer", var);
} FC_LOG_AND_RETHROW() } } FC_LOG_AND_RETHROW() }
...@@ -2000,7 +2001,7 @@ BOOST_AUTO_TEST_CASE(general) ...@@ -2000,7 +2001,7 @@ BOOST_AUTO_TEST_CASE(general)
)====="; )=====";
auto var = fc::json::from_string(my_other); auto var = fc::json::from_string(my_other);
verify_byte_round_trip_conversion(abi, "A", var); verify_byte_round_trip_conversion(abi_serializer{abi}, "A", var);
} FC_LOG_AND_RETHROW() } } FC_LOG_AND_RETHROW() }
...@@ -3300,4 +3301,107 @@ BOOST_AUTO_TEST_CASE(abi_account_name_in_eosio_abi) ...@@ -3300,4 +3301,107 @@ BOOST_AUTO_TEST_CASE(abi_account_name_in_eosio_abi)
} FC_LOG_AND_RETHROW() } } FC_LOG_AND_RETHROW() }
// Infinite recursion of abi_serializer is_type
BOOST_AUTO_TEST_CASE(abi_is_type_recursion)
{
try {
const char* abi_str = R"=====(
{
"types": [
{
"new_type_name": "a[]",
"type": "a[][]",
},
],
"structs": [
{
"name": "a[]",
"base": "",
"fields": []
},
{
"name": "hi",
"base": "",
"fields": [{
"name": "user",
"type": "name"
}
]
}
],
"actions": [{
"name": "hi",
"type": "hi",
"ricardian_contract": ""
}
],
"tables": []
}
)=====";
BOOST_CHECK_THROW( abi_serializer abis(fc::json::from_string(abi_str).as<abi_def>()), fc::exception );
} FC_LOG_AND_RETHROW()
}
// Infinite recursion of abi_serializer in struct definitions
BOOST_AUTO_TEST_CASE(abi_recursive_structs)
{
try {
const char* abi_str = R"=====(
{
"types": [],
"structs": [
{
"name": "a"
"base": "",
"fields": [
{
"name": "user",
"type": "b"
}
]
},
{
"name": "b"
"base": "",
"fields": [
{
"name": "user",
"type": "a"
}
]
},
{
"name": "hi",
"base": "",
"fields": [{
"name": "user",
"type": "name"
},
{
"name": "arg2",
"type": "a"
}
]
}
],
"actions": [{
"name": "hi",
"type": "hi",
"ricardian_contract": ""
}
],
"tables": []
}
)=====";
abi_serializer abis(fc::json::from_string(abi_str).as<abi_def>());
string hi_data = "{\"user\":\"eosio\",\"arg2\":{\"user\":\"1\"}}";
auto bin = abis.variant_to_binary("hi", fc::json::from_string(hi_data));
BOOST_CHECK_THROW( abis.binary_to_variant("hi", bin);, fc::exception );
} FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()
...@@ -65,7 +65,7 @@ BOOST_AUTO_TEST_SUITE(tic_tac_toe_tests) ...@@ -65,7 +65,7 @@ BOOST_AUTO_TEST_SUITE(tic_tac_toe_tests)
BOOST_AUTO_TEST_CASE( tic_tac_toe_game ) try { BOOST_AUTO_TEST_CASE( tic_tac_toe_game ) try {
TESTER chain; TESTER chain;
abi_serializer abi_ser = json::from_string(tic_tac_toe_abi).as<abi_def>(); abi_serializer abi_ser{json::from_string(tic_tac_toe_abi).as<abi_def>()};
chain.create_account(N(tic.tac.toe)); chain.create_account(N(tic.tac.toe));
chain.produce_blocks(10); chain.produce_blocks(10);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册