Files
coopos/unittests/wasm_config_tests.cpp
2023-08-03 16:24:19 -04:00

1148 lines
41 KiB
C++

#include <eosio/chain/abi_serializer.hpp>
#include <eosio/testing/tester.hpp>
#include <eosio/chain/wast_to_wasm.hpp>
#include <fc/string.hpp>
#include <fc/variant_object.hpp>
#include <boost/test/data/test_case.hpp>
#include <boost/test/data/monomorphic.hpp>
#include <boost/test/unit_test.hpp>
#include "test_wasts.hpp"
#include <contracts.hpp>
#include <test_contracts.hpp>
using namespace eosio;
using namespace eosio::chain;
using namespace eosio::testing;
using namespace fc;
namespace data = boost::unit_test::data;
namespace {
struct wasm_config_tester : validating_tester {
wasm_config_tester() {
set_abi(config::system_account_name, test_contracts::wasm_config_bios_abi());
set_code(config::system_account_name, test_contracts::wasm_config_bios_wasm());
bios_abi_ser = *get_resolver()(config::system_account_name);
}
void set_wasm_params(const wasm_config& params) {
signed_transaction trx;
trx.actions.emplace_back(vector<permission_level>{{"eosio"_n,config::active_name}}, "eosio"_n, "setwparams"_n,
bios_abi_ser.variant_to_binary("setwparams", fc::mutable_variant_object()("cfg", params),
abi_serializer::create_yield_function( abi_serializer_max_time )));
trx.actions[0].authorization = vector<permission_level>{{"eosio"_n,config::active_name}};
set_transaction_headers(trx);
trx.sign(get_private_key("eosio"_n, "active"), control->get_chain_id());
push_transaction(trx);
}
// Pushes an empty action
void push_action(account_name account) {
signed_transaction trx;
trx.actions.push_back({{{account,config::active_name}}, account, name(), {}});
set_transaction_headers(trx);
trx.sign(get_private_key( account, "active" ), control->get_chain_id());
push_transaction(trx);
}
chain::abi_serializer bios_abi_ser;
};
std::string make_locals_wasm(int n_params, int n_locals, int n_stack)
{
std::stringstream ss;
ss << "(module ";
ss << " (func (export \"apply\") (param i64 i64 i64))";
ss << " (func ";
for(int i = 0; i < n_params; i+=8)
ss << "(param i64)";
for(int i = 0; i < n_locals; i+=8)
ss << "(local i64)";
for(int i = 0; i < n_stack; i+=8)
ss << "(i64.const 0)";
for(int i = 0; i < n_stack; i+=8)
ss << "(drop)";
ss << " )";
ss << ")";
return ss.str();
}
}
BOOST_AUTO_TEST_SUITE(wasm_config_tests)
struct old_wasm_tester : tester {
old_wasm_tester() : tester{setup_policy::old_wasm_parser} {}
};
BOOST_DATA_TEST_CASE_F(wasm_config_tester, max_mutable_global_bytes, data::make({ 4096, 8192 , 16384 }) * data::make({0, 1}), n_globals, oversize) {
produce_block();
create_accounts({"globals"_n});
produce_block();
auto params = genesis_state::default_initial_wasm_configuration;
params.max_mutable_global_bytes = n_globals;
set_wasm_params(params);
std::string code = [&] {
std::ostringstream ss;
ss << "(module ";
ss << " (func $eosio_assert (import \"env\" \"eosio_assert\") (param i32 i32))";
ss << " (memory 1)";
for(int i = 0; i < n_globals + oversize; i += 4)
ss << "(global (mut i32) (i32.const " << i << "))";
ss << " (func (export \"apply\") (param i64 i64 i64)";
for(int i = 0; i < n_globals + oversize; i += 4)
ss << "(call $eosio_assert (i32.eq (get_global " << i/4 << ") (i32.const " << i << ")) (i32.const 0))";
ss << " )";
ss << ")";
return ss.str();
}();
if(oversize) {
BOOST_CHECK_THROW(set_code("globals"_n, code.c_str()), wasm_exception);
produce_block();
} else {
set_code("globals"_n, code.c_str());
push_action("globals"_n);
produce_block();
--params.max_mutable_global_bytes;
set_wasm_params(params);
push_action("globals"_n);
}
}
static const char many_funcs_wast[] = R"=====(
(module
(export "apply" (func 0))
${SECTION}
)
)=====";
static const char one_func[] = "(func (param i64 i64 i64))";
static const char many_types_wast[] = R"=====(
(module
${SECTION}
(export "apply" (func 0))
(func (type 0))
)
)=====";
static const char one_type[] = "(type (func (param i64 i64 i64)))";
static const char many_imports_wast[] = R"=====(
(module
${SECTION}
(func (export "apply") (param i64 i64 i64))
)
)=====";
static const char one_import[] = "(func (import \"env\" \"abort\"))";
static const char many_globals_wast[] = R"=====(
(module
${SECTION}
(func (export "apply") (param i64 i64 i64))
)
)=====";
static const char one_global[] = "(global i32 (i32.const 0))";
static const char many_exports_wast[] = R"=====(
(module
${SECTION}
(func (export "apply") (param i64 i64 i64))
)
)=====";
//static const char one_export[] = "(export \"fn${N}\" (func 0))";
static const char many_elem_wast[] = R"=====(
(module
(table 0 anyfunc)
${SECTION}
(func (export "apply") (param i64 i64 i64))
)
)=====";
static const char one_elem[] = "(elem (i32.const 0))";
static const char many_data_wast[] = R"=====(
(module
(memory 1)
${SECTION}
(func (export "apply") (param i64 i64 i64))
)
)=====";
static const char one_data[] = "(data (i32.const 0))";
BOOST_DATA_TEST_CASE_F(wasm_config_tester, max_section_elements,
data::make({1024, 8192, 16384}) * data::make({0, 1}) *
(data::make({many_funcs_wast, many_types_wast, many_imports_wast, many_globals_wast, many_elem_wast, many_data_wast}) ^
data::make({one_func , one_type , one_import, one_global , one_elem , one_data})),
n_elements, oversize, wast, one_element) {
produce_blocks(2);
create_accounts({"section"_n});
produce_block();
std::string buf;
for(int i = 0; i < n_elements + oversize; ++i) {
buf += one_element;
}
std::string code = fc::format_string(wast, fc::mutable_variant_object("SECTION", buf));
auto params = genesis_state::default_initial_wasm_configuration;
params.max_section_elements = n_elements;
set_wasm_params(params);
if(oversize) {
BOOST_CHECK_THROW(set_code("section"_n, code.c_str()), wasm_exception);
} else {
set_code("section"_n, code.c_str());
push_action("section"_n);
--params.max_section_elements;
set_wasm_params(params);
produce_block();
push_action("section"_n);
produce_block();
set_code("section"_n, vector<uint8_t>{}); // clear existing code
BOOST_CHECK_THROW(set_code("section"_n, code.c_str()), wasm_exception);
}
}
// export has to be formatted slightly differently because export names
// must be unique and apply must be one of the exports.
BOOST_DATA_TEST_CASE_F(wasm_config_tester, max_section_elements_export,
data::make({1024, 8192, 16384}) * data::make({0, 1}),
n_elements, oversize) {
produce_blocks(2);
create_accounts({"section"_n});
produce_block();
std::string buf;
for(int i = 0; i < n_elements + oversize - 1; ++i) {
buf += "(export \"fn$";
buf += std::to_string(i);
buf += "\" (func 0))";
}
std::string code = fc::format_string(many_exports_wast, fc::mutable_variant_object("SECTION", buf));
auto params = genesis_state::default_initial_wasm_configuration;
params.max_section_elements = n_elements;
set_wasm_params(params);
if(oversize) {
BOOST_CHECK_THROW(set_code("section"_n, code.c_str()), wasm_exception);
} else {
set_code("section"_n, code.c_str());
push_action("section"_n);
--params.max_section_elements;
set_wasm_params(params);
produce_block();
push_action("section"_n);
produce_block();
set_code("section"_n, vector<uint8_t>{}); // clear existing code
BOOST_CHECK_THROW(set_code("section"_n, code.c_str()), wasm_exception);
}
}
static const char max_linear_memory_wast[] = R"=====(
(module
(import "env" "eosio_assert" (func $$eosio_assert (param i32 i32)))
(memory 4)
(data (i32.const ${OFFSET}) "\11\22\33\44")
(func (export "apply") (param i64 i64 i64)
(call $$eosio_assert (i32.eq (i32.load (i32.const ${OFFSET})) (i32.const 0x44332211)) (i32.const 0))
)
)
)=====";
BOOST_DATA_TEST_CASE_F(wasm_config_tester, max_linear_memory_init,
data::make({32768, 65536, 86513, 131072}) * data::make({0, 1}),
n_init, oversize) {
produce_blocks(2);
create_accounts({"initdata"_n});
produce_block();
std::string code = fc::format_string(max_linear_memory_wast, fc::mutable_variant_object("OFFSET", n_init + oversize - 4));
auto params = genesis_state::default_initial_wasm_configuration;
params.max_linear_memory_init = n_init;
set_wasm_params(params);
if(oversize) {
BOOST_CHECK_THROW(set_code("initdata"_n, code.c_str()), wasm_exception);
} else {
set_code("initdata"_n, code.c_str());
push_action("initdata"_n);
--params.max_linear_memory_init;
set_wasm_params(params);
produce_block();
push_action("initdata"_n);
produce_block();
set_code("initdata"_n, vector<uint8_t>{}); // clear existing code
BOOST_CHECK_THROW(set_code("initdata"_n, code.c_str()), wasm_exception);
}
}
static const std::vector<std::tuple<int, int, bool, bool>> func_local_params = {
// Default value of max_func_local_bytes
{8192, 0, false, true}, {4096, 4096, false, true}, {0, 8192, false, true},
{8192 + 1, 0, false, false}, {4096 + 1, 4096, false, false}, {0, 8192 + 1, false, false},
// Larger than the default
{16384, 0, true, true}, {8192, 8192, true, true}, {0, 16384, true, true},
{16384 + 1, 0, true, false}, {8192 + 1, 8192, true, false}, {0, 16384 + 1, true, false}
};
BOOST_DATA_TEST_CASE_F(wasm_config_tester, max_func_local_bytes, data::make({0, 8192, 16384}) * data::make(func_local_params), n_params, n_locals, n_stack, set_high, expect_success) {
produce_blocks(2);
create_accounts({"stackz"_n});
produce_block();
auto def_params = genesis_state::default_initial_wasm_configuration;
auto high_params = def_params;
high_params.max_func_local_bytes = 16384;
auto low_params = def_params;
low_params.max_func_local_bytes = 4096;
if(set_high) {
set_wasm_params(high_params);
produce_block();
}
auto pushit = [&]() {
action act;
act.account = "stackz"_n;
act.name = name();
act.authorization = vector<permission_level>{{"stackz"_n,config::active_name}};
signed_transaction trx;
trx.actions.push_back(act);
set_transaction_headers(trx);
trx.sign(get_private_key( "stackz"_n, "active" ), control->get_chain_id());
push_transaction(trx);
};
std::string code = make_locals_wasm(n_params, n_locals, n_stack);
if(expect_success) {
set_code("stackz"_n, code.c_str());
produce_block();
pushit();
set_wasm_params(low_params);
produce_block();
pushit(); // Only checked at set_code.
set_code("stackz"_n, vector<uint8_t>{}); // clear existing code
BOOST_CHECK_THROW(set_code("stackz"_n, code.c_str()), wasm_exception);
produce_block();
} else {
BOOST_CHECK_THROW(set_code("stackz"_n, code.c_str()), wasm_exception);
produce_block();
}
}
BOOST_FIXTURE_TEST_CASE(max_func_local_bytes_mixed, wasm_config_tester) {
produce_blocks(2);
create_accounts({"stackz"_n});
produce_block();
std::string code;
{
std::stringstream ss;
ss << "(module ";
ss << " (func (export \"apply\") (param i64 i64 i64))";
ss << " (func ";
ss << " (local i32 i64 i64 f32 f32 f32 f32 f64 f64 f64 f64 f64 f64 f64 f64)";
for(int i = 0; i < 16; ++i)
ss << "(i32.const 0)";
for(int i = 0; i < 32; ++i)
ss << "(i64.const 0)";
for(int i = 0; i < 64; ++i)
ss << "(f32.const 0)";
for(int i = 0; i < 128; ++i)
ss << "(f64.const 0)";
ss << "(drop)(f64.const 0)";
ss << "(return)";
for(int i = 0; i < 8192; ++i)
ss << "(i64.const 0)";
ss << "(return)";
ss << " )";
ss << ")";
code = ss.str();
}
auto params = genesis_state::default_initial_wasm_configuration;
params.max_func_local_bytes = 4 + 16 + 16 + 64 + 64 + 256 + 256 + 1024;
set_code("stackz"_n, code.c_str());
set_code("stackz"_n, std::vector<uint8_t>{});
produce_block();
--params.max_func_local_bytes;
set_wasm_params(params);
BOOST_CHECK_THROW(set_code("stackz"_n, code.c_str()), wasm_exception);
}
static const std::vector<std::tuple<int, int, bool>> old_func_local_params = {
{8192, 0, true}, {4096, 4096, true}, {0, 8192, true},
{8192 + 1, 0, false}, {4096 + 1, 4096, false}, {0, 8192 + 1, false},
};
BOOST_DATA_TEST_CASE_F(old_wasm_tester, max_func_local_bytes_old, data::make({0, 8192, 16384}) * data::make(old_func_local_params), n_stack, n_params, n_locals, expect_success) {
produce_blocks(2);
create_accounts({"stackz"_n});
produce_block();
auto pushit = [&]() {
action act;
act.account = "stackz"_n;
act.name = name();
act.authorization = vector<permission_level>{{"stackz"_n,config::active_name}};
signed_transaction trx;
trx.actions.push_back(act);
set_transaction_headers(trx);
trx.sign(get_private_key( "stackz"_n, "active" ), control->get_chain_id());
push_transaction(trx);
};
std::string code = make_locals_wasm(n_params, n_locals, n_stack);
if(expect_success) {
set_code("stackz"_n, code.c_str());
produce_block();
pushit();
produce_block();
} else {
BOOST_CHECK_THROW(set_code("stackz"_n, code.c_str()), wasm_exception);
produce_block();
}
}
// Combines max_call_depth and max_func_local_bytes
BOOST_FIXTURE_TEST_CASE(max_stack, wasm_config_tester) {
produce_blocks();
create_accounts({"stackz"_n});
produce_block();
std::string code;
{
std::stringstream ss;
ss << "(module ";
ss << " (func (export \"apply\") (param i64 i64 i64)";
// pad to 8192 bytes
for(int i = 0; i < 2046; ++i)
ss << "(local i32)";
ss << " (get_local 2)"
<< " (i32.wrap/i64)"
<< " (i32.const 0)"
<< " (call 1))";
ss << " (func ";
ss << " (param i32 i32)";
// pad to 8192 bytes
for(int i = 0; i < 2046; ++i)
ss << "(local i32)";
ss << " (if (get_local 0)"
<< " (then"
<< " (get_local 0)"
<< " (i32.const 1)"
<< " (i32.sub)"
<< " (get_local 1)"
<< " (call 1)))";
ss << " )";
ss << ")";
code = ss.str();
}
set_code("stackz"_n, code.c_str());
produce_block();
auto params = genesis_state::default_initial_wasm_configuration;
auto pushit = [&]{
signed_transaction trx;
trx.actions.push_back({{{"stackz"_n,config::active_name}}, "stackz"_n, name(params.max_call_depth - 2), {}});
set_transaction_headers(trx);
trx.sign(get_private_key( "stackz"_n, "active" ), control->get_chain_id());
push_transaction(trx);
};
pushit();
params.max_call_depth = 1024;
set_wasm_params(params);
pushit();
}
BOOST_FIXTURE_TEST_CASE(max_stack_old, old_wasm_tester) {
produce_blocks();
create_accounts({"stackz"_n});
produce_block();
std::string code;
{
std::stringstream ss;
ss << "(module ";
ss << " (func (export \"apply\") (param i64 i64 i64)";
// pad to 8192 bytes
for(int i = 0; i < 2042; ++i)
ss << "(local i32)";
ss << " (call 1 (i32.const 249)))";
ss << " (func ";
ss << " (param i32)";
// pad to 8192 bytes
for(int i = 0; i < 2047; ++i)
ss << "(local i32)";
ss << " (if (get_local 0) (call 1 (i32.sub (get_local 0) (i32.const 1))))";
ss << " )";
ss << ")";
code = ss.str();
}
set_code("stackz"_n, code.c_str());
produce_block();
auto pushit = [&]{
signed_transaction trx;
trx.actions.push_back({{{"stackz"_n,config::active_name}}, "stackz"_n, name(), {}});
set_transaction_headers(trx);
trx.sign(get_private_key( "stackz"_n, "active" ), control->get_chain_id());
push_transaction(trx);
};
pushit();
}
BOOST_FIXTURE_TEST_CASE(max_func_local_bytes_mixed_old, old_wasm_tester) {
produce_blocks(2);
create_accounts({"stackz"_n});
produce_block();
std::string code;
{
std::stringstream ss;
ss << "(module ";
ss << " (func (export \"apply\") (param i64 i64 i64))";
ss << " (func ";
ss << " (param i32 i64 i64 f32 f32 f32 f32 f64 f64 f64 f64 f64 f64 f64 f64)";
for(int i = 0; i < 16; ++i)
ss << "(local i32)";
for(int i = 0; i < 32; ++i)
ss << "(local i64)";
for(int i = 0; i < 64; ++i)
ss << "(local f32)";
for(int i = 0; i < 128; ++i)
ss << "(local f64)";
// pad to 8192 bytes
for(int i = 0; i < 1623; ++i)
ss << "(local i32)";
ss << " )";
ss << ")";
code = ss.str();
}
set_code("stackz"_n, code.c_str());
produce_block();
// add one more parameter
code.replace(code.find("param i32"), 5, "param f32");
BOOST_CHECK_THROW(set_code("stackz"_n, code.c_str()), wasm_exception);
}
BOOST_DATA_TEST_CASE_F(wasm_config_tester, max_table_elements, data::make({512, 2048}) * data::make({0, 1}), max_table_elements, oversize) {
produce_block();
create_accounts( { "table"_n } );
produce_block();
auto pushit = [&]{
signed_transaction trx;
trx.actions.push_back({{{"table"_n,config::active_name}}, "table"_n, name(), {}});
set_transaction_headers(trx);
trx.sign(get_private_key( "table"_n, "active" ), control->get_chain_id());
push_transaction(trx);
};
auto params = genesis_state::default_initial_wasm_configuration;
params.max_table_elements = max_table_elements;
set_wasm_params(params);
std::string code = fc::format_string(variable_table, fc::mutable_variant_object()("TABLE_SIZE", max_table_elements + oversize)("TABLE_OFFSET", max_table_elements - 2));
if(!oversize) {
set_code("table"_n, code.c_str());
pushit();
produce_block();
--params.max_table_elements;
set_wasm_params(params);
pushit();
} else {
BOOST_CHECK_THROW(set_code("table"_n, code.c_str()), wasm_exception);
}
}
BOOST_DATA_TEST_CASE_F(wasm_config_tester, max_nested_structures,
data::make({512, 1024, 2048}) * data::make({0, 1}),
n_nesting, oversize) {
produce_block();
create_accounts( { "nested"_n } );
produce_block();
std::string code = [&]{
std::ostringstream ss;
ss << "(module ";
ss << " (func (export \"apply\") (param i64 i64 i64) ";
for(int i = 0; i < n_nesting + oversize - 1; ++i)
ss << "(block ";
for(int i = 0; i < n_nesting + oversize - 1; ++i)
ss << ")";
ss << " )";
ss << ")";
return ss.str();
}();
auto params = genesis_state::default_initial_wasm_configuration;
params.max_nested_structures = n_nesting;
set_wasm_params(params);
if(oversize) {
BOOST_CHECK_THROW(set_code("nested"_n, code.c_str()), wasm_exception);
} else {
set_code("nested"_n, code.c_str());
push_action("nested"_n);
--params.max_nested_structures;
set_wasm_params(params);
produce_block();
push_action("nested"_n);
produce_block();
set_code("nested"_n, vector<uint8_t>{}); // clear existing code
BOOST_CHECK_THROW(set_code("nested"_n, code.c_str()), wasm_exception);
}
}
static const char max_symbol_func_wast[] = R"=====(
(module
(func (export "apply") (param i64 i64 i64))
(func (export "${NAME}"))
)
)=====";
static const char max_symbol_global_wast[] = R"=====(
(module
(global (export "${NAME}") i32 (i32.const 0))
(func (export "apply") (param i64 i64 i64))
)
)=====";
static const char max_symbol_memory_wast[] = R"=====(
(module
(memory (export "${NAME}") 0)
(func (export "apply") (param i64 i64 i64))
)
)=====";
static const char max_symbol_table_wast[] = R"=====(
(module
(table (export "${NAME}") 0 anyfunc)
(func (export "apply") (param i64 i64 i64))
)
)=====";
BOOST_DATA_TEST_CASE_F( wasm_config_tester, max_symbol_bytes_export, data::make({4096, 8192, 16384}) * data::make({0, 1}) *
data::make({max_symbol_func_wast, max_symbol_global_wast, max_symbol_memory_wast, max_symbol_table_wast}),
n_symbol, oversize, wast ) {
produce_blocks(2);
create_accounts({"bigname"_n});
std::string name(n_symbol + oversize, 'x');
std::string code = fc::format_string(wast, fc::mutable_variant_object("NAME", name));
auto params = genesis_state::default_initial_wasm_configuration;
params.max_symbol_bytes = n_symbol;
set_wasm_params(params);
if(oversize) {
BOOST_CHECK_THROW(set_code("bigname"_n, code.c_str()), wasm_exception);
} else {
set_code("bigname"_n, code.c_str());
push_action("bigname"_n);
--params.max_symbol_bytes;
set_wasm_params(params);
produce_block();
push_action("bigname"_n);
produce_block();
set_code("bigname"_n, vector<uint8_t>{}); // clear existing code
BOOST_CHECK_THROW(set_code("bigname"_n, code.c_str()), wasm_exception);
}
}
static const char max_symbol_import_wast[] = R"=====(
(module
(func (import "env" "db_idx_long_double_find_secondary") (param i64 i64 i64 i32 i32) (result i32))
(func (export "apply") (param i64 i64 i64))
)
)=====";
BOOST_FIXTURE_TEST_CASE( max_symbol_bytes_import, wasm_config_tester ) {
produce_blocks(2);
create_accounts({"bigname"_n});
constexpr int n_symbol = 33;
auto params = genesis_state::default_initial_wasm_configuration;
params.max_symbol_bytes = n_symbol;
set_wasm_params(params);
set_code("bigname"_n, max_symbol_import_wast);
push_action("bigname"_n);
--params.max_symbol_bytes;
set_wasm_params(params);
produce_block();
push_action("bigname"_n);
produce_block();
set_code("bigname"_n, vector<uint8_t>{}); // clear existing code
BOOST_CHECK_THROW(set_code("bigname"_n, max_symbol_import_wast), wasm_exception);
}
static const std::vector<uint8_t> small_contract_wasm{
0x00, 'a', 's', 'm', 0x01, 0x00, 0x00, 0x00,
0x01, 0x07, 0x01, 0x60, 0x03, 0x7e, 0x7e, 0x7e, 0x00,
0x03, 0x02, 0x01, 0x00,
0x07, 0x09, 0x01, 0x05, 'a', 'p', 'p', 'l', 'y', 0x00, 0x00,
0x0a, 0xE3, 0x01, 0x01, 0xE0, 0x01,
0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x0b
};
BOOST_FIXTURE_TEST_CASE( max_module_bytes, wasm_config_tester ) {
produce_blocks(2);
create_accounts({"bigmodule"_n});
const int n_module = small_contract_wasm.size();
auto params = genesis_state::default_initial_wasm_configuration;
params.max_module_bytes = n_module;
set_wasm_params(params);
set_code("bigmodule"_n, small_contract_wasm);
push_action("bigmodule"_n);
--params.max_module_bytes;
set_wasm_params(params);
produce_block();
push_action("bigmodule"_n);
produce_block();
set_code("bigmodule"_n, vector<uint8_t>{}); // clear existing code
BOOST_CHECK_THROW(set_code("bigmodule"_n, small_contract_wasm), wasm_exception);
}
BOOST_FIXTURE_TEST_CASE( max_code_bytes, wasm_config_tester ) {
produce_blocks(2);
create_accounts({"bigcode"_n});
constexpr int n_code = 224;
auto params = genesis_state::default_initial_wasm_configuration;
params.max_code_bytes = n_code;
set_wasm_params(params);
set_code("bigcode"_n, small_contract_wasm);
push_action("bigcode"_n);
--params.max_code_bytes;
set_wasm_params(params);
produce_block();
push_action("bigcode"_n);
produce_block();
set_code("bigcode"_n, vector<uint8_t>{}); // clear existing code
BOOST_CHECK_THROW(set_code("bigcode"_n, small_contract_wasm), wasm_exception);
}
static const char access_biggest_memory_wast[] = R"=====(
(module
(memory 0)
(func (export "apply") (param i64 i64 i64)
(drop (grow_memory (i32.wrap/i64 (get_local 2))))
(i32.store (i32.mul (i32.wrap/i64 (get_local 2)) (i32.const 65536)) (i32.const 0))
)
)
)=====";
static const char intrinsic_biggest_memory_wast[] = R"=====(
(module
(import "env" "memcpy" (func $memcpy (param i32 i32 i32) (result i32)))
(memory 0)
(func (export "apply") (param i64 i64 i64)
(drop (grow_memory (i32.wrap/i64 (get_local 2))))
(drop (call $memcpy (i32.mul (i32.wrap/i64 (get_local 2)) (i32.const 65536)) (i32.const 0) (i32.const 0)))
)
)
)=====";
BOOST_FIXTURE_TEST_CASE( max_pages, wasm_config_tester ) try {
produce_blocks(2);
create_accounts( { "bigmem"_n, "accessmem"_n, "intrinsicmem"_n } );
set_code("accessmem"_n, access_biggest_memory_wast);
set_code("intrinsicmem"_n, intrinsic_biggest_memory_wast);
produce_block();
auto params = genesis_state::default_initial_wasm_configuration;
for(uint64_t max_pages : {600, 400}) { // above and below the default limit
params.max_pages = max_pages;
set_wasm_params(params);
produce_block();
string biggest_memory_wast_f = fc::format_string(biggest_memory_variable_wast, fc::mutable_variant_object(
"MAX_WASM_PAGES", params.max_pages - 1));
set_code("bigmem"_n, biggest_memory_wast_f.c_str());
produce_blocks(1);
auto pushit = [&](uint64_t extra_pages) {
action act;
act.account = "bigmem"_n;
act.name = name(extra_pages);
act.authorization = vector<permission_level>{{"bigmem"_n,config::active_name}};
signed_transaction trx;
trx.actions.push_back(act);
set_transaction_headers(trx);
trx.sign(get_private_key( "bigmem"_n, "active" ), control->get_chain_id());
//but should not be able to grow beyond largest page
push_transaction(trx);
};
// verify that page accessibility cannot leak across wasm executions
auto checkaccess = [&](uint64_t pagenum) {
action act;
act.account = "accessmem"_n;
act.name = name(pagenum);
act.authorization = vector<permission_level>{{"accessmem"_n,config::active_name}};
signed_transaction trx;
trx.actions.push_back(act);
set_transaction_headers(trx);
trx.sign(get_private_key( "accessmem"_n, "active" ), control->get_chain_id());
BOOST_CHECK_THROW(push_transaction(trx), eosio::chain::wasm_exception);
};
// verify checking of intrinsic arguments
auto pushintrinsic = [&](uint64_t pages) {
action act;
act.account = "intrinsicmem"_n;
act.name = name(pages);
act.authorization = vector<permission_level>{{"intrinsicmem"_n,config::active_name}};
signed_transaction trx;
trx.actions.push_back(act);
set_transaction_headers(trx);
trx.sign(get_private_key( "intrinsicmem"_n, "active" ), control->get_chain_id());
BOOST_CHECK_THROW(push_transaction(trx), eosio::chain::wasm_exception);
};
pushit(1);
checkaccess(max_pages - 1);
pushintrinsic(max_pages);
produce_blocks(1);
// Increase memory limit
++params.max_pages;
set_wasm_params(params);
produce_block();
pushit(2);
checkaccess(max_pages);
// Decrease memory limit
params.max_pages -= 2;
set_wasm_params(params);
produce_block();
pushit(0);
// Reduce memory limit below initial memory
--params.max_pages;
set_wasm_params(params);
produce_block();
BOOST_CHECK_THROW(pushit(0), eosio::chain::wasm_exception);
params.max_pages = max_pages;
set_wasm_params(params);
string too_big_memory_wast_f = fc::format_string(too_big_memory_wast, fc::mutable_variant_object(
"MAX_WASM_PAGES_PLUS_ONE", params.max_pages+1));
BOOST_CHECK_THROW(set_code("bigmem"_n, too_big_memory_wast_f.c_str()), eosio::chain::wasm_exception);
// Check that the max memory defined by the contract is respected
string memory_over_max_wast = fc::format_string(max_memory_wast, fc::mutable_variant_object()
("INIT_WASM_PAGES", params.max_pages - 3)
("MAX_WASM_PAGES", params.max_pages - 1));
set_code("bigmem"_n, memory_over_max_wast.c_str());
produce_block();
pushit(2);
// Move max_pages in between the contract's initial and maximum memories
params.max_pages -= 2;
set_wasm_params(params);
produce_block();
pushit(1);
// Move it back
params.max_pages += 2;
set_wasm_params(params);
produce_block();
pushit(2);
}
} FC_LOG_AND_RETHROW()
BOOST_FIXTURE_TEST_CASE( call_depth, wasm_config_tester ) try {
produce_block();
create_accounts( {"depth"_n} );
produce_block();
uint32_t max_call_depth = 150;
auto high_params = genesis_state::default_initial_wasm_configuration;
high_params.max_call_depth = max_call_depth + 1;
wasm_config low_params = high_params;
low_params.max_call_depth = 50;
set_wasm_params(high_params);
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 = [&]() {
produce_block();
trx.signatures.clear();
set_transaction_headers(trx);
trx.sign(get_private_key("depth"_n, "active"), control->get_chain_id());
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", max_call_depth));
set_code("depth"_n, wasm_depth_okay.c_str());
pushit();
// The depth should not be cached.
set_wasm_params(low_params);
BOOST_CHECK_THROW(pushit(), wasm_execution_error);
set_wasm_params(high_params);
produce_block();
pushit();
produce_block();
string wasm_depth_one_over = fc::format_string(depth_assert_wasm, fc::mutable_variant_object()
("MAX_DEPTH", max_call_depth+1));
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", max_call_depth));
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", max_call_depth+1));
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", max_call_depth));
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", max_call_depth+1));
set_code("depth"_n, wasm_float_depth_one_over.c_str());
BOOST_CHECK_THROW(pushit(), wasm_execution_error);
} FC_LOG_AND_RETHROW()
// This contract is one of the smallest that can be used to reset
// the wasm parameters. It should be impossible to set parameters
// that would prevent it from being set on the eosio account and executing.
static const char min_set_parameters_wast[] = R"======(
(module
(import "env" "set_wasm_parameters_packed" (func $set_wasm_parameters_packed (param i32 i32)))
(import "env" "read_action_data" (func $read_action_data (param i32 i32) (result i32)))
(memory 1)
(func (export "apply") (param i64 i64 i64)
(br_if 0 (i32.eqz (i32.eqz (i32.wrap/i64 (get_local 2)))))
(drop (call $read_action_data (i32.const 4) (i32.const 44)))
(call $set_wasm_parameters_packed (i32.const 0) (i32.const 48))
)
)
)======";
BOOST_FIXTURE_TEST_CASE(reset_chain_tests, wasm_config_tester) {
produce_block();
wasm_config min_params = {
.max_mutable_global_bytes = 0,
.max_table_elements = 0,
.max_section_elements = 4,
.max_linear_memory_init = 0,
.max_func_local_bytes = 8,
.max_nested_structures = 1,
.max_symbol_bytes = 32,
.max_module_bytes = 256,
.max_code_bytes = 32,
.max_pages = 1,
.max_call_depth = 2
};
auto check_minimal = [&](auto& member) {
if (member > 0) {
--member;
BOOST_CHECK_THROW(set_wasm_params(min_params), fc::exception);
++member;
}
};
check_minimal(min_params.max_mutable_global_bytes);
check_minimal(min_params.max_table_elements);
check_minimal(min_params.max_section_elements);
check_minimal(min_params.max_func_local_bytes);
check_minimal(min_params.max_linear_memory_init);
check_minimal(min_params.max_nested_structures);
check_minimal(min_params.max_symbol_bytes);
check_minimal(min_params.max_module_bytes);
check_minimal(min_params.max_code_bytes);
check_minimal(min_params.max_pages);
check_minimal(min_params.max_call_depth);
set_wasm_params(min_params);
produce_block();
// Reset parameters and system contract
{
signed_transaction trx;
auto make_setcode = [](const std::vector<uint8_t>& code) {
return setcode{ "eosio"_n, 0, 0, bytes(code.begin(), code.end()) };
};
trx.actions.push_back({ { { "eosio"_n, config::active_name} }, make_setcode(wast_to_wasm(min_set_parameters_wast)) });
trx.actions.push_back({ { { "eosio"_n, config::active_name} }, "eosio"_n, ""_n, fc::raw::pack(genesis_state::default_initial_wasm_configuration) });
trx.actions.push_back({ { { "eosio"_n, config::active_name} }, make_setcode(contracts::eosio_bios_wasm()) });
set_transaction_headers(trx);
trx.sign(get_private_key("eosio"_n, "active"), control->get_chain_id());
push_transaction(trx);
}
produce_block();
// Make sure that a normal contract works
set_wasm_params(genesis_state::default_initial_wasm_configuration);
produce_block();
}
// Verifies the result of get_wasm_parameters_packed
static const char check_get_wasm_parameters_wast[] = R"======(
(module
(import "env" "get_wasm_parameters_packed" (func $get_wasm_parameters_packed (param i32 i32 i32) (result i32)))
(import "env" "read_action_data" (func $read_action_data (param i32 i32) (result i32)))
(import "env" "action_data_size" (func $action_data_size (result i32)))
(import "env" "memcmp" (func $memcmp (param i32 i32 i32) (result i32)))
(import "env" "memset" (func $memset (param i32 i32 i32) (result i32)))
(import "env" "printhex" (func $printhex (param i32 i32)))
(import "env" "eosio_assert" (func $eosio_assert (param i32 i32)))
(memory 1)
(func (export "apply") (param i64 i64 i64)
(drop (call $read_action_data (i32.const 0) (call $action_data_size)))
(drop (call $memset (i32.const 256) (i32.const 255) (call $action_data_size)))
(drop (call $get_wasm_parameters_packed (i32.const 256) (call $action_data_size) (i32.const 0)))
(if (call $memcmp (i32.const 0) (i32.const 256) (call $action_data_size))
(then
(call $printhex (i32.const 256) (call $action_data_size))
(call $eosio_assert (i32.const 0) (i32.const 512))
)
)
)
(data (i32.const 512) "Wrong result for get_wasm_parameters_packed")
)
)======";
BOOST_FIXTURE_TEST_CASE(get_wasm_parameters_test, validating_tester) {
produce_block();
create_account( "test"_n );
produce_block();
wasm_config original_params = {
.max_mutable_global_bytes = 1024,
.max_table_elements = 1024,
.max_section_elements = 8192,
.max_linear_memory_init = 65536,
.max_func_local_bytes = 8192,
.max_nested_structures = 1024,
.max_symbol_bytes = 8192,
.max_module_bytes = 20971520,
.max_code_bytes = 20971520,
.max_pages = 528,
.max_call_depth = 251
};
set_code("test"_n, check_get_wasm_parameters_wast);
produce_block();
auto check_wasm_params = [&](const std::vector<char>& params){
signed_transaction trx;
trx.actions.emplace_back(vector<permission_level>{{"test"_n,config::active_name}}, "test"_n, ""_n,
params);
set_transaction_headers(trx);
trx.sign(get_private_key("test"_n, "active"), control->get_chain_id());
push_transaction(trx);
};
BOOST_CHECK_THROW(check_wasm_params(fc::raw::pack(uint32_t{0}, wasm_config(original_params))), unaccessible_api);
push_action( config::system_account_name, "setpriv"_n, config::system_account_name,
fc::mutable_variant_object()("account", "test"_n)("is_priv", true) );
check_wasm_params(fc::raw::pack(uint32_t{0}, wasm_config(original_params)));
// Extra space is left unmodified
check_wasm_params(fc::raw::pack(uint32_t{0}, wasm_config(original_params), static_cast<unsigned char>(0xFF)));
// Does nothing if the buffer is too small
check_wasm_params(std::vector<char>(fc::raw::pack_size(uint32_t{0}) + fc::raw::pack_size(original_params) - 1, '\xFF'));
// Test case sanity check
BOOST_CHECK_THROW(check_wasm_params(fc::raw::pack(uint32_t{0}, wasm_config{ 0,0,0,0,0,0,0,0,0,0,0 })), fc::exception);
}
// Uses a custom section with large size
BOOST_FIXTURE_TEST_CASE(large_custom_section, old_wasm_tester)
{
create_account( "hugecustom"_n );
std::vector<uint8_t> custom_section_wasm{
0x00, 'a', 's', 'm', 0x01, 0x00, 0x00, 0x00,
0x01, 0x07, 0x01, 0x60, 0x03, 0x7e, 0x7e, 0x7e, 0x00, //type section containing a function as void(i64,i64,i64)
0x03, 0x02, 0x01, 0x00, //a function
0x07, 0x09, 0x01, 0x05, 'a', 'p', 'p', 'l', 'y', 0x00, 0x00, //export function 0 as "apply"
0x0a, 0x04, 0x01, //code section
0x02, 0x00, //function body start with length 3; no locals
0x0b, //end
0x00, //custom section
0x85, 0x80, 0x04, //size 2^16 + 5
0x04, 'h', 'u', 'g', 'e' //name
};
custom_section_wasm.resize(custom_section_wasm.size() + 65536);
BOOST_CHECK_THROW(set_code( "hugecustom"_n, custom_section_wasm ), wasm_serialization_error);
// One byte less and it should pass
auto okay_custom = custom_section_wasm;
--okay_custom[okay_custom.size() - 65536 - 5 - 3];
okay_custom.pop_back();
set_code( "hugecustom"_n, okay_custom );
// It's also okay once CONFIGURABLE_WASM_LIMITS is activated
preactivate_builtin_protocol_features({builtin_protocol_feature_t::configurable_wasm_limits});
produce_block();
set_code( "hugecustom"_n, custom_section_wasm );
signed_transaction trx;
trx.actions.emplace_back(vector<permission_level>{{"hugecustom"_n,config::active_name}}, "hugecustom"_n, ""_n, std::vector<char>{});
set_transaction_headers(trx);
trx.sign(get_private_key("hugecustom"_n, "active"), control->get_chain_id());
push_transaction(trx);
}
BOOST_AUTO_TEST_SUITE_END()