Files
coopos/unittests/dry_run_trx_tests.cpp
2023-08-14 16:37:36 -04:00

333 lines
12 KiB
C++

#include <eosio/chain/abi_serializer.hpp>
#include <boost/test/unit_test.hpp>
#include <eosio/testing/tester.hpp>
#include <eosio/chain/global_property_object.hpp>
#include <fc/variant_object.hpp>
#include <test_contracts.hpp>
using namespace eosio;
using namespace eosio::chain;
using namespace eosio::testing;
using namespace fc;
using mvo = fc::mutable_variant_object;
struct dry_run_trx_tester : validating_tester {
dry_run_trx_tester() {
produce_block();
};
void set_up_test_contract() {
create_accounts( {"noauthtable"_n, "alice"_n} );
set_code( "noauthtable"_n, test_contracts::no_auth_table_wasm() );
set_abi( "noauthtable"_n, test_contracts::no_auth_table_abi() );
produce_block();
insert_data = abi_ser.variant_to_binary( "insert", mutable_variant_object()
("user", "alice") ("id", 1) ("age", 10),
abi_serializer::create_yield_function( abi_serializer_max_time ) );
getage_data = abi_ser.variant_to_binary("getage", mutable_variant_object()
("user", "alice"),
abi_serializer::create_yield_function( abi_serializer_max_time ));
}
void send_action(const action& act, bool sign = false) {
signed_transaction trx;
trx.actions.push_back( act );
set_transaction_headers( trx );
if (sign) // dry-run can contain signature, but not required
trx.sign(get_private_key(act.authorization.at(0).actor, act.authorization.at(0).permission.to_string()), control->get_chain_id());
push_transaction( trx, fc::time_point::maximum(), DEFAULT_BILLED_CPU_TIME_US, false, transaction_metadata::trx_type::dry_run );
}
auto send_db_api_transaction(action_name name, bytes data, const vector<permission_level>& auth={{"alice"_n, config::active_name}},
transaction_metadata::trx_type type=transaction_metadata::trx_type::input, uint32_t delay_sec=0) {
action act;
signed_transaction trx;
act.account = "noauthtable"_n;
act.name = name;
act.authorization = auth;
act.data = data;
trx.actions.push_back( act );
set_transaction_headers( trx );
trx.delay_sec = delay_sec;
if ( type == transaction_metadata::trx_type::input ) {
trx.sign(get_private_key("alice"_n, "active"), control->get_chain_id());
}
return push_transaction( trx, fc::time_point::maximum(), DEFAULT_BILLED_CPU_TIME_US, false, type );
}
void insert_a_record() {
auto res = send_db_api_transaction("insert"_n, insert_data);
BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed);
produce_block();
}
abi_serializer abi_ser{ json::from_string(test_contracts::no_auth_table_abi()).as<abi_def>(), abi_serializer::create_yield_function(abi_serializer_max_time )};
bytes insert_data;
bytes getage_data;
};
BOOST_AUTO_TEST_SUITE(dry_run_trx_tests)
BOOST_FIXTURE_TEST_CASE(require_authorization, dry_run_trx_tester) { try {
produce_blocks( 1 );
action act = {
{}, // no authorization provided: vector<permission_level>{{config::system_account_name,config::active_name}},
newaccount{
.creator = config::system_account_name,
.name = "alice"_n,
.owner = authority( get_public_key( "alice"_n, "owner" ) ),
.active = authority( get_public_key( "alice"_n, "active" ) )
}
};
// dry-run requires authorization
BOOST_REQUIRE_THROW(send_action(act, false), tx_no_auths);
// sign trx with no authorization
signed_transaction trx;
trx.actions.push_back( act );
set_transaction_headers( trx );
trx.sign(get_private_key("alice"_n, "active"), control->get_chain_id());
BOOST_REQUIRE_THROW(push_transaction( trx, fc::time_point::maximum(), DEFAULT_BILLED_CPU_TIME_US, false, transaction_metadata::trx_type::dry_run ), tx_no_auths);
} FC_LOG_AND_RETHROW() }
BOOST_FIXTURE_TEST_CASE(newaccount_test, dry_run_trx_tester) { try {
produce_blocks( 1 );
action act = {
vector<permission_level>{{config::system_account_name,config::active_name}},
newaccount{
.creator = config::system_account_name,
.name = "alice"_n,
.owner = authority( get_public_key( "alice"_n, "owner" ) ),
.active = authority( get_public_key( "alice"_n, "active" ) )
}
};
send_action(act, false); // should not throw
send_action(act, false); // should not throw
send_action(act, true); // should not throw
BOOST_CHECK_THROW(control->get_account("alice"_n), fc::exception); // not actually created
produce_blocks( 1 );
BOOST_CHECK_THROW(control->get_account("alice"_n), fc::exception); // not actually created
} FC_LOG_AND_RETHROW() }
BOOST_FIXTURE_TEST_CASE(setcode_test, dry_run_trx_tester) { try {
produce_blocks( 1 );
create_accounts( {"setcodetest"_n} );
auto wasm = test_contracts::no_auth_table_wasm();
action act = {
vector<permission_level>{{"setcodetest"_n,config::active_name}},
setcode{
.account = "setcodetest"_n,
.vmtype = 0,
.vmversion = 0,
.code = bytes(wasm.begin(), wasm.end())
}
};
send_action(act, false); // should not throw
send_action(act, true); // should not throw
BOOST_TEST(!is_code_cached("setcodetest"_n));
} FC_LOG_AND_RETHROW() }
BOOST_FIXTURE_TEST_CASE(setabi_test, dry_run_trx_tester) { try {
produce_blocks( 1 );
create_accounts( {"setabitest"_n} );
auto abi = test_contracts::no_auth_table_abi();
action act = {
vector<permission_level>{{"setabitest"_n,config::active_name}},
setabi {
.account = "setabitest"_n, .abi = bytes(abi.begin(), abi.end())
}
};
send_action(act, false); // should not throw
send_action(act, true); // should not throw
const auto* accnt = control->db().template find<chain::account_object, chain::by_name>( "setabitest"_n );
BOOST_REQUIRE(accnt);
BOOST_TEST(accnt->abi.size() == 0u); // no abi actually set
} FC_LOG_AND_RETHROW() }
BOOST_FIXTURE_TEST_CASE(updateauth_test, dry_run_trx_tester) { try {
produce_blocks( 1 );
create_accounts( {"alice"_n} );
auto auth = authority( get_public_key( "alice"_n, "test" ) );
action act = {
vector<permission_level>{{"alice"_n, config::active_name}},
updateauth {
.account = "alice"_n, .permission = "active"_n, .parent = "owner"_n, .auth = auth
}
};
send_action(act, false); // should not throw
send_action(act, true); // should not throw
} FC_LOG_AND_RETHROW() }
BOOST_FIXTURE_TEST_CASE(deleteauth_test, dry_run_trx_tester) { try {
produce_blocks( 1 );
create_accounts( {"alice"_n} );
// update auth
push_action(config::system_account_name, updateauth::get_name(), "alice"_n, fc::mutable_variant_object()
("account", "alice")
("permission", "first")
("parent", "active")
("auth", authority(get_public_key("alice"_n, "first")))
);
name account = "alice"_n;
name permission = "first"_n;
action act = {
vector<permission_level>{{"alice"_n, config::active_name}},
deleteauth { account, permission }
};
send_action(act, false); // should not throw
send_action(act, true); // should not throw
} FC_LOG_AND_RETHROW() }
BOOST_FIXTURE_TEST_CASE(linkauth_test, dry_run_trx_tester) { try {
produce_blocks( 1 );
create_account("eosio.token"_n);
set_code("eosio.token"_n, test_contracts::eosio_token_wasm());
set_abi("eosio.token"_n, test_contracts::eosio_token_abi());
create_accounts( {"alice"_n} );
// update auth
push_action(config::system_account_name, updateauth::get_name(), "alice"_n, fc::mutable_variant_object()
("account", "alice")
("permission", "first")
("parent", "active")
("auth", authority(get_public_key("alice"_n, "first")))
);
name account = "alice"_n;
name code = "eosio_token"_n;
name type = "transfer"_n;
name requirement = "first"_n;
action act = {
vector<permission_level>{{"alice"_n, config::active_name}},
linkauth { account, code, type, requirement }
};
send_action(act, false); // should not throw
send_action(act, true); // should not throw
} FC_LOG_AND_RETHROW() }
BOOST_FIXTURE_TEST_CASE(unlinkauth_test, dry_run_trx_tester) { try {
produce_blocks( 1 );
create_account("eosio.token"_n);
set_code("eosio.token"_n, test_contracts::eosio_token_wasm());
set_abi("eosio.token"_n, test_contracts::eosio_token_abi());
create_accounts( {"alice"_n} );
// update auth
push_action(config::system_account_name, updateauth::get_name(), "alice"_n, fc::mutable_variant_object()
("account", "alice")
("permission", "first")
("parent", "active")
("auth", authority(get_public_key("alice"_n, "first")))
);
// link auth
push_action(config::system_account_name, linkauth::get_name(), "alice"_n, fc::mutable_variant_object()
("account", "alice")
("code", "eosio.token")
("type", "transfer")
("requirement", "first"));
name account = "alice"_n;
name code = "eosio_token"_n;
name type = "transfer"_n;
action act = {
vector<permission_level>{{"alice"_n, config::active_name}},
unlinkauth { account, code, type }
};
send_action(act, false); // should not throw
send_action(act, true); // should not throw
} FC_LOG_AND_RETHROW() }
BOOST_FIXTURE_TEST_CASE(delay_sec_test, dry_run_trx_tester) { try {
set_up_test_contract();
// verify dry-run transaction does not allow non-zero delay_sec.
BOOST_CHECK_THROW(send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::dry_run, 3), transaction_exception);
} FC_LOG_AND_RETHROW() }
BOOST_FIXTURE_TEST_CASE(db_insert_test, dry_run_trx_tester) { try {
set_up_test_contract();
// verify DB operation is allowed by dry-run transaction
send_db_api_transaction("insert"_n, insert_data, vector<permission_level>{{"alice"_n, config::active_name}}, transaction_metadata::trx_type::dry_run);
// verify the dry-run insert was rolled back, use a read-only trx to query
BOOST_CHECK_EXCEPTION(send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::read_only), fc::exception,
[](const fc::exception& e) {
return expect_assert_message(e, "Record does not exist");
});
insert_a_record();
// do a dry-run transaction and verify the return value (age) is the same as inserted
auto res = send_db_api_transaction("getage"_n, getage_data, vector<permission_level>{{"alice"_n, config::active_name}}, transaction_metadata::trx_type::dry_run);
BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed);
BOOST_CHECK_EQUAL(res->action_traces[0].return_value[0], 10);
BOOST_CHECK_GT(res->net_usage, 0u);
BOOST_CHECK_GT(res->elapsed.count(), 0u);
} FC_LOG_AND_RETHROW() }
BOOST_FIXTURE_TEST_CASE(sequence_numbers_test, dry_run_trx_tester) { try {
set_up_test_contract();
const auto& p = control->get_dynamic_global_properties();
auto receiver_account = control->db().find<account_metadata_object,by_name>("noauthtable"_n);
auto amo = control->db().find<account_metadata_object,by_name>("alice"_n);
// verify sequence numbers in state increment for non-read-only transactions
auto prev_global_action_sequence = p.global_action_sequence;
auto prev_recv_sequence = receiver_account->recv_sequence;
auto prev_auth_sequence = amo->auth_sequence;
auto res = send_db_api_transaction("insert"_n, insert_data);
BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed);
BOOST_CHECK_EQUAL( prev_global_action_sequence + 1, p.global_action_sequence );
BOOST_CHECK_EQUAL( prev_recv_sequence + 1, receiver_account->recv_sequence );
BOOST_CHECK_EQUAL( prev_auth_sequence + 1, amo->auth_sequence );
produce_block();
// verify sequence numbers in state do not change for dry-run transactions
prev_global_action_sequence = p.global_action_sequence;
prev_recv_sequence = receiver_account->recv_sequence;
prev_auth_sequence = amo->auth_sequence;
send_db_api_transaction("getage"_n, getage_data, vector<permission_level>{{"alice"_n, config::active_name}}, transaction_metadata::trx_type::dry_run);
BOOST_CHECK_EQUAL( prev_global_action_sequence, p.global_action_sequence );
BOOST_CHECK_EQUAL( prev_recv_sequence, receiver_account->recv_sequence );
BOOST_CHECK_EQUAL( prev_auth_sequence, amo->auth_sequence );
} FC_LOG_AND_RETHROW() }
BOOST_AUTO_TEST_SUITE_END()