2372 lines
92 KiB
C++
2372 lines
92 KiB
C++
#include <array>
|
|
#include <utility>
|
|
|
|
#include <eosio/chain/abi_serializer.hpp>
|
|
#include <eosio/chain/exceptions.hpp>
|
|
#include <eosio/chain/global_property_object.hpp>
|
|
#include <eosio/chain/resource_limits.hpp>
|
|
#include <eosio/chain/wasm_eosio_constraints.hpp>
|
|
#include <eosio/chain/wast_to_wasm.hpp>
|
|
#include <eosio/testing/tester.hpp>
|
|
|
|
#include <Inline/Serialization.h>
|
|
#include <IR/Module.h>
|
|
#include <WASM/WASM.h>
|
|
|
|
#include <boost/test/unit_test.hpp>
|
|
#include <boost/test/data/test_case.hpp>
|
|
#include <boost/test/data/monomorphic.hpp>
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
|
|
#include <fc/io/fstream.hpp>
|
|
#include <fc/io/incbin.h>
|
|
#include <fc/io/json.hpp>
|
|
#include <fc/variant_object.hpp>
|
|
|
|
#include "test_wasts.hpp"
|
|
#include "test_softfloat_wasts.hpp"
|
|
|
|
#include <test_contracts.hpp>
|
|
|
|
using namespace eosio;
|
|
using namespace eosio::chain;
|
|
using namespace eosio::testing;
|
|
using namespace fc;
|
|
namespace bdata = boost::unit_test::data;
|
|
|
|
|
|
struct assertdef {
|
|
int8_t condition;
|
|
string message;
|
|
|
|
static account_name get_account() {
|
|
return "asserter"_n;
|
|
}
|
|
|
|
static action_name get_name() {
|
|
return "procassert"_n;
|
|
}
|
|
};
|
|
|
|
FC_REFLECT(assertdef, (condition)(message));
|
|
|
|
struct provereset {
|
|
static account_name get_account() {
|
|
return "asserter"_n;
|
|
}
|
|
|
|
static action_name get_name() {
|
|
return "provereset"_n;
|
|
}
|
|
};
|
|
|
|
FC_REFLECT_EMPTY(provereset);
|
|
|
|
BOOST_AUTO_TEST_SUITE(wasm_tests)
|
|
|
|
/**
|
|
* Prove that action reading and assertions are working
|
|
*/
|
|
BOOST_FIXTURE_TEST_CASE( basic_test, validating_tester ) try {
|
|
produce_blocks(2);
|
|
|
|
create_accounts( {"asserter"_n} );
|
|
produce_block();
|
|
|
|
set_code("asserter"_n, test_contracts::asserter_wasm());
|
|
produce_blocks(1);
|
|
|
|
transaction_id_type no_assert_id;
|
|
{
|
|
signed_transaction trx;
|
|
trx.actions.emplace_back( vector<permission_level>{{"asserter"_n,config::active_name}},
|
|
assertdef {1, "Should Not Assert!"} );
|
|
trx.actions[0].authorization = {{"asserter"_n,config::active_name}};
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign( get_private_key( "asserter"_n, "active" ), control->get_chain_id() );
|
|
auto result = push_transaction( trx );
|
|
BOOST_CHECK_EQUAL(result->receipt->status, transaction_receipt::executed);
|
|
BOOST_CHECK_EQUAL(result->action_traces.size(), 1u);
|
|
BOOST_CHECK_EQUAL(result->action_traces.at(0).receiver.to_string(), name("asserter"_n).to_string() );
|
|
BOOST_CHECK_EQUAL(result->action_traces.at(0).act.account.to_string(), name("asserter"_n).to_string() );
|
|
BOOST_CHECK_EQUAL(result->action_traces.at(0).act.name.to_string(), name("procassert"_n).to_string() );
|
|
BOOST_CHECK_EQUAL(result->action_traces.at(0).act.authorization.size(), 1u );
|
|
BOOST_CHECK_EQUAL(result->action_traces.at(0).act.authorization.at(0).actor.to_string(), name("asserter"_n).to_string() );
|
|
BOOST_CHECK_EQUAL(result->action_traces.at(0).act.authorization.at(0).permission.to_string(), name(config::active_name).to_string() );
|
|
no_assert_id = trx.id();
|
|
}
|
|
|
|
produce_blocks(1);
|
|
|
|
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(no_assert_id));
|
|
const auto& receipt = get_transaction_receipt(no_assert_id);
|
|
BOOST_CHECK_EQUAL(transaction_receipt::executed, receipt.status);
|
|
|
|
transaction_id_type yes_assert_id;
|
|
{
|
|
signed_transaction trx;
|
|
trx.actions.emplace_back( vector<permission_level>{{"asserter"_n,config::active_name}},
|
|
assertdef {0, "Should Assert!"} );
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign( get_private_key( "asserter"_n, "active" ), control->get_chain_id() );
|
|
yes_assert_id = trx.id();
|
|
|
|
BOOST_CHECK_THROW(push_transaction( trx ), eosio_assert_message_exception);
|
|
}
|
|
|
|
produce_blocks(1);
|
|
|
|
auto has_tx = chain_has_transaction(yes_assert_id);
|
|
BOOST_REQUIRE_EQUAL(false, has_tx);
|
|
|
|
} FC_LOG_AND_RETHROW() /// basic_test
|
|
|
|
/**
|
|
* Prove the modifications to global variables are wiped between runs
|
|
*/
|
|
BOOST_FIXTURE_TEST_CASE( prove_mem_reset, validating_tester ) try {
|
|
produce_blocks(2);
|
|
|
|
create_accounts( {"asserter"_n} );
|
|
produce_block();
|
|
|
|
set_code("asserter"_n, test_contracts::asserter_wasm());
|
|
produce_blocks(1);
|
|
|
|
// repeat the action multiple times, each time the action handler checks for the expected
|
|
// default value then modifies the value which should not survive until the next invoction
|
|
for (int i = 0; i < 5; i++) {
|
|
signed_transaction trx;
|
|
trx.actions.emplace_back( vector<permission_level>{{"asserter"_n,config::active_name}},
|
|
provereset {} );
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign( get_private_key( "asserter"_n, "active" ), control->get_chain_id() );
|
|
push_transaction( trx );
|
|
produce_blocks(1);
|
|
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
|
|
const auto& receipt = get_transaction_receipt(trx.id());
|
|
BOOST_CHECK_EQUAL(transaction_receipt::executed, receipt.status);
|
|
}
|
|
|
|
} FC_LOG_AND_RETHROW() /// prove_mem_reset
|
|
|
|
/**
|
|
* Prove the modifications to global variables are wiped between runs
|
|
*/
|
|
BOOST_FIXTURE_TEST_CASE( abi_from_variant, validating_tester ) try {
|
|
produce_blocks(2);
|
|
|
|
create_accounts( {"asserter"_n} );
|
|
produce_block();
|
|
|
|
set_code("asserter"_n, test_contracts::asserter_wasm());
|
|
set_abi("asserter"_n, test_contracts::asserter_abi());
|
|
produce_blocks(1);
|
|
|
|
auto resolver = [&,this]( const account_name& name ) -> std::optional<abi_serializer> {
|
|
try {
|
|
const auto& accnt = this->control->db().get<account_object,by_name>( name );
|
|
if (abi_def abi; abi_serializer::to_abi(accnt.abi, abi)) {
|
|
return abi_serializer(std::move(abi), abi_serializer::create_yield_function( abi_serializer_max_time ));
|
|
}
|
|
return std::optional<abi_serializer>();
|
|
} FC_RETHROW_EXCEPTIONS(error, "Failed to find or parse ABI for ${name}", ("name", name))
|
|
};
|
|
|
|
fc::variant pretty_trx = mutable_variant_object()
|
|
("actions", variants({
|
|
mutable_variant_object()
|
|
("account", "asserter")
|
|
("name", "procassert")
|
|
("authorization", variants({
|
|
mutable_variant_object()
|
|
("actor", "asserter")
|
|
("permission", name(config::active_name).to_string())
|
|
}))
|
|
("data", mutable_variant_object()
|
|
("condition", 1)
|
|
("message", "Should Not Assert!")
|
|
)
|
|
})
|
|
);
|
|
|
|
signed_transaction trx;
|
|
abi_serializer::from_variant(pretty_trx, trx, resolver, abi_serializer::create_yield_function( abi_serializer_max_time ));
|
|
set_transaction_headers(trx);
|
|
trx.sign( get_private_key( "asserter"_n, "active" ), control->get_chain_id() );
|
|
push_transaction( trx );
|
|
produce_blocks(1);
|
|
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
|
|
const auto& receipt = get_transaction_receipt(trx.id());
|
|
BOOST_CHECK_EQUAL(transaction_receipt::executed, receipt.status);
|
|
|
|
} FC_LOG_AND_RETHROW() /// prove_mem_reset
|
|
|
|
// test softfloat 32 bit operations
|
|
BOOST_FIXTURE_TEST_CASE( f32_tests, validating_tester ) try {
|
|
produce_blocks(2);
|
|
produce_block();
|
|
create_accounts( {"f32.tests"_n} );
|
|
{
|
|
set_code("f32.tests"_n, f32_test_wast);
|
|
produce_blocks(10);
|
|
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "f32.tests"_n;
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{"f32.tests"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "f32.tests"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
produce_blocks(1);
|
|
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
|
|
get_transaction_receipt(trx.id());
|
|
}
|
|
} FC_LOG_AND_RETHROW()
|
|
BOOST_FIXTURE_TEST_CASE( f32_test_bitwise, validating_tester ) try {
|
|
produce_blocks(2);
|
|
create_accounts( {"f32.tests"_n} );
|
|
produce_block();
|
|
{
|
|
set_code("f32.tests"_n, f32_bitwise_test_wast);
|
|
produce_blocks(10);
|
|
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "f32.tests"_n;
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{"f32.tests"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "f32.tests"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
produce_blocks(1);
|
|
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
|
|
get_transaction_receipt(trx.id());
|
|
}
|
|
} FC_LOG_AND_RETHROW()
|
|
BOOST_FIXTURE_TEST_CASE( f32_test_cmp, validating_tester ) try {
|
|
produce_blocks(2);
|
|
create_accounts( {"f32.tests"_n} );
|
|
produce_block();
|
|
{
|
|
set_code("f32.tests"_n, f32_cmp_test_wast);
|
|
produce_blocks(10);
|
|
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "f32.tests"_n;
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{"f32.tests"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "f32.tests"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
produce_blocks(1);
|
|
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
|
|
get_transaction_receipt(trx.id());
|
|
}
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
// test softfloat 64 bit operations
|
|
BOOST_FIXTURE_TEST_CASE( f64_tests, validating_tester ) try {
|
|
produce_blocks(2);
|
|
create_accounts( {"f.tests"_n} );
|
|
produce_block();
|
|
{
|
|
set_code("f.tests"_n, f64_test_wast);
|
|
produce_blocks(10);
|
|
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "f.tests"_n;
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{"f.tests"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "f.tests"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
produce_blocks(1);
|
|
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
|
|
get_transaction_receipt(trx.id());
|
|
}
|
|
} FC_LOG_AND_RETHROW()
|
|
BOOST_FIXTURE_TEST_CASE( f64_test_bitwise, validating_tester ) try {
|
|
produce_blocks(2);
|
|
create_accounts( {"f.tests"_n} );
|
|
produce_block();
|
|
{
|
|
set_code("f.tests"_n, f64_bitwise_test_wast);
|
|
produce_blocks(10);
|
|
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "f.tests"_n;
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{"f.tests"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "f.tests"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
produce_blocks(1);
|
|
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
|
|
get_transaction_receipt(trx.id());
|
|
}
|
|
} FC_LOG_AND_RETHROW()
|
|
BOOST_FIXTURE_TEST_CASE( f64_test_cmp, validating_tester ) try {
|
|
produce_blocks(2);
|
|
create_accounts( {"f.tests"_n} );
|
|
produce_block();
|
|
{
|
|
set_code("f.tests"_n, f64_cmp_test_wast);
|
|
produce_blocks(10);
|
|
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "f.tests"_n;
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{"f.tests"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "f.tests"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
produce_blocks(1);
|
|
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
|
|
get_transaction_receipt(trx.id());
|
|
}
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
// test softfloat conversion operations
|
|
BOOST_FIXTURE_TEST_CASE( f32_f64_conversion_tests, validating_tester ) try {
|
|
produce_blocks(2);
|
|
|
|
create_accounts( {"ftests"_n} );
|
|
produce_block();
|
|
{
|
|
set_code("ftests"_n, f32_f64_conv_wast);
|
|
produce_blocks(10);
|
|
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "ftests"_n;
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{"ftests"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "ftests"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
produce_blocks(1);
|
|
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
|
|
get_transaction_receipt(trx.id());
|
|
}
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
// test softfloat conversion operations
|
|
BOOST_FIXTURE_TEST_CASE( f32_f64_overflow_tests, validating_tester ) try {
|
|
int count = 0;
|
|
auto check = [&](const char *wast_template, const char *op, const char *param) -> bool {
|
|
count+=16;
|
|
create_accounts( {name("ftests"_n.to_uint64_t()+count)} );
|
|
produce_blocks(1);
|
|
std::vector<char> wast;
|
|
wast.resize(strlen(wast_template) + 128);
|
|
sprintf(&(wast[0]), wast_template, op, param);
|
|
set_code(name("ftests"_n.to_uint64_t()+count), &(wast[0]));
|
|
produce_blocks(10);
|
|
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = name("ftests"_n.to_uint64_t()+count);
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{name("ftests"_n.to_uint64_t()+count),config::active_name}};
|
|
trx.actions.push_back(act);
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( name("ftests"_n.to_uint64_t()+count), "active" ), control->get_chain_id());
|
|
|
|
try {
|
|
push_transaction(trx);
|
|
produce_blocks(1);
|
|
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
|
|
get_transaction_receipt(trx.id());
|
|
return true;
|
|
} catch (eosio::chain::wasm_execution_error &) {
|
|
return false;
|
|
}
|
|
};
|
|
//
|
|
//// float32 => int32
|
|
// 2^31
|
|
BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_s_f32", "f32.const 2147483648"));
|
|
// the maximum value below 2^31 representable in IEEE float32
|
|
BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_s_f32", "f32.const 2147483520"));
|
|
// -2^31
|
|
BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_s_f32", "f32.const -2147483648"));
|
|
// the maximum value below -2^31 in IEEE float32
|
|
BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_s_f32", "f32.const -2147483904"));
|
|
|
|
//
|
|
//// float32 => uint32
|
|
BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_u_f32", "f32.const 0"));
|
|
BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_u_f32", "f32.const -1"));
|
|
// max value below 2^32 in IEEE float32
|
|
BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_u_f32", "f32.const 4294967040"));
|
|
BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_u_f32", "f32.const 4294967296"));
|
|
|
|
//
|
|
//// double => int32
|
|
BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_s_f64", "f64.const 2147483648"));
|
|
BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_s_f64", "f64.const 2147483647"));
|
|
BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_s_f64", "f64.const -2147483648"));
|
|
BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_s_f64", "f64.const -2147483649"));
|
|
|
|
//
|
|
//// double => uint32
|
|
BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_u_f64", "f64.const 0"));
|
|
BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_u_f64", "f64.const -1"));
|
|
BOOST_REQUIRE_EQUAL(true, check(i32_overflow_wast, "i32_trunc_u_f64", "f64.const 4294967295"));
|
|
BOOST_REQUIRE_EQUAL(false, check(i32_overflow_wast, "i32_trunc_u_f64", "f64.const 4294967296"));
|
|
|
|
|
|
//// float32 => int64
|
|
// 2^63
|
|
BOOST_REQUIRE_EQUAL(false, check(i64_overflow_wast, "i64_trunc_s_f32", "f32.const 9223372036854775808"));
|
|
// the maximum value below 2^63 representable in IEEE float32
|
|
BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_s_f32", "f32.const 9223371487098961920"));
|
|
// -2^63
|
|
BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_s_f32", "f32.const -9223372036854775808"));
|
|
// the maximum value below -2^63 in IEEE float32
|
|
BOOST_REQUIRE_EQUAL(false, check(i64_overflow_wast, "i64_trunc_s_f32", "f32.const -9223373136366403584"));
|
|
|
|
//// float32 => uint64
|
|
BOOST_REQUIRE_EQUAL(false, check(i64_overflow_wast, "i64_trunc_u_f32", "f32.const -1"));
|
|
BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_u_f32", "f32.const 0"));
|
|
// max value below 2^64 in IEEE float32
|
|
BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_u_f32", "f32.const 18446742974197923840"));
|
|
BOOST_REQUIRE_EQUAL(false, check(i64_overflow_wast, "i64_trunc_u_f32", "f32.const 18446744073709551616"));
|
|
|
|
//// double => int64
|
|
// 2^63
|
|
BOOST_REQUIRE_EQUAL(false, check(i64_overflow_wast, "i64_trunc_s_f64", "f64.const 9223372036854775808"));
|
|
// the maximum value below 2^63 representable in IEEE float64
|
|
BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_s_f64", "f64.const 9223372036854774784"));
|
|
// -2^63
|
|
BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_s_f64", "f64.const -9223372036854775808"));
|
|
// the maximum value below -2^63 in IEEE float64
|
|
BOOST_REQUIRE_EQUAL(false, check(i64_overflow_wast, "i64_trunc_s_f64", "f64.const -9223372036854777856"));
|
|
|
|
//// double => uint64
|
|
BOOST_REQUIRE_EQUAL(false, check(i64_overflow_wast, "i64_trunc_u_f64", "f64.const -1"));
|
|
BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_u_f64", "f64.const 0"));
|
|
// max value below 2^64 in IEEE float64
|
|
BOOST_REQUIRE_EQUAL(true, check(i64_overflow_wast, "i64_trunc_u_f64", "f64.const 18446744073709549568"));
|
|
BOOST_REQUIRE_EQUAL(false, check(i64_overflow_wast, "i64_trunc_u_f64", "f64.const 18446744073709551616"));
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_FIXTURE_TEST_CASE(misaligned_tests, validating_tester ) try {
|
|
produce_blocks(2);
|
|
create_accounts( {"aligncheck"_n} );
|
|
produce_block();
|
|
|
|
auto check_aligned = [&]( auto wast ) {
|
|
set_code("aligncheck"_n, wast);
|
|
produce_blocks(10);
|
|
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "aligncheck"_n;
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{"aligncheck"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "aligncheck"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
produce_block();
|
|
|
|
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
|
|
};
|
|
|
|
check_aligned(aligned_ref_wast);
|
|
check_aligned(misaligned_ref_wast);
|
|
check_aligned(aligned_const_ref_wast);
|
|
check_aligned(misaligned_const_ref_wast);
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
/**
|
|
* Make sure WASM "start" method is used correctly
|
|
*/
|
|
BOOST_FIXTURE_TEST_CASE( check_entry_behavior, validating_tester ) try {
|
|
produce_blocks(2);
|
|
create_accounts( {"entrycheck"_n} );
|
|
produce_block();
|
|
|
|
set_code("entrycheck"_n, entry_wast);
|
|
produce_blocks(10);
|
|
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "entrycheck"_n;
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{"entrycheck"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "entrycheck"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
produce_blocks(1);
|
|
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
|
|
const auto& receipt = get_transaction_receipt(trx.id());
|
|
BOOST_CHECK_EQUAL(transaction_receipt::executed, receipt.status);
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_FIXTURE_TEST_CASE( check_entry_behavior_2, validating_tester ) try {
|
|
produce_blocks(2);
|
|
create_accounts( {"entrycheck"_n} );
|
|
produce_block();
|
|
|
|
set_code("entrycheck"_n, entry_wast_2);
|
|
produce_blocks(10);
|
|
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "entrycheck"_n;
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{"entrycheck"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "entrycheck"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
produce_blocks(1);
|
|
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
|
|
const auto& receipt = get_transaction_receipt(trx.id());
|
|
BOOST_CHECK_EQUAL(transaction_receipt::executed, receipt.status);
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_FIXTURE_TEST_CASE( entry_import, validating_tester ) try {
|
|
create_accounts( {"enterimport"_n} );
|
|
produce_block();
|
|
|
|
set_code("enterimport"_n, entry_import_wast);
|
|
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "enterimport"_n;
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{"enterimport"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "enterimport"_n, "active" ), control->get_chain_id());
|
|
BOOST_CHECK_THROW(push_transaction(trx), abort_called);
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_FIXTURE_TEST_CASE( entry_db, validating_tester ) try {
|
|
create_accounts( {"entrydb"_n} );
|
|
produce_block();
|
|
|
|
set_code("entrydb"_n, entry_db_wast);
|
|
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "entrydb"_n;
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{"entrydb"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "entrydb"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
/**
|
|
* Ensure we can load a wasm w/o memory
|
|
*/
|
|
BOOST_FIXTURE_TEST_CASE( simple_no_memory_check, validating_tester ) try {
|
|
produce_blocks(2);
|
|
|
|
create_accounts( {"nomem"_n} );
|
|
produce_block();
|
|
|
|
set_code("nomem"_n, simple_no_memory_wast);
|
|
produce_blocks(1);
|
|
|
|
//the apply func of simple_no_memory_wast tries to call a native func with linear memory pointer
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "nomem"_n;
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{"nomem"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
trx.expiration = fc::time_point_sec{control->head_block_time()};
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "nomem"_n, "active" ), control->get_chain_id());
|
|
BOOST_CHECK_THROW(push_transaction( trx ), wasm_execution_error);
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
//Make sure globals are all reset to their inital values
|
|
BOOST_FIXTURE_TEST_CASE( check_global_reset, validating_tester ) try {
|
|
produce_blocks(2);
|
|
|
|
create_accounts( {"globalreset"_n} );
|
|
produce_block();
|
|
|
|
set_code("globalreset"_n, mutable_global_wast);
|
|
produce_blocks(1);
|
|
|
|
signed_transaction trx;
|
|
{
|
|
action act;
|
|
act.account = "globalreset"_n;
|
|
act.name = name(0ULL);
|
|
act.authorization = vector<permission_level>{{"globalreset"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
}
|
|
{
|
|
action act;
|
|
act.account = "globalreset"_n;
|
|
act.name = name(1ULL);
|
|
act.authorization = vector<permission_level>{{"globalreset"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
}
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "globalreset"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
produce_blocks(1);
|
|
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
|
|
const auto& receipt = get_transaction_receipt(trx.id());
|
|
BOOST_CHECK_EQUAL(transaction_receipt::executed, receipt.status);
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
//Make sure we can create a wasm with maximum pages, but not grow it any
|
|
BOOST_DATA_TEST_CASE( big_memory, bdata::make({setup_policy::preactivate_feature_and_new_bios, setup_policy::old_wasm_parser, setup_policy::full}), policy ) try {
|
|
validating_tester t(flat_set<account_name>{}, {}, policy);
|
|
if(policy != setup_policy::full)
|
|
t.preactivate_builtin_protocol_features({builtin_protocol_feature_t::configurable_wasm_limits});
|
|
|
|
t.produce_blocks(2);
|
|
|
|
|
|
t.create_accounts( {"bigmem"_n} );
|
|
t.produce_block();
|
|
|
|
string biggest_memory_wast_f = fc::format_string(biggest_memory_wast, fc::mutable_variant_object(
|
|
"MAX_WASM_PAGES", eosio::chain::wasm_constraints::maximum_linear_memory/(64*1024)));
|
|
|
|
t.set_code("bigmem"_n, biggest_memory_wast_f.c_str());
|
|
t.produce_blocks(1);
|
|
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "bigmem"_n;
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{"bigmem"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
|
|
t.set_transaction_headers(trx);
|
|
trx.sign(validating_tester::get_private_key( "bigmem"_n, "active" ), t.control->get_chain_id());
|
|
//but should not be able to grow beyond largest page
|
|
t.push_transaction(trx);
|
|
|
|
t.produce_blocks(1);
|
|
|
|
string too_big_memory_wast_f = fc::format_string(too_big_memory_wast, fc::mutable_variant_object(
|
|
"MAX_WASM_PAGES_PLUS_ONE", eosio::chain::wasm_constraints::maximum_linear_memory/(64*1024)+1));
|
|
BOOST_CHECK_THROW(t.set_code("bigmem"_n, too_big_memory_wast_f.c_str()), eosio::chain::wasm_exception);
|
|
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_DATA_TEST_CASE( table_init_tests, bdata::make({setup_policy::preactivate_feature_and_new_bios, setup_policy::old_wasm_parser, setup_policy::full}), policy ) try {
|
|
validating_tester t(flat_set<account_name>{}, {}, policy);
|
|
if(policy != setup_policy::full)
|
|
t.preactivate_builtin_protocol_features({builtin_protocol_feature_t::configurable_wasm_limits});
|
|
t.produce_blocks(2);
|
|
|
|
t.create_accounts( {"tableinit"_n} );
|
|
t.produce_block();
|
|
|
|
t.set_code("tableinit"_n, valid_sparse_table);
|
|
t.produce_blocks(1);
|
|
|
|
BOOST_CHECK_THROW(t.set_code("tableinit"_n, too_big_table), eosio::chain::wasm_exception);
|
|
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_FIXTURE_TEST_CASE( table_init_oob, validating_tester ) try {
|
|
create_accounts( {"tableinitoob"_n} );
|
|
produce_block();
|
|
|
|
signed_transaction trx;
|
|
trx.actions.emplace_back(vector<permission_level>{{"tableinitoob"_n,config::active_name}}, "tableinitoob"_n, ""_n, bytes{});
|
|
trx.actions[0].authorization = vector<permission_level>{{"tableinitoob"_n,config::active_name}};
|
|
|
|
auto pushit_and_expect_fail = [&]() {
|
|
produce_block();
|
|
trx.signatures.clear();
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key("tableinitoob"_n, "active"), control->get_chain_id());
|
|
|
|
//the unspecified_exception_code comes from WAVM, which manages to throw a WAVM specific exception
|
|
// up to where exec_one captures it and doesn't understand it
|
|
BOOST_CHECK_THROW(push_transaction(trx), eosio::chain::wasm_exception);
|
|
};
|
|
|
|
set_code("tableinitoob"_n, table_init_oob_wast);
|
|
produce_block();
|
|
|
|
pushit_and_expect_fail();
|
|
//make sure doing it again didn't lodge something funky in to a cache
|
|
pushit_and_expect_fail();
|
|
|
|
set_code("tableinitoob"_n, table_init_oob_smaller_wast);
|
|
produce_block();
|
|
pushit_and_expect_fail();
|
|
pushit_and_expect_fail();
|
|
|
|
//an elem w/o a table is a setcode fail though
|
|
BOOST_CHECK_THROW(set_code("tableinitoob"_n, table_init_oob_no_table_wast), eosio::chain::wasm_exception);
|
|
|
|
set_code("tableinitoob"_n, table_init_oob_empty_wast);
|
|
produce_block();
|
|
pushit_and_expect_fail();
|
|
pushit_and_expect_fail();
|
|
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_FIXTURE_TEST_CASE( memory_init_border, validating_tester ) try {
|
|
produce_blocks(2);
|
|
|
|
create_accounts( {"memoryborder"_n} );
|
|
produce_block();
|
|
|
|
set_code("memoryborder"_n, memory_init_borderline);
|
|
produce_blocks(1);
|
|
|
|
BOOST_CHECK_THROW(set_code("memoryborder"_n, memory_init_toolong), eosio::chain::wasm_exception);
|
|
BOOST_CHECK_THROW(set_code("memoryborder"_n, memory_init_negative), eosio::chain::wasm_exception);
|
|
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_FIXTURE_TEST_CASE( imports, validating_tester ) try {
|
|
try {
|
|
produce_blocks(2);
|
|
|
|
create_accounts( {"imports"_n} );
|
|
produce_block();
|
|
|
|
//this will fail to link but that's okay; mainly looking to make sure that the constraint
|
|
// system doesn't choke when memories and tables exist only as imports
|
|
BOOST_CHECK_THROW(set_code("imports"_n, memory_table_import), fc::exception);
|
|
} catch ( const fc::exception& e ) {
|
|
|
|
edump((e.to_detail_string()));
|
|
throw;
|
|
}
|
|
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_FIXTURE_TEST_CASE( nested_limit_test, validating_tester ) try {
|
|
produce_blocks(2);
|
|
|
|
create_accounts( {"nested"_n} );
|
|
produce_block();
|
|
|
|
// nested loops
|
|
{
|
|
std::stringstream ss;
|
|
ss << "(module (export \"apply\" (func $apply)) (func $apply (param $0 i64) (param $1 i64) (param $2 i64)";
|
|
for(unsigned int i = 0; i < 1023; ++i)
|
|
ss << "(loop (drop (i32.const " << i << "))";
|
|
for(unsigned int i = 0; i < 1023; ++i)
|
|
ss << ")";
|
|
ss << "))";
|
|
set_code("nested"_n, ss.str().c_str());
|
|
}
|
|
{
|
|
std::stringstream ss;
|
|
ss << "(module (export \"apply\" (func $apply)) (func $apply (param $0 i64) (param $1 i64) (param $2 i64)";
|
|
for(unsigned int i = 0; i < 1024; ++i)
|
|
ss << "(loop (drop (i32.const " << i << "))";
|
|
for(unsigned int i = 0; i < 1024; ++i)
|
|
ss << ")";
|
|
ss << "))";
|
|
BOOST_CHECK_THROW(set_code("nested"_n, ss.str().c_str()), eosio::chain::wasm_exception);
|
|
}
|
|
|
|
// nested blocks
|
|
{
|
|
std::stringstream ss;
|
|
ss << "(module (export \"apply\" (func $apply)) (func $apply (param $0 i64) (param $1 i64) (param $2 i64)";
|
|
for(unsigned int i = 0; i < 1023; ++i)
|
|
ss << "(block (drop (i32.const " << i << "))";
|
|
for(unsigned int i = 0; i < 1023; ++i)
|
|
ss << ")";
|
|
ss << "))";
|
|
set_code("nested"_n, ss.str().c_str());
|
|
}
|
|
{
|
|
std::stringstream ss;
|
|
ss << "(module (export \"apply\" (func $apply)) (func $apply (param $0 i64) (param $1 i64) (param $2 i64)";
|
|
for(unsigned int i = 0; i < 1024; ++i)
|
|
ss << "(block (drop (i32.const " << i << "))";
|
|
for(unsigned int i = 0; i < 1024; ++i)
|
|
ss << ")";
|
|
ss << "))";
|
|
BOOST_CHECK_THROW(set_code("nested"_n, ss.str().c_str()), eosio::chain::wasm_exception);
|
|
}
|
|
// nested ifs
|
|
{
|
|
std::stringstream ss;
|
|
ss << "(module (export \"apply\" (func $apply)) (func $apply (param $0 i64) (param $1 i64) (param $2 i64)";
|
|
for(unsigned int i = 0; i < 1023; ++i)
|
|
ss << "(if (i32.wrap/i64 (get_local $0)) (then (drop (i32.const " << i << "))";
|
|
for(unsigned int i = 0; i < 1023; ++i)
|
|
ss << "))";
|
|
ss << "))";
|
|
set_code("nested"_n, ss.str().c_str());
|
|
}
|
|
{
|
|
std::stringstream ss;
|
|
ss << "(module (export \"apply\" (func $apply)) (func $apply (param $0 i64) (param $1 i64) (param $2 i64)";
|
|
for(unsigned int i = 0; i < 1024; ++i)
|
|
ss << "(if (i32.wrap/i64 (get_local $0)) (then (drop (i32.const " << i << "))";
|
|
for(unsigned int i = 0; i < 1024; ++i)
|
|
ss << "))";
|
|
ss << "))";
|
|
BOOST_CHECK_THROW(set_code("nested"_n, ss.str().c_str()), eosio::chain::wasm_exception);
|
|
}
|
|
// mixed nested
|
|
{
|
|
std::stringstream ss;
|
|
ss << "(module (export \"apply\" (func $apply)) (func $apply (param $0 i64) (param $1 i64) (param $2 i64)";
|
|
for(unsigned int i = 0; i < 223; ++i)
|
|
ss << "(if (i32.wrap/i64 (get_local $0)) (then (drop (i32.const " << i << "))";
|
|
for(unsigned int i = 0; i < 400; ++i)
|
|
ss << "(block (drop (i32.const " << i << "))";
|
|
for(unsigned int i = 0; i < 400; ++i)
|
|
ss << "(loop (drop (i32.const " << i << "))";
|
|
for(unsigned int i = 0; i < 800; ++i)
|
|
ss << ")";
|
|
for(unsigned int i = 0; i < 223; ++i)
|
|
ss << "))";
|
|
ss << "))";
|
|
set_code("nested"_n, ss.str().c_str());
|
|
}
|
|
{
|
|
std::stringstream ss;
|
|
ss << "(module (export \"apply\" (func $apply)) (func $apply (param $0 i64) (param $1 i64) (param $2 i64)";
|
|
for(unsigned int i = 0; i < 224; ++i)
|
|
ss << "(if (i32.wrap/i64 (get_local $0)) (then (drop (i32.const " << i << "))";
|
|
for(unsigned int i = 0; i < 400; ++i)
|
|
ss << "(block (drop (i32.const " << i << "))";
|
|
for(unsigned int i = 0; i < 400; ++i)
|
|
ss << "(loop (drop (i32.const " << i << "))";
|
|
for(unsigned int i = 0; i < 800; ++i)
|
|
ss << ")";
|
|
for(unsigned int i = 0; i < 224; ++i)
|
|
ss << "))";
|
|
ss << "))";
|
|
BOOST_CHECK_THROW(set_code("nested"_n, ss.str().c_str()), eosio::chain::wasm_exception);
|
|
}
|
|
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
|
|
BOOST_DATA_TEST_CASE( lotso_globals, bdata::make({setup_policy::preactivate_feature_and_new_bios, setup_policy::old_wasm_parser, setup_policy::full}), policy ) try {
|
|
validating_tester t(flat_set<account_name>{}, {}, policy);
|
|
if(policy != setup_policy::full)
|
|
t.preactivate_builtin_protocol_features({builtin_protocol_feature_t::configurable_wasm_limits});
|
|
|
|
t.produce_blocks(2);
|
|
|
|
t.create_accounts( {"globals"_n} );
|
|
t.produce_block();
|
|
|
|
std::stringstream ss;
|
|
ss << "(module (export \"apply\" (func $apply)) (func $apply (param $0 i64) (param $1 i64) (param $2 i64))";
|
|
for(unsigned int i = 0; i < 85; ++i)
|
|
ss << "(global $g" << i << " (mut i32) (i32.const 0))" << "(global $g" << i+100 << " (mut i64) (i64.const 0))";
|
|
//that gives us 1020 bytes of mutable globals
|
|
//add a few immutable ones for good measure
|
|
for(unsigned int i = 0; i < 10; ++i)
|
|
ss << "(global $g" << i+200 << " i32 (i32.const 0))";
|
|
|
|
t.set_code("globals"_n, (ss.str() + ")").c_str());
|
|
//1024 should pass
|
|
t.set_code("globals"_n, (ss.str() + "(global $z (mut i32) (i32.const -12)))").c_str());
|
|
//1028 should fail
|
|
BOOST_CHECK_THROW(t.set_code("globals"_n, (ss.str() + "(global $z (mut i64) (i64.const -12)))").c_str()), eosio::chain::wasm_exception);
|
|
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_AUTO_TEST_CASE( offset_check_old ) try {
|
|
validating_tester t(flat_set<account_name>{}, {}, setup_policy::old_wasm_parser);
|
|
t.produce_blocks(2);
|
|
|
|
t.create_accounts( {"offsets"_n} );
|
|
t.produce_block();
|
|
|
|
vector<string> loadops = {
|
|
"i32.load", "i64.load", "f32.load", "f64.load", "i32.load8_s", "i32.load8_u",
|
|
"i32.load16_s", "i32.load16_u", "i64.load8_s", "i64.load8_u", "i64.load16_s",
|
|
"i64.load16_u", "i64.load32_s", "i64.load32_u"
|
|
};
|
|
vector<vector<string>> storeops = {
|
|
{"i32.store", "i32"},
|
|
{"i64.store", "i64"},
|
|
{"f32.store", "f32"},
|
|
{"f64.store", "f64"},
|
|
{"i32.store8", "i32"},
|
|
{"i32.store16", "i32"},
|
|
{"i64.store8", "i64"},
|
|
{"i64.store16", "i64"},
|
|
{"i64.store32", "i64"},
|
|
};
|
|
|
|
for(const string& s : loadops) {
|
|
std::stringstream ss;
|
|
ss << "(module (memory $0 " << eosio::chain::wasm_constraints::maximum_linear_memory/(64*1024) << ") (func $apply (param $0 i64) (param $1 i64) (param $2 i64)";
|
|
ss << "(drop (" << s << " offset=" << eosio::chain::wasm_constraints::maximum_linear_memory-2 << " (i32.const 0)))";
|
|
ss << ") (export \"apply\" (func $apply)) )";
|
|
|
|
t.set_code("offsets"_n, ss.str().c_str());
|
|
t.produce_block();
|
|
}
|
|
for(const vector<string>& o : storeops) {
|
|
std::stringstream ss;
|
|
ss << "(module (memory $0 " << eosio::chain::wasm_constraints::maximum_linear_memory/(64*1024) << ") (func $apply (param $0 i64) (param $1 i64) (param $2 i64)";
|
|
ss << "(" << o[0] << " offset=" << eosio::chain::wasm_constraints::maximum_linear_memory-2 << " (i32.const 0) (" << o[1] << ".const 0))";
|
|
ss << ") (export \"apply\" (func $apply)) )";
|
|
|
|
t.set_code("offsets"_n, ss.str().c_str());
|
|
t.produce_block();
|
|
}
|
|
|
|
for(const string& s : loadops) {
|
|
std::stringstream ss;
|
|
ss << "(module (memory $0 " << eosio::chain::wasm_constraints::maximum_linear_memory/(64*1024) << ") (func $apply (param $0 i64) (param $1 i64) (param $2 i64)";
|
|
ss << "(drop (" << s << " offset=" << eosio::chain::wasm_constraints::maximum_linear_memory+4 << " (i32.const 0)))";
|
|
ss << ") (export \"apply\" (func $apply)) )";
|
|
|
|
BOOST_CHECK_THROW(t.set_code("offsets"_n, ss.str().c_str()), eosio::chain::wasm_exception);
|
|
t.produce_block();
|
|
}
|
|
for(const vector<string>& o : storeops) {
|
|
std::stringstream ss;
|
|
ss << "(module (memory $0 " << eosio::chain::wasm_constraints::maximum_linear_memory/(64*1024) << ") (func $apply (param $0 i64) (param $1 i64) (param $2 i64)";
|
|
ss << "(" << o[0] << " offset=" << eosio::chain::wasm_constraints::maximum_linear_memory+4 << " (i32.const 0) (" << o[1] << ".const 0))";
|
|
ss << ") (export \"apply\" (func $apply)) )";
|
|
|
|
BOOST_CHECK_THROW(t.set_code("offsets"_n, ss.str().c_str()), eosio::chain::wasm_exception);
|
|
t.produce_block();
|
|
}
|
|
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_FIXTURE_TEST_CASE( offset_check, validating_tester ) try {
|
|
produce_blocks(2);
|
|
|
|
create_accounts( {"offsets"_n} );
|
|
produce_block();
|
|
|
|
vector<string> loadops = {
|
|
"i32.load", "i64.load", "f32.load", "f64.load", "i32.load8_s", "i32.load8_u",
|
|
"i32.load16_s", "i32.load16_u", "i64.load8_s", "i64.load8_u", "i64.load16_s",
|
|
"i64.load16_u", "i64.load32_s", "i64.load32_u"
|
|
};
|
|
vector<vector<string>> storeops = {
|
|
{"i32.store", "i32"},
|
|
{"i64.store", "i64"},
|
|
{"f32.store", "f32"},
|
|
{"f64.store", "f64"},
|
|
{"i32.store8", "i32"},
|
|
{"i32.store16", "i32"},
|
|
{"i64.store8", "i64"},
|
|
{"i64.store16", "i64"},
|
|
{"i64.store32", "i64"},
|
|
};
|
|
|
|
for(const string& s : loadops) {
|
|
std::stringstream ss;
|
|
ss << "(module (memory $0 " << eosio::chain::wasm_constraints::maximum_linear_memory/(64*1024) << ") (func $apply (param $0 i64) (param $1 i64) (param $2 i64)";
|
|
ss << "(drop (" << s << " offset=" << 0xFFFFFFFFu << " (i32.const 0)))";
|
|
ss << ") (export \"apply\" (func $apply)) )";
|
|
|
|
set_code("offsets"_n, ss.str().c_str());
|
|
produce_block();
|
|
}
|
|
for(const vector<string>& o : storeops) {
|
|
std::stringstream ss;
|
|
ss << "(module (memory $0 " << eosio::chain::wasm_constraints::maximum_linear_memory/(64*1024) << ") (func $apply (param $0 i64) (param $1 i64) (param $2 i64)";
|
|
ss << "(" << o[0] << " offset=" << 0xFFFFFFFFu << " (i32.const 0) (" << o[1] << ".const 0))";
|
|
ss << ") (export \"apply\" (func $apply)) )";
|
|
|
|
set_code("offsets"_n, ss.str().c_str());
|
|
produce_block();
|
|
}
|
|
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
|
|
BOOST_FIXTURE_TEST_CASE(noop, validating_tester) try {
|
|
produce_blocks(2);
|
|
create_accounts( {"noop"_n, "alice"_n} );
|
|
produce_block();
|
|
|
|
set_code("noop"_n, test_contracts::noop_wasm());
|
|
|
|
set_abi("noop"_n, test_contracts::noop_abi());
|
|
const auto& accnt = control->db().get<account_object,by_name>("noop"_n);
|
|
abi_def abi;
|
|
BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi), true);
|
|
abi_serializer abi_ser(std::move(abi), abi_serializer::create_yield_function( abi_serializer_max_time ));
|
|
|
|
{
|
|
produce_blocks(5);
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "noop"_n;
|
|
act.name = "anyaction"_n;
|
|
act.authorization = vector<permission_level>{{"noop"_n, config::active_name}};
|
|
|
|
act.data = abi_ser.variant_to_binary("anyaction", mutable_variant_object()
|
|
("from", "noop")
|
|
("type", "some type")
|
|
("data", "some data goes here"),
|
|
abi_serializer::create_yield_function( abi_serializer_max_time )
|
|
);
|
|
|
|
trx.actions.emplace_back(std::move(act));
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key("noop"_n, "active"), control->get_chain_id());
|
|
push_transaction(trx);
|
|
produce_block();
|
|
|
|
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
|
|
}
|
|
|
|
{
|
|
produce_blocks(5);
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "noop"_n;
|
|
act.name = "anyaction"_n;
|
|
act.authorization = vector<permission_level>{{"alice"_n, config::active_name}};
|
|
|
|
act.data = abi_ser.variant_to_binary("anyaction", mutable_variant_object()
|
|
("from", "alice")
|
|
("type", "some type")
|
|
("data", "some data goes here"),
|
|
abi_serializer::create_yield_function( abi_serializer_max_time )
|
|
);
|
|
|
|
trx.actions.emplace_back(std::move(act));
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key("alice"_n, "active"), control->get_chain_id());
|
|
push_transaction(trx);
|
|
produce_block();
|
|
|
|
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
|
|
}
|
|
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
// abi_serializer::to_variant failed because eosio_system_abi modified via set_abi.
|
|
// This test also verifies that chain_initializer::eos_contract_abi() does not conflict
|
|
// with eosio_system_abi as they are not allowed to contain duplicates.
|
|
BOOST_FIXTURE_TEST_CASE(eosio_abi, validating_tester) try {
|
|
produce_blocks(2);
|
|
|
|
const auto& accnt = control->db().get<account_object,by_name>(config::system_account_name);
|
|
abi_def abi;
|
|
BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi), true);
|
|
abi_serializer abi_ser(std::move(abi), abi_serializer::create_yield_function( abi_serializer_max_time ));
|
|
|
|
signed_transaction trx;
|
|
name a = "alice"_n;
|
|
authority owner_auth = authority( get_public_key( a, "owner" ) );
|
|
trx.actions.emplace_back( vector<permission_level>{{config::system_account_name,config::active_name}},
|
|
newaccount{
|
|
.creator = config::system_account_name,
|
|
.name = a,
|
|
.owner = owner_auth,
|
|
.active = authority( get_public_key( a, "active" ) )
|
|
});
|
|
set_transaction_headers(trx);
|
|
trx.sign( get_private_key( config::system_account_name, "active" ), control->get_chain_id() );
|
|
auto result = push_transaction( trx );
|
|
|
|
fc::variant pretty_output;
|
|
// verify to_variant works on eos native contract type: newaccount
|
|
// see abi_serializer::to_abi()
|
|
abi_serializer::to_variant(*result, pretty_output, get_resolver(), abi_serializer::create_yield_function( abi_serializer_max_time ));
|
|
BOOST_TEST(fc::json::to_string(pretty_output, fc::time_point::now() + abi_serializer_max_time).find("newaccount") != std::string::npos);
|
|
|
|
pretty_output.clear();
|
|
abi_serializer::to_variant(*result, pretty_output, get_resolver(), abi_serializer_max_time);
|
|
BOOST_TEST(fc::json::to_string(pretty_output, fc::time_point::now() + abi_serializer_max_time).find("newaccount") != std::string::npos);
|
|
|
|
produce_block();
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_AUTO_TEST_CASE( check_big_deserialization ) try {
|
|
validating_tester t(flat_set<account_name>{}, {}, setup_policy::old_wasm_parser);
|
|
t.produce_blocks(2);
|
|
t.create_accounts( {"cbd"_n} );
|
|
t.produce_block();
|
|
|
|
std::stringstream ss;
|
|
ss << "(module ";
|
|
ss << "(export \"apply\" (func $apply))";
|
|
ss << " (func $apply (param $0 i64)(param $1 i64)(param $2 i64))";
|
|
for(unsigned int i = 0; i < wasm_constraints::maximum_section_elements-2; i++)
|
|
ss << " (func " << "$AA_" << i << ")";
|
|
ss << ")";
|
|
|
|
t.set_code("cbd"_n, ss.str().c_str());
|
|
t.produce_blocks(1);
|
|
|
|
t.produce_blocks(1);
|
|
|
|
ss.str("");
|
|
ss << "(module ";
|
|
ss << "(export \"apply\" (func $apply))";
|
|
ss << " (func $apply (param $0 i64)(param $1 i64)(param $2 i64))";
|
|
for(unsigned int i = 0; i < wasm_constraints::maximum_section_elements; i++)
|
|
ss << " (func " << "$AA_" << i << ")";
|
|
ss << ")";
|
|
|
|
BOOST_CHECK_THROW(t.set_code("cbd"_n, ss.str().c_str()), wasm_serialization_error);
|
|
t.produce_blocks(1);
|
|
|
|
ss.str("");
|
|
ss << "(module ";
|
|
ss << "(export \"apply\" (func $apply))";
|
|
ss << " (func $apply (param $0 i64)(param $1 i64)(param $2 i64))";
|
|
ss << " (func $aa ";
|
|
for(unsigned int i = 0; i < wasm_constraints::maximum_code_size; i++)
|
|
ss << " (drop (i32.const 3))";
|
|
ss << "))";
|
|
|
|
BOOST_CHECK_THROW(t.set_code("cbd"_n, ss.str().c_str()), fc::assert_exception); // this is caught first by MAX_SIZE_OF_ARRAYS check
|
|
t.produce_blocks(1);
|
|
|
|
ss.str("");
|
|
ss << "(module ";
|
|
ss << "(memory $0 1)";
|
|
ss << "(data (i32.const 20) \"";
|
|
for(unsigned int i = 0; i < wasm_constraints::maximum_func_local_bytes-1; i++)
|
|
ss << 'a';
|
|
ss << "\")";
|
|
ss << "(export \"apply\" (func $apply))";
|
|
ss << " (func $apply (param $0 i64)(param $1 i64)(param $2 i64))";
|
|
ss << " (func $aa ";
|
|
ss << " (drop (i32.const 3))";
|
|
ss << "))";
|
|
|
|
t.set_code("cbd"_n, ss.str().c_str());
|
|
t.produce_blocks(1);
|
|
|
|
ss.str("");
|
|
ss << "(module ";
|
|
ss << "(memory $0 1)";
|
|
ss << "(data (i32.const 20) \"";
|
|
for(unsigned int i = 0; i < wasm_constraints::maximum_func_local_bytes; i++)
|
|
ss << 'a';
|
|
ss << "\")";
|
|
ss << "(export \"apply\" (func $apply))";
|
|
ss << " (func $apply (param $0 i64)(param $1 i64)(param $2 i64))";
|
|
ss << " (func $aa ";
|
|
ss << " (drop (i32.const 3))";
|
|
ss << "))";
|
|
|
|
BOOST_CHECK_THROW(t.set_code("cbd"_n, ss.str().c_str()), wasm_serialization_error);
|
|
t.produce_blocks(1);
|
|
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
|
|
BOOST_FIXTURE_TEST_CASE( check_table_maximum, validating_tester ) try {
|
|
produce_blocks(2);
|
|
create_accounts( {"tbl"_n} );
|
|
produce_block();
|
|
|
|
set_code("tbl"_n, table_checker_wast);
|
|
produce_blocks(1);
|
|
{
|
|
signed_transaction trx;
|
|
action act;
|
|
act.name = name(555ULL<<32 | 0ULL); //top 32 is what we assert against, bottom 32 is indirect call index
|
|
act.account = "tbl"_n;
|
|
act.authorization = vector<permission_level>{{"tbl"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "tbl"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
}
|
|
|
|
produce_blocks(1);
|
|
|
|
{
|
|
signed_transaction trx;
|
|
action act;
|
|
act.name = name(555ULL<<32 | 1022ULL); //top 32 is what we assert against, bottom 32 is indirect call index
|
|
act.account = "tbl"_n;
|
|
act.authorization = vector<permission_level>{{"tbl"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "tbl"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
}
|
|
|
|
produce_blocks(1);
|
|
|
|
{
|
|
signed_transaction trx;
|
|
action act;
|
|
act.name = name(7777ULL<<32 | 1023ULL); //top 32 is what we assert against, bottom 32 is indirect call index
|
|
act.account = "tbl"_n;
|
|
act.authorization = vector<permission_level>{{"tbl"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "tbl"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
}
|
|
|
|
produce_blocks(1);
|
|
|
|
{
|
|
signed_transaction trx;
|
|
action act;
|
|
act.name = name(7778ULL<<32 | 1023ULL); //top 32 is what we assert against, bottom 32 is indirect call index
|
|
act.account = "tbl"_n;
|
|
act.authorization = vector<permission_level>{{"tbl"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "tbl"_n, "active" ), control->get_chain_id());
|
|
|
|
//should fail, a check to make sure assert() in wasm is being evaluated correctly
|
|
BOOST_CHECK_THROW(push_transaction(trx), eosio_assert_message_exception);
|
|
}
|
|
|
|
produce_blocks(1);
|
|
|
|
{
|
|
signed_transaction trx;
|
|
action act;
|
|
act.name = name(133ULL<<32 | 5ULL); //top 32 is what we assert against, bottom 32 is indirect call index
|
|
act.account = "tbl"_n;
|
|
act.authorization = vector<permission_level>{{"tbl"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "tbl"_n, "active" ), control->get_chain_id());
|
|
|
|
//should fail, this element index (5) does not exist
|
|
BOOST_CHECK_THROW(push_transaction(trx), eosio::chain::wasm_execution_error);
|
|
}
|
|
|
|
produce_blocks(1);
|
|
|
|
{
|
|
signed_transaction trx;
|
|
action act;
|
|
act.name = name(eosio::chain::wasm_constraints::maximum_table_elements+54334);
|
|
act.account = "tbl"_n;
|
|
act.authorization = vector<permission_level>{{"tbl"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "tbl"_n, "active" ), control->get_chain_id());
|
|
|
|
//should fail, this element index is out of range
|
|
BOOST_CHECK_THROW(push_transaction(trx), eosio::chain::wasm_execution_error);
|
|
}
|
|
|
|
produce_blocks(1);
|
|
|
|
//run a few tests with new, proper syntax, call_indirect
|
|
set_code("tbl"_n, table_checker_proper_syntax_wast);
|
|
produce_blocks(1);
|
|
|
|
{
|
|
signed_transaction trx;
|
|
action act;
|
|
act.name = name(555ULL<<32 | 1022ULL); //top 32 is what we assert against, bottom 32 is indirect call index
|
|
act.account = "tbl"_n;
|
|
act.authorization = vector<permission_level>{{"tbl"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "tbl"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
}
|
|
|
|
produce_blocks(1);
|
|
{
|
|
signed_transaction trx;
|
|
action act;
|
|
act.name = name(7777ULL<<32 | 1023ULL); //top 32 is what we assert against, bottom 32 is indirect call index
|
|
act.account = "tbl"_n;
|
|
act.authorization = vector<permission_level>{{"tbl"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "tbl"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
}
|
|
set_code("tbl"_n, table_checker_small_wast);
|
|
produce_blocks(1);
|
|
|
|
{
|
|
signed_transaction trx;
|
|
action act;
|
|
act.name = name(888ULL);
|
|
act.account = "tbl"_n;
|
|
act.authorization = vector<permission_level>{{"tbl"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "tbl"_n, "active" ), control->get_chain_id());
|
|
|
|
//an element that is out of range and has no mmap access permission either (should be a trapped segv)
|
|
BOOST_CHECK_EXCEPTION(push_transaction(trx), eosio::chain::wasm_execution_error, [](const eosio::chain::wasm_execution_error &e) {return true;});
|
|
}
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_FIXTURE_TEST_CASE( protected_globals, validating_tester ) try {
|
|
produce_blocks(2);
|
|
|
|
create_accounts( {"gob"_n} );
|
|
produce_block();
|
|
|
|
BOOST_CHECK_THROW(set_code("gob"_n, global_protection_none_get_wast), fc::exception);
|
|
produce_blocks(1);
|
|
|
|
BOOST_CHECK_THROW(set_code("gob"_n, global_protection_some_get_wast), fc::exception);
|
|
produce_blocks(1);
|
|
|
|
BOOST_CHECK_THROW(set_code("gob"_n, global_protection_none_set_wast), fc::exception);
|
|
produce_blocks(1);
|
|
|
|
BOOST_CHECK_THROW(set_code("gob"_n, global_protection_some_set_wast), fc::exception);
|
|
produce_blocks(1);
|
|
|
|
//sanity to make sure I got general binary construction okay
|
|
set_code("gob"_n, global_protection_okay_get_wasm);
|
|
produce_blocks(1);
|
|
|
|
BOOST_CHECK_THROW(set_code("gob"_n, global_protection_none_get_wasm), fc::exception);
|
|
produce_blocks(1);
|
|
|
|
BOOST_CHECK_THROW(set_code("gob"_n, global_protection_some_get_wasm), fc::exception);
|
|
produce_blocks(1);
|
|
|
|
set_code("gob"_n, global_protection_okay_set_wasm);
|
|
produce_blocks(1);
|
|
|
|
BOOST_CHECK_THROW(set_code("gob"_n, global_protection_some_set_wasm), fc::exception);
|
|
produce_blocks(1);
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_FIXTURE_TEST_CASE( apply_export_and_signature, validating_tester ) try {
|
|
produce_blocks(2);
|
|
create_accounts( {"bbb"_n} );
|
|
produce_block();
|
|
|
|
BOOST_CHECK_THROW(set_code("bbb"_n, no_apply_wast), fc::exception);
|
|
produce_blocks(1);
|
|
|
|
BOOST_CHECK_THROW(set_code("bbb"_n, no_apply_2_wast), fc::exception);
|
|
produce_blocks(1);
|
|
|
|
BOOST_CHECK_THROW(set_code("bbb"_n, no_apply_3_wast), fc::exception);
|
|
produce_blocks(1);
|
|
|
|
BOOST_CHECK_THROW(set_code("bbb"_n, apply_wrong_signature_wast), fc::exception);
|
|
produce_blocks(1);
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_FIXTURE_TEST_CASE( trigger_serialization_errors, validating_tester) try {
|
|
produce_blocks(2);
|
|
const vector<uint8_t> proper_wasm = { 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0d, 0x02, 0x60, 0x03, 0x7f, 0x7f, 0x7f,
|
|
0x00, 0x60, 0x03, 0x7e, 0x7e, 0x7e, 0x00, 0x02, 0x0e, 0x01, 0x03, 0x65, 0x6e, 0x76, 0x06, 0x73,
|
|
0x68, 0x61, 0x32, 0x35, 0x36, 0x00, 0x00, 0x03, 0x02, 0x01, 0x01, 0x04, 0x04, 0x01, 0x70, 0x00,
|
|
0x00, 0x05, 0x03, 0x01, 0x00, 0x20, 0x07, 0x09, 0x01, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x00,
|
|
0x01, 0x0a, 0x0c, 0x01, 0x0a, 0x00, 0x41, 0x04, 0x41, 0x05, 0x41, 0x10, 0x10, 0x00, 0x0b, 0x0b,
|
|
0x0b, 0x01, 0x00, 0x41, 0x04, 0x0b, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f };
|
|
|
|
const vector<uint8_t> malformed_wasm = { 0x00, 0x61, 0x03, 0x0d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0d, 0x02, 0x60, 0x03, 0x7f, 0x7f, 0x7f,
|
|
0x00, 0x60, 0x03, 0x7e, 0x7e, 0x7e, 0x00, 0x02, 0x0e, 0x01, 0x03, 0x65, 0x6e, 0x76, 0x06, 0x73,
|
|
0x68, 0x61, 0x32, 0x38, 0x36, 0x00, 0x00, 0x03, 0x03, 0x01, 0x01, 0x04, 0x04, 0x01, 0x70, 0x00,
|
|
0x00, 0x05, 0x03, 0x01, 0x00, 0x20, 0x07, 0x09, 0x01, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x00,
|
|
0x01, 0x0a, 0x0c, 0x01, 0x0a, 0x00, 0x41, 0x04, 0x41, 0x05, 0x41, 0x10, 0x10, 0x00, 0x0b, 0x0b,
|
|
0x0b, 0x01, 0x00, 0x41, 0x04, 0x0b, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f };
|
|
|
|
create_accounts( {"bbb"_n} );
|
|
produce_block();
|
|
|
|
set_code("bbb"_n, proper_wasm);
|
|
BOOST_CHECK_THROW(set_code("bbb"_n, malformed_wasm), wasm_serialization_error);
|
|
produce_blocks(1);
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_FIXTURE_TEST_CASE( protect_injected, validating_tester ) try {
|
|
produce_blocks(2);
|
|
|
|
create_accounts( {"inj"_n} );
|
|
produce_block();
|
|
|
|
BOOST_CHECK_THROW(set_code("inj"_n, import_injected_wast), fc::exception);
|
|
produce_blocks(1);
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_FIXTURE_TEST_CASE( import_signature, validating_tester ) try {
|
|
produce_blocks(2);
|
|
|
|
create_accounts( {"imp"_n} );
|
|
produce_block();
|
|
|
|
BOOST_CHECK_THROW(set_code("imp"_n, import_wrong_signature_wast), wasm_exception);
|
|
produce_blocks(1);
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_FIXTURE_TEST_CASE( mem_growth_memset, validating_tester ) try {
|
|
produce_blocks(2);
|
|
|
|
create_accounts( {"grower"_n} );
|
|
produce_block();
|
|
|
|
action act;
|
|
act.account = "grower"_n;
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{"grower"_n,config::active_name}};
|
|
|
|
set_code("grower"_n, memory_growth_memset_store);
|
|
{
|
|
signed_transaction trx;
|
|
trx.actions.push_back(act);
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "grower"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
}
|
|
|
|
produce_blocks(1);
|
|
set_code("grower"_n, memory_growth_memset_test);
|
|
{
|
|
signed_transaction trx;
|
|
trx.actions.push_back(act);
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "grower"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
}
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
INCBIN(fuzz1, "fuzz1.wasm");
|
|
INCBIN(fuzz2, "fuzz2.wasm");
|
|
INCBIN(fuzz3, "fuzz3.wasm");
|
|
INCBIN(fuzz4, "fuzz4.wasm");
|
|
INCBIN(fuzz5, "fuzz5.wasm");
|
|
INCBIN(fuzz6, "fuzz6.wasm");
|
|
INCBIN(fuzz7, "fuzz7.wasm");
|
|
INCBIN(fuzz8, "fuzz8.wasm");
|
|
INCBIN(fuzz9, "fuzz9.wasm");
|
|
INCBIN(fuzz10, "fuzz10.wasm");
|
|
INCBIN(fuzz11, "fuzz11.wasm");
|
|
INCBIN(fuzz12, "fuzz12.wasm");
|
|
INCBIN(fuzz13, "fuzz13.wasm");
|
|
INCBIN(fuzz14, "fuzz14.wasm");
|
|
INCBIN(fuzz15, "fuzz15.wasm");
|
|
//INCBIN(fuzz13, "fuzz13.wasm");
|
|
INCBIN(big_allocation, "big_allocation.wasm");
|
|
INCBIN(crash_section_size_too_big, "crash_section_size_too_big.wasm");
|
|
INCBIN(leak_no_destructor, "leak_no_destructor.wasm");
|
|
INCBIN(leak_readExports, "leak_readExports.wasm");
|
|
INCBIN(leak_readFunctions, "leak_readFunctions.wasm");
|
|
INCBIN(leak_readFunctions_2, "leak_readFunctions_2.wasm");
|
|
INCBIN(leak_readFunctions_3, "leak_readFunctions_3.wasm");
|
|
INCBIN(leak_readGlobals, "leak_readGlobals.wasm");
|
|
INCBIN(leak_readImports, "leak_readImports.wasm");
|
|
INCBIN(leak_wasm_binary_cpp_L1249, "leak_wasm_binary_cpp_L1249.wasm");
|
|
INCBIN(readFunctions_slowness_out_of_memory, "readFunctions_slowness_out_of_memory.wasm");
|
|
INCBIN(locals_yc, "locals-yc.wasm");
|
|
INCBIN(locals_s, "locals-s.wasm");
|
|
INCBIN(slowwasm_localsets, "slowwasm_localsets.wasm");
|
|
INCBIN(getcode_deepindent, "getcode_deepindent.wasm");
|
|
INCBIN(indent_mismatch, "indent-mismatch.wasm");
|
|
INCBIN(deep_loops_ext_report, "deep_loops_ext_report.wasm");
|
|
INCBIN(80k_deep_loop_with_ret, "80k_deep_loop_with_ret.wasm");
|
|
INCBIN(80k_deep_loop_with_void, "80k_deep_loop_with_void.wasm");
|
|
|
|
BOOST_FIXTURE_TEST_CASE( fuzz, validating_tester ) try {
|
|
produce_blocks(2);
|
|
|
|
create_accounts( {"fuzzy"_n} );
|
|
produce_block();
|
|
|
|
{
|
|
vector<uint8_t> wasm(gfuzz1Data, gfuzz1Data + gfuzz1Size);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), fc::exception);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gfuzz2Data, gfuzz2Data + gfuzz2Size);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), fc::exception);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gfuzz3Data, gfuzz3Data + gfuzz3Size);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), fc::exception);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gfuzz4Data, gfuzz4Data + gfuzz4Size);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), fc::exception);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gfuzz5Data, gfuzz5Data + gfuzz5Size);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), fc::exception);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gfuzz6Data, gfuzz6Data + gfuzz6Size);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), fc::exception);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gfuzz7Data, gfuzz7Data + gfuzz7Size);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), fc::exception);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gfuzz8Data, gfuzz8Data + gfuzz8Size);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), fc::exception);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gfuzz9Data, gfuzz9Data + gfuzz9Size);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), fc::exception);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gfuzz10Data, gfuzz10Data + gfuzz10Size);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), fc::exception);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gfuzz11Data, gfuzz11Data + gfuzz11Size);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), fc::exception);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gfuzz12Data, gfuzz12Data + gfuzz12Size);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), fc::exception);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gfuzz13Data, gfuzz13Data + gfuzz13Size);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), fc::exception);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gfuzz14Data, gfuzz14Data + gfuzz14Size);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), fc::exception);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gfuzz15Data, gfuzz15Data + gfuzz15Size);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), fc::exception);
|
|
}
|
|
/* TODO: update wasm to have apply(...) then call, claim is that this
|
|
* takes 1.6 seconds under wavm...
|
|
{
|
|
auto start = fc::time_point::now();
|
|
vector<uint8_t> wasm(gfuzz13Data, gfuzz13Data + gfuzz13Size);
|
|
set_code("fuzzy"_n, wasm);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), fc::exception);
|
|
auto end = fc::time_point::now();
|
|
edump((end-start));
|
|
}
|
|
*/
|
|
|
|
{
|
|
vector<uint8_t> wasm(gbig_allocationData, gbig_allocationData + gbig_allocationSize);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), wasm_serialization_error);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gcrash_section_size_too_bigData, gcrash_section_size_too_bigData + gcrash_section_size_too_bigSize);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), wasm_serialization_error);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gleak_no_destructorData, gleak_no_destructorData + gleak_no_destructorSize);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), wasm_serialization_error);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gleak_readExportsData, gleak_readExportsData + gleak_readExportsSize);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), wasm_serialization_error);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gleak_readFunctionsData, gleak_readFunctionsData + gleak_readFunctionsSize);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), wasm_serialization_error);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gleak_readFunctions_2Data, gleak_readFunctions_2Data + gleak_readFunctions_2Size);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), wasm_serialization_error);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gleak_readFunctions_3Data, gleak_readFunctions_3Data + gleak_readFunctions_3Size);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), wasm_serialization_error);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gleak_readGlobalsData, gleak_readGlobalsData + gleak_readGlobalsSize);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), wasm_serialization_error);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gleak_readImportsData, gleak_readImportsData + gleak_readImportsSize);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), wasm_serialization_error);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gleak_wasm_binary_cpp_L1249Data, gleak_wasm_binary_cpp_L1249Data + gleak_wasm_binary_cpp_L1249Size);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), wasm_serialization_error);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(greadFunctions_slowness_out_of_memoryData, greadFunctions_slowness_out_of_memoryData + greadFunctions_slowness_out_of_memorySize);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), wasm_serialization_error);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(glocals_ycData, glocals_ycData + glocals_ycSize);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), wasm_serialization_error);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(glocals_sData, glocals_sData + glocals_sSize);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), wasm_serialization_error);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gslowwasm_localsetsData, gslowwasm_localsetsData + gslowwasm_localsetsSize);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), wasm_serialization_error);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gdeep_loops_ext_reportData, gdeep_loops_ext_reportData + gdeep_loops_ext_reportSize);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), wasm_exception);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(g80k_deep_loop_with_retData, g80k_deep_loop_with_retData + g80k_deep_loop_with_retSize);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), wasm_exception);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(g80k_deep_loop_with_voidData, g80k_deep_loop_with_voidData + g80k_deep_loop_with_voidSize);
|
|
BOOST_CHECK_THROW(set_code("fuzzy"_n, wasm), wasm_exception);
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(ggetcode_deepindentData, ggetcode_deepindentData + ggetcode_deepindentSize);
|
|
set_code( "fuzzy"_n, wasm );
|
|
}
|
|
{
|
|
vector<uint8_t> wasm(gindent_mismatchData, gindent_mismatchData + gindent_mismatchSize);
|
|
set_code( "fuzzy"_n, wasm );
|
|
}
|
|
|
|
produce_blocks(1);
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_FIXTURE_TEST_CASE( big_maligned_host_ptr, validating_tester ) try {
|
|
produce_blocks(2);
|
|
create_accounts( {"bigmaligned"_n} );
|
|
produce_block();
|
|
|
|
string large_maligned_host_ptr_wast_f = fc::format_string(large_maligned_host_ptr, fc::mutable_variant_object()
|
|
("MAX_WASM_PAGES", eosio::chain::wasm_constraints::maximum_linear_memory/(64*1024))
|
|
("MAX_NAME_ARRAY", (eosio::chain::wasm_constraints::maximum_linear_memory-1)/sizeof(chain::account_name)));
|
|
|
|
set_code("bigmaligned"_n, large_maligned_host_ptr_wast_f.c_str());
|
|
produce_blocks(1);
|
|
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "bigmaligned"_n;
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{"bigmaligned"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "bigmaligned"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
produce_blocks(1);
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_DATA_TEST_CASE( depth_tests, bdata::make({setup_policy::preactivate_feature_and_new_bios, setup_policy::old_wasm_parser, setup_policy::full}), policy ) try {
|
|
validating_tester t(flat_set<account_name>{}, {}, policy);
|
|
if(policy != setup_policy::full)
|
|
t.preactivate_builtin_protocol_features({builtin_protocol_feature_t::configurable_wasm_limits});
|
|
|
|
t.produce_block();
|
|
t.create_accounts( {"depth"_n} );
|
|
t.produce_block();
|
|
|
|
signed_transaction trx;
|
|
trx.actions.emplace_back(vector<permission_level>{{"depth"_n,config::active_name}}, "depth"_n, ""_n, bytes{});
|
|
trx.actions[0].authorization = vector<permission_level>{{"depth"_n,config::active_name}};
|
|
|
|
auto pushit = [&]() {
|
|
t.produce_block();
|
|
trx.signatures.clear();
|
|
t.set_transaction_headers(trx);
|
|
trx.sign(validating_tester::get_private_key("depth"_n, "active"), t.control->get_chain_id());
|
|
t.push_transaction(trx);
|
|
};
|
|
|
|
//strictly wasm recursion to maximum_call_depth & maximum_call_depth+1
|
|
string wasm_depth_okay = fc::format_string(depth_assert_wasm, fc::mutable_variant_object()
|
|
("MAX_DEPTH", eosio::chain::wasm_constraints::maximum_call_depth));
|
|
t.set_code("depth"_n, wasm_depth_okay.c_str());
|
|
pushit();
|
|
|
|
string wasm_depth_one_over = fc::format_string(depth_assert_wasm, fc::mutable_variant_object()
|
|
("MAX_DEPTH", eosio::chain::wasm_constraints::maximum_call_depth+1));
|
|
t.set_code("depth"_n, wasm_depth_one_over.c_str());
|
|
BOOST_CHECK_THROW(pushit(), wasm_execution_error);
|
|
|
|
//wasm recursion but call an intrinsic as the last function instead
|
|
string intrinsic_depth_okay = fc::format_string(depth_assert_intrinsic, fc::mutable_variant_object()
|
|
("MAX_DEPTH", eosio::chain::wasm_constraints::maximum_call_depth));
|
|
t.set_code("depth"_n, intrinsic_depth_okay.c_str());
|
|
pushit();
|
|
|
|
string intrinsic_depth_one_over = fc::format_string(depth_assert_intrinsic, fc::mutable_variant_object()
|
|
("MAX_DEPTH", eosio::chain::wasm_constraints::maximum_call_depth+1));
|
|
t.set_code("depth"_n, intrinsic_depth_one_over.c_str());
|
|
BOOST_CHECK_THROW(pushit(), wasm_execution_error);
|
|
|
|
//add a float operation in the mix to ensure any injected softfloat call doesn't count against limit
|
|
string wasm_float_depth_okay = fc::format_string(depth_assert_wasm_float, fc::mutable_variant_object()
|
|
("MAX_DEPTH", eosio::chain::wasm_constraints::maximum_call_depth));
|
|
t.set_code("depth"_n, wasm_float_depth_okay.c_str());
|
|
pushit();
|
|
|
|
string wasm_float_depth_one_over = fc::format_string(depth_assert_wasm_float, fc::mutable_variant_object()
|
|
("MAX_DEPTH", eosio::chain::wasm_constraints::maximum_call_depth+1));
|
|
t.set_code("depth"_n, wasm_float_depth_one_over.c_str());
|
|
BOOST_CHECK_THROW(pushit(), wasm_execution_error);
|
|
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_AUTO_TEST_CASE( varuint_memory_flags_tests ) try {
|
|
validating_tester t(flat_set<account_name>{}, {}, setup_policy::preactivate_feature_and_new_bios);
|
|
t.produce_block();
|
|
|
|
t.create_accounts( {"memflags"_n} );
|
|
t.produce_block();
|
|
|
|
t.set_code("memflags"_n, varuint_memory_flags);
|
|
t.produce_block();
|
|
|
|
{
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "memflags"_n;
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{"memflags"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
t.set_transaction_headers(trx);
|
|
trx.sign(validating_tester::get_private_key( "memflags"_n, "active" ), t.control->get_chain_id());
|
|
t.push_transaction(trx);
|
|
t.produce_block();
|
|
}
|
|
|
|
// Activate new parser
|
|
t.preactivate_builtin_protocol_features({builtin_protocol_feature_t::configurable_wasm_limits});
|
|
t.produce_block();
|
|
|
|
// We should still be able to execute the old code
|
|
{
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "memflags"_n;
|
|
act.name = ""_n;
|
|
act.authorization = vector<permission_level>{{"memflags"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
t.set_transaction_headers(trx);
|
|
trx.sign(validating_tester::get_private_key( "memflags"_n, "active" ), t.control->get_chain_id());
|
|
t.push_transaction(trx);
|
|
t.produce_block();
|
|
}
|
|
|
|
t.set_code("memflags"_n, std::vector<uint8_t>{});
|
|
BOOST_REQUIRE_THROW(t.set_code("memflags"_n, varuint_memory_flags), wasm_exception);
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
static char reset_memory_fail1_wast[] = R"======(
|
|
(module
|
|
(memory 2)
|
|
(func (export "apply") (param i64 i64 i64))
|
|
)
|
|
)======";
|
|
|
|
// In a previous version of eos-vm, this would leave
|
|
// memory incorrectly accessible to the next action.
|
|
static char reset_memory_fail2_wast[] = R"======(
|
|
(module
|
|
(memory 2)
|
|
(table 1 anyfunc)
|
|
(func $apply (export "apply") (param i64 i64 i64))
|
|
(elem (i32.const 1) $apply)
|
|
)
|
|
)======";
|
|
|
|
static char reset_memory_fail3_wast[] = R"======(
|
|
(module
|
|
(memory 1)
|
|
(func (export "apply") (param i64 i64 i64)
|
|
(i64.store (i32.const 65536) (i64.const 0))
|
|
)
|
|
)
|
|
)======";
|
|
|
|
BOOST_FIXTURE_TEST_CASE( reset_memory_fail, validating_tester ) try {
|
|
produce_block();
|
|
create_accounts( {"usemem"_n, "resetmem"_n, "accessmem"_n} );
|
|
produce_block();
|
|
|
|
set_code("usemem"_n, reset_memory_fail1_wast);
|
|
set_code("resetmem"_n, reset_memory_fail2_wast);
|
|
set_code("accessmem"_n, reset_memory_fail3_wast);
|
|
produce_block();
|
|
|
|
auto pushit = [&](name acct) {
|
|
signed_transaction trx;
|
|
trx.actions.push_back({ { { acct, config::active_name } }, acct, ""_n, bytes() });
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( acct, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
};
|
|
pushit("usemem"_n);
|
|
BOOST_CHECK_THROW(pushit("resetmem"_n), wasm_execution_error);
|
|
BOOST_CHECK_THROW(pushit("accessmem"_n), wasm_execution_error);
|
|
produce_block();
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
// TODO: Update to use eos-vm once merged
|
|
BOOST_AUTO_TEST_CASE( code_size ) try {
|
|
using namespace IR;
|
|
using namespace Serialization;
|
|
std::vector<U8> code_start = {
|
|
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x60,
|
|
0x03, 0x7e, 0x7e, 0x7e, 0x00, 0x03, 0x02, 0x01, 0x00, 0x07, 0x09, 0x01,
|
|
0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x00, 0x00, 0x0a, 0x8b, 0x80, 0x80,
|
|
0x0a, 0x01, 0x86, 0x80, 0x80, 0x0a, 0x00
|
|
};
|
|
|
|
std::vector<U8> code_end = { 0x0b };
|
|
|
|
std::vector<U8> code_function_body;
|
|
code_function_body.insert(code_function_body.end(), wasm_constraints::maximum_code_size + 4, 0x01);
|
|
|
|
std::vector<U8> code;
|
|
code.insert(code.end(), code_start.begin(), code_start.end());
|
|
code.insert(code.end(), code_function_body.begin(), code_function_body.end());
|
|
code.insert(code.end(), code_end.begin(), code_end.end());
|
|
|
|
Module module;
|
|
Serialization::MemoryInputStream stream((const U8*)code.data(), code.size());
|
|
BOOST_CHECK_THROW(WASM::serialize(stream, module), FatalSerializationException);
|
|
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_AUTO_TEST_CASE( billed_cpu_test ) try {
|
|
|
|
fc::temp_directory tempdir;
|
|
tester chain( tempdir, true );
|
|
chain.execute_setup_policy( setup_policy::full );
|
|
|
|
const resource_limits_manager& mgr = chain.control->get_resource_limits_manager();
|
|
|
|
account_name acc = "asserter"_n;
|
|
account_name user = "user"_n;
|
|
chain.create_accounts( {acc, user} );
|
|
chain.produce_block();
|
|
|
|
auto create_trx = [&](auto trx_max_ms) {
|
|
signed_transaction trx;
|
|
trx.actions.emplace_back( vector<permission_level>{{acc, config::active_name}},
|
|
assertdef {1, "Should Not Assert!"} );
|
|
static int num_secs = 1;
|
|
chain.set_transaction_headers( trx, ++num_secs ); // num_secs provides nonce
|
|
trx.max_cpu_usage_ms = trx_max_ms;
|
|
trx.sign( chain.get_private_key( acc, "active" ), chain.control->get_chain_id() );
|
|
auto ptrx = std::make_shared<packed_transaction>(trx);
|
|
auto fut = transaction_metadata::start_recover_keys( ptrx, chain.control->get_thread_pool(), chain.control->get_chain_id(), fc::microseconds::maximum(), transaction_metadata::trx_type::input );
|
|
return fut.get();
|
|
};
|
|
|
|
auto push_trx = [&]( const transaction_metadata_ptr& trx, fc::time_point deadline,
|
|
uint32_t billed_cpu_time_us, bool explicit_billed_cpu_time, uint32_t subjective_cpu_bill_us ) {
|
|
auto r = chain.control->push_transaction( trx, deadline, fc::microseconds::maximum(), billed_cpu_time_us, explicit_billed_cpu_time, subjective_cpu_bill_us );
|
|
if( r->except_ptr ) std::rethrow_exception( r->except_ptr );
|
|
if( r->except ) throw *r->except;
|
|
return r;
|
|
};
|
|
|
|
auto ptrx = create_trx(0);
|
|
// no limits, just verifying trx works
|
|
push_trx( ptrx, fc::time_point::maximum(), 0, false, 0 ); // non-explicit billing
|
|
|
|
// setup account acc with large limits
|
|
chain.push_action( config::system_account_name, "setalimits"_n, config::system_account_name, fc::mutable_variant_object()
|
|
("account", user)
|
|
("ram_bytes", -1)
|
|
("net_weight", 19'999'999)
|
|
("cpu_weight", 19'999'999)
|
|
);
|
|
chain.push_action( config::system_account_name, "setalimits"_n, config::system_account_name, fc::mutable_variant_object()
|
|
("account", acc)
|
|
("ram_bytes", -1)
|
|
("net_weight", 9'999)
|
|
("cpu_weight", 9'999)
|
|
);
|
|
|
|
chain.produce_block();
|
|
|
|
auto max_cpu_time_us = chain.control->get_global_properties().configuration.max_transaction_cpu_usage;
|
|
auto min_cpu_time_us = chain.control->get_global_properties().configuration.min_transaction_cpu_usage;
|
|
|
|
auto cpu_limit = mgr.get_account_cpu_limit(acc).first; // huge limit ~17s
|
|
|
|
ptrx = create_trx(0);
|
|
BOOST_CHECK_LT( max_cpu_time_us, cpu_limit ); // max_cpu_time_us has to be less than cpu_limit to actually test max and not account
|
|
// indicate explicit billing at transaction max, max_cpu_time_us has to be greater than account cpu time
|
|
push_trx( ptrx, fc::time_point::maximum(), max_cpu_time_us, true, 0 );
|
|
chain.produce_block();
|
|
|
|
cpu_limit = mgr.get_account_cpu_limit(acc).first;
|
|
|
|
// do not allow to bill greater than chain configured max, objective failure even with explicit billing for over max
|
|
ptrx = create_trx(0);
|
|
BOOST_CHECK_LT( max_cpu_time_us + 1, cpu_limit ); // max_cpu_time_us+1 has to be less than cpu_limit to actually test max and not account
|
|
// indicate explicit billing at max + 1
|
|
BOOST_CHECK_EXCEPTION( push_trx( ptrx, fc::time_point::maximum(), max_cpu_time_us + 1, true, 0 ), tx_cpu_usage_exceeded,
|
|
[](const tx_cpu_usage_exceeded& e){ fc_exception_message_starts_with starts("billed");
|
|
fc_exception_message_contains contains("reached on chain max_transaction_cpu_usage");
|
|
return starts(e) && contains(e); } );
|
|
|
|
// allow to bill at trx configured max
|
|
ptrx = create_trx(5); // set trx max at 5ms
|
|
BOOST_CHECK_LT( 5 * 1000, cpu_limit ); // 5ms has to be less than cpu_limit to actually test trx max and not account
|
|
// indicate explicit billing at max
|
|
push_trx( ptrx, fc::time_point::maximum(), 5 * 1000, true, 0 );
|
|
chain.produce_block();
|
|
|
|
cpu_limit = mgr.get_account_cpu_limit(acc).first; // update after last trx
|
|
|
|
// do not allow to bill greater than trx configured max, objective failure even with explicit billing for over max
|
|
ptrx = create_trx(5); // set trx max at 5ms
|
|
BOOST_CHECK_LT( 5 * 1000 + 1, cpu_limit ); // 5ms has to be less than cpu_limit to actually test trx max and not account
|
|
// indicate explicit billing at max + 1
|
|
BOOST_CHECK_EXCEPTION( push_trx( ptrx, fc::time_point::maximum(), 5 * 1000 + 1, true, 0 ), tx_cpu_usage_exceeded,
|
|
[](const tx_cpu_usage_exceeded& e){ fc_exception_message_starts_with starts("billed");
|
|
fc_exception_message_contains contains("reached trx specified max_cpu_usage_ms");
|
|
return starts(e) && contains(e); } );
|
|
|
|
// bill at minimum
|
|
ptrx = create_trx(0);
|
|
// indicate explicit billing at transaction minimum
|
|
push_trx( ptrx, fc::time_point::maximum(), min_cpu_time_us, true, 0 );
|
|
chain.produce_block();
|
|
|
|
// do not allow to bill less than minimum
|
|
ptrx = create_trx(0);
|
|
// indicate explicit billing at minimum-1, objective failure even with explicit billing for under min
|
|
BOOST_CHECK_EXCEPTION( push_trx( ptrx, fc::time_point::maximum(), min_cpu_time_us - 1, true, 0 ), transaction_exception,
|
|
fc_exception_message_starts_with("cannot bill CPU time less than the minimum") );
|
|
|
|
chain.push_action( config::system_account_name, "setalimits"_n, config::system_account_name, fc::mutable_variant_object()
|
|
("account", acc)
|
|
("ram_bytes", -1)
|
|
("net_weight", 75)
|
|
("cpu_weight", 75) // ~130ms
|
|
);
|
|
|
|
chain.produce_block();
|
|
chain.produce_block( fc::days(1) ); // produce for one day to reset account cpu
|
|
|
|
cpu_limit = mgr.get_account_cpu_limit_ex(acc).first.max;
|
|
cpu_limit -= EOS_PERCENT( cpu_limit, 10 * config::percent_1 ); // transaction_context verifies within 10%, so subtract 10% out
|
|
|
|
ptrx = create_trx(0);
|
|
BOOST_CHECK_LT( cpu_limit, max_cpu_time_us );
|
|
// indicate non-explicit billing at one less than our account cpu limit, will allow this trx to run, but only bills for actual use
|
|
auto r = push_trx( ptrx, fc::time_point::maximum(), cpu_limit-1, false, 0 );
|
|
BOOST_CHECK_LT( r->receipt->cpu_usage_us, cpu_limit-1 ); // verify not billed at provided bill amount when explicit_billed_cpu_time=false
|
|
|
|
chain.produce_block();
|
|
chain.produce_block( fc::days(1) ); // produce for one day to reset account cpu
|
|
|
|
ptrx = create_trx(0);
|
|
BOOST_CHECK_LT( cpu_limit+1, max_cpu_time_us ); // needs to be less or this just tests the same thing as max_cpu_time_us test above
|
|
// indicate explicit billing at over our account cpu limit, not allowed
|
|
cpu_limit = mgr.get_account_cpu_limit_ex(acc).first.max;
|
|
BOOST_CHECK_EXCEPTION( push_trx( ptrx, fc::time_point::maximum(), cpu_limit+1, true, 0 ), tx_cpu_usage_exceeded,
|
|
[](const tx_cpu_usage_exceeded& e){ fc_exception_message_starts_with starts("billed");
|
|
fc_exception_message_contains contains("reached account cpu limit");
|
|
return starts(e) && contains(e); } );
|
|
|
|
// leeway and subjective billing interaction tests
|
|
auto leeway = fc::microseconds(config::default_subjective_cpu_leeway_us);
|
|
chain.control->set_subjective_cpu_leeway(leeway);
|
|
|
|
// Allow transaction with billed cpu less than 90% of (account cpu limit + leeway - subjective bill)
|
|
chain.produce_block();
|
|
chain.produce_block( fc::days(1) ); // produce for one day to reset account cpu
|
|
ptrx = create_trx(0);
|
|
uint32_t combined_cpu_limit = mgr.get_account_cpu_limit_ex(acc).first.max + leeway.count();
|
|
uint32_t subjective_cpu_bill_us = leeway.count();
|
|
uint32_t billed_cpu_time_us = EOS_PERCENT( (combined_cpu_limit - subjective_cpu_bill_us), 89 *config::percent_1 );
|
|
push_trx( ptrx, fc::time_point::maximum(), billed_cpu_time_us, false, subjective_cpu_bill_us );
|
|
|
|
// Allow transaction with billed cpu less than 90% of (account cpu limit + leeway) if subject bill is 0
|
|
chain.control->set_subjective_cpu_leeway(leeway);
|
|
chain.produce_block();
|
|
chain.produce_block( fc::days(1) ); // produce for one day to reset account cpu
|
|
ptrx = create_trx(0);
|
|
combined_cpu_limit = mgr.get_account_cpu_limit_ex(acc).first.max + leeway.count();
|
|
subjective_cpu_bill_us = 0;
|
|
billed_cpu_time_us = EOS_PERCENT( combined_cpu_limit - subjective_cpu_bill_us, 89 *config::percent_1 );
|
|
push_trx( ptrx, fc::time_point::maximum(), billed_cpu_time_us, false, subjective_cpu_bill_us );
|
|
|
|
// Disallow transaction with billed cpu equal to 90% of (account cpu limit + leeway - subjective bill)
|
|
chain.produce_block();
|
|
chain.produce_block( fc::days(1) ); // produce for one day to reset account cpu
|
|
ptrx = create_trx(0);
|
|
cpu_limit = mgr.get_account_cpu_limit_ex(acc).first.max;
|
|
combined_cpu_limit = cpu_limit + leeway.count();
|
|
subjective_cpu_bill_us = cpu_limit;
|
|
billed_cpu_time_us = EOS_PERCENT( combined_cpu_limit - subjective_cpu_bill_us, 90 * config::percent_1 );
|
|
BOOST_CHECK_EXCEPTION(push_trx( ptrx, fc::time_point::maximum(), billed_cpu_time_us, false, subjective_cpu_bill_us ), tx_cpu_usage_exceeded,
|
|
[](const tx_cpu_usage_exceeded& e){ fc_exception_message_starts_with starts("estimated");
|
|
fc_exception_message_contains contains_reached("reached account cpu limit");
|
|
fc_exception_message_contains contains_subjective("with a subjective cpu of");
|
|
return starts(e) && contains_reached(e) && contains_subjective(e); } );
|
|
|
|
// Disallow transaction with billed cpu greater 90% of (account cpu limit + leeway - subjective bill)
|
|
subjective_cpu_bill_us = 0;
|
|
billed_cpu_time_us = EOS_PERCENT( combined_cpu_limit - subjective_cpu_bill_us, 91 * config::percent_1 );
|
|
BOOST_CHECK_EXCEPTION(push_trx( ptrx, fc::time_point::maximum(), billed_cpu_time_us, false, subjective_cpu_bill_us ), tx_cpu_usage_exceeded,
|
|
[](const tx_cpu_usage_exceeded& e){ fc_exception_message_starts_with starts("estimated");
|
|
fc_exception_message_contains contains_reached("reached account cpu limit");
|
|
fc_exception_message_contains contains_subjective("with a subjective cpu of");
|
|
return starts(e) && contains_reached(e) && !contains_subjective(e); } );
|
|
|
|
// Test when cpu limit is 0
|
|
chain.push_action( config::system_account_name, "setalimits"_n, config::system_account_name, fc::mutable_variant_object()
|
|
("account", acc)
|
|
("ram_bytes", -1)
|
|
("net_weight", 75)
|
|
("cpu_weight", 0)
|
|
);
|
|
|
|
chain.produce_block();
|
|
chain.produce_block( fc::days(1) ); // produce for one day to reset account cpu
|
|
|
|
// Allow transaction with billed cpu less than 90% of leeway subjective bill being 0 to run but fail it if no cpu is staked afterwards
|
|
ptrx = create_trx(0);
|
|
subjective_cpu_bill_us = 0;
|
|
billed_cpu_time_us = EOS_PERCENT( leeway.count(), 89 *config::percent_1 );
|
|
BOOST_CHECK_EXCEPTION(push_trx( ptrx, fc::time_point::maximum(), billed_cpu_time_us, false, subjective_cpu_bill_us ), tx_cpu_usage_exceeded,
|
|
[](const tx_cpu_usage_exceeded& e){ fc_exception_message_starts_with starts("billed");
|
|
fc_exception_message_contains contains("reached account cpu limit");
|
|
return starts(e) && contains(e); } );
|
|
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
/**
|
|
* various tests with wasm & 0 pages worth of memory
|
|
*/
|
|
BOOST_FIXTURE_TEST_CASE( zero_memory_pages, validating_tester ) try {
|
|
produce_blocks(2);
|
|
|
|
create_accounts( {"zero"_n} );
|
|
produce_block();
|
|
|
|
signed_transaction trx;
|
|
trx.actions.emplace_back(vector<permission_level>{{"zero"_n,config::active_name}}, "zero"_n, name(), bytes{});
|
|
trx.actions[0].authorization = vector<permission_level>{{"zero"_n,config::active_name}};
|
|
|
|
auto pushit = [&]() {
|
|
produce_block();
|
|
trx.signatures.clear();
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key("zero"_n, "active"), control->get_chain_id());
|
|
push_transaction(trx);
|
|
};
|
|
|
|
//first, let's run another large memory contract just to prime the pump so to catch any
|
|
//memory reinit faults.
|
|
set_code("zero"_n, misaligned_ref_wast);
|
|
pushit();
|
|
|
|
//contract w/ 0 pages that does nothing
|
|
set_code("zero"_n, zero_memory_do_nothing);
|
|
pushit();
|
|
|
|
//memory load with 0 pages of memory
|
|
set_code("zero"_n, zero_memory_load);
|
|
BOOST_CHECK_THROW(pushit(), wasm_execution_error);
|
|
|
|
//do an intrinsic with 0 pages of memory
|
|
set_code("zero"_n, zero_memory_intrinsic);
|
|
BOOST_CHECK_THROW(pushit(), wasm_execution_error);
|
|
|
|
//grow memory from 0 -> 1, should be able to access byte 0 now
|
|
set_code("zero"_n, zero_memory_grow);
|
|
pushit();
|
|
|
|
//grow memory from 0 -> 1, should be unable to access byte 70K
|
|
set_code("zero"_n, zero_memory_grow_hi);
|
|
BOOST_CHECK_THROW(pushit(), wasm_execution_error);
|
|
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_FIXTURE_TEST_CASE( eosio_exit_in_start, validating_tester ) try {
|
|
produce_blocks(2);
|
|
create_accounts( {"startexit"_n} );
|
|
produce_block();
|
|
|
|
set_code("startexit"_n, exit_in_start_wast);
|
|
produce_blocks(1);
|
|
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "startexit"_n;
|
|
act.name = name();
|
|
act.authorization = vector<permission_level>{{"startexit"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "startexit"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
produce_blocks(1);
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
// memory.grow with a negative argument can shrink the available memory.
|
|
BOOST_FIXTURE_TEST_CASE( negative_memory_grow, validating_tester ) try {
|
|
produce_blocks(2);
|
|
|
|
|
|
create_accounts( {"negmemgrow"_n} );
|
|
produce_block();
|
|
|
|
set_code("negmemgrow"_n, negative_memory_grow_wast);
|
|
produce_blocks(1);
|
|
|
|
{
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "negmemgrow"_n;
|
|
act.name = name();
|
|
act.authorization = vector<permission_level>{{"negmemgrow"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "negmemgrow"_n, "active" ), control->get_chain_id());
|
|
push_transaction(trx);
|
|
produce_blocks(1);
|
|
}
|
|
|
|
set_code("negmemgrow"_n, negative_memory_grow_trap_wast);
|
|
produce_block();
|
|
{
|
|
signed_transaction trx;
|
|
action act;
|
|
act.account = "negmemgrow"_n;
|
|
act.name = name();
|
|
act.authorization = vector<permission_level>{{"negmemgrow"_n,config::active_name}};
|
|
trx.actions.push_back(act);
|
|
|
|
set_transaction_headers(trx);
|
|
trx.sign(get_private_key( "negmemgrow"_n, "active" ), control->get_chain_id());
|
|
BOOST_CHECK_THROW(push_transaction(trx), eosio::chain::wasm_execution_error);
|
|
}
|
|
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
// This test is only applicable to Linux
|
|
#if defined(__linux__)
|
|
// Returns the number of memory mappings of the current process. Only works on Linux
|
|
static uint32_t get_num_memory_mappings() {
|
|
std::string maps_file = "/proc/" + std::to_string(getpid()) + "/maps";
|
|
std::ifstream maps_ifs(maps_file);
|
|
if (!maps_ifs.is_open()) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t num_mappings = 0;
|
|
std::string line;
|
|
while (std::getline(maps_ifs, line)) {
|
|
num_mappings++;
|
|
}
|
|
return num_mappings;
|
|
}
|
|
|
|
BOOST_FIXTURE_TEST_CASE( memory_mapping_test, validating_tester ) try {
|
|
static const std::string mem_map_wast_start = R"=====(
|
|
(module
|
|
(export "apply" (func $apply))
|
|
(global i32 (i32.const
|
|
)=====";
|
|
static const std::string mem_map_wast_end = R"=====(
|
|
))
|
|
(func $apply (param i64) (param i64) (param i64)
|
|
)
|
|
)
|
|
)=====";
|
|
static const char* mem_map_abi = R"=====(
|
|
{
|
|
"version": "eosio::abi/1.2",
|
|
"types": [],
|
|
"structs": [{ "name": "dothedew", "base": "", "fields": [] }],
|
|
"actions": [{ "name": "dothedew", "type": "dothedew", "ricardian_contract": ""}],
|
|
"tables": [],
|
|
"ricardian_clauses": []
|
|
}
|
|
)=====";
|
|
|
|
produce_block();
|
|
|
|
auto num_mappings_before = get_num_memory_mappings();
|
|
BOOST_CHECK_GT(num_mappings_before, 0U); // must be able to get number of memory mappings
|
|
|
|
// number of contracts to deploy
|
|
constexpr uint32_t num_contracts = 5000;
|
|
|
|
for (uint32_t i = 1; i < num_contracts; ++i) {
|
|
std::stringstream ss;
|
|
ss << "0x" << std::hex << i << "00000000";
|
|
uint64_t name_value;
|
|
ss >> name_value;
|
|
auto acct = name(name_value);
|
|
create_accounts({acct});
|
|
|
|
std::string contract_wast = mem_map_wast_start + " " + std::to_string(i) + mem_map_wast_end;
|
|
set_code(acct, contract_wast.c_str());
|
|
set_abi(acct, mem_map_abi);
|
|
|
|
push_action(acct, "dothedew"_n, "eosio"_n, {});
|
|
|
|
// do not put too many transactions in a single block
|
|
if (i % 20 == 0 ) {
|
|
produce_block();
|
|
}
|
|
}
|
|
|
|
constexpr uint32_t margin_of_changes = 50;
|
|
auto num_mappings_now = get_num_memory_mappings();
|
|
if (num_mappings_now > num_mappings_before) {
|
|
BOOST_CHECK_LT(num_mappings_now - num_mappings_before, margin_of_changes);
|
|
}
|
|
} FC_LOG_AND_RETHROW()
|
|
#endif // defined(__linux__)
|
|
|
|
BOOST_FIXTURE_TEST_CASE(net_usage_tests, tester ) try {
|
|
int count = 0;
|
|
auto check = [&](int coderepeat, int max_net_usage)-> bool {
|
|
account_name account{"f_tests"_n.to_uint64_t() + (count++) * 16};
|
|
create_accounts({account});
|
|
|
|
std::string code = R"=====(
|
|
(module
|
|
(import "env" "require_auth" (func $require_auth (param i64)))
|
|
(import "env" "eosio_assert" (func $eosio_assert (param i32 i32)))
|
|
(table 0 anyfunc)
|
|
(memory $0 1)
|
|
(export "apply" (func $apply))
|
|
(func $i64_trunc_u_f64 (param $0 f64) (result i64) (i64.trunc_u/f64 (get_local $0)))
|
|
(func $test (param $0 i64))
|
|
(func $apply (param $0 i64)(param $1 i64)(param $2 i64)
|
|
)=====";
|
|
for (int i = 0; i < coderepeat; ++i) {
|
|
code += "(call $test (call $i64_trunc_u_f64 (f64.const 1)))\n";
|
|
}
|
|
code += "))";
|
|
produce_blocks(1);
|
|
signed_transaction trx;
|
|
auto wasm = ::eosio::chain::wast_to_wasm(code);
|
|
trx.actions.emplace_back( vector<permission_level>{{account,config::active_name}},
|
|
setcode{
|
|
.account = account,
|
|
.vmtype = 0,
|
|
.vmversion = 0,
|
|
.code = bytes(wasm.begin(), wasm.end())
|
|
});
|
|
set_transaction_headers(trx);
|
|
if (max_net_usage) trx.max_net_usage_words = max_net_usage;
|
|
trx.sign( get_private_key( account, "active" ), control->get_chain_id() );
|
|
try {
|
|
packed_transaction ptrx(trx);
|
|
push_transaction(ptrx);
|
|
produce_blocks(1);
|
|
return true;
|
|
} catch (tx_net_usage_exceeded &) {
|
|
return false;
|
|
} catch (transaction_exception &) {
|
|
return false;
|
|
}
|
|
};
|
|
BOOST_REQUIRE_EQUAL(true, check(1024, 0)); // default behavior
|
|
BOOST_REQUIRE_EQUAL(false, check(1024, 100)); // transaction max_net_usage too small
|
|
BOOST_REQUIRE_EQUAL(false, check(config::default_max_block_net_usage+1, 0)); // larger than global maximum
|
|
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_FIXTURE_TEST_CASE(weighted_net_usage_tests, tester ) try {
|
|
account_name account = "f_tests"_n;
|
|
account_name acc2 = "acc2"_n;
|
|
create_accounts({account, acc2});
|
|
int ver = 0;
|
|
auto check = [&](int coderepeat)-> bool {
|
|
std::string code = R"=====(
|
|
(module
|
|
(import "env" "require_auth" (func $require_auth (param i64)))
|
|
(import "env" "eosio_assert" (func $eosio_assert (param i32 i32)))
|
|
(table 0 anyfunc)
|
|
(memory $0 1)
|
|
(export "apply" (func $apply))
|
|
(func $i64_trunc_u_f64 (param $0 f64) (result i64) (i64.trunc_u/f64 (get_local $0)))
|
|
(func $test (param $0 i64))
|
|
(func $apply (param $0 i64)(param $1 i64)(param $2 i64)
|
|
)=====";
|
|
for (int i = 0; i < coderepeat; ++i) {
|
|
code += "(call $test (call $i64_trunc_u_f64 (f64.const ";
|
|
code += (char)('0' + ver);
|
|
code += ")))\n";
|
|
}
|
|
code += "))"; ver++;
|
|
produce_blocks(1);
|
|
signed_transaction trx;
|
|
auto wasm = ::eosio::chain::wast_to_wasm(code);
|
|
trx.actions.emplace_back( vector<permission_level>{{account,config::active_name}},
|
|
setcode{
|
|
.account = account,
|
|
.vmtype = 0,
|
|
.vmversion = 0,
|
|
.code = bytes(wasm.begin(), wasm.end())
|
|
});
|
|
set_transaction_headers(trx);
|
|
trx.sign( get_private_key( account, "active" ), control->get_chain_id() );
|
|
try {
|
|
packed_transaction ptrx(trx);
|
|
push_transaction(ptrx );
|
|
produce_blocks(1);
|
|
return true;
|
|
} catch (tx_net_usage_exceeded &) {
|
|
return false;
|
|
}
|
|
};
|
|
BOOST_REQUIRE_EQUAL(true, check(128)); // no limits, should pass
|
|
|
|
resource_limits_manager mgr = control->get_mutable_resource_limits_manager();
|
|
mgr.set_account_limits(account, -1, 1, -1, false); // set weight = 1 for account
|
|
|
|
BOOST_REQUIRE_EQUAL(true, check(128));
|
|
|
|
mgr.set_account_limits(acc2, -1, 100000000, -1, false); // set a big weight for other account
|
|
BOOST_REQUIRE_EQUAL(false, check(128));
|
|
|
|
} FC_LOG_AND_RETHROW()
|
|
|
|
BOOST_AUTO_TEST_SUITE_END()
|