C++ Smart Contracts Test Framework for EOS
- It is a C++ smart contracts test framework for EOS.
- It makes writing C++ Smart Contracts test code more convenient.
- It supports source-level C++ Smart Contracts debugging.
- It supports generating code coverage reports for C++ Smart Contracts.
cpp-chaintester depends on ipyeos to run the test code.
Install it with the following command:
python3 -m pip install -U ipyeosThen run the debugging server:
eosdebuggerAlso, you can run eosdebugger in a docker container. Just follow the steps below.
docker pull ghcr.io/uuosio/ipyeos:latestRun the debugging server:
docker run -it --rm -p 9090:9090 -p 9092:9092 -t ghcr.io/uuosio/ipyeosa simple test case example
TEST_CASE( "test hello", "[hello]" ) {
ChainTester tester(true);
tester.deploy_contract("hello"_n, HELLO_WASM, HELLO_ABI);
tester.push_action(
"hello"_n,
"hello"_n,
"sum"_n,
std::make_tuple(uint64_t(1), uint64_t(2))
);
tester.produce_block();
}In order to support debugging, the contract project needs to compile with Debug mode
mkdir build
cd build
cmake -DENABLE_COVERAGE=TRUE -DCMAKE_BUILD_TYPE=Debug -Dcdt_DIR=`cdt-get-dir` -GNinja ..Enable debugging by setting a native apply function
TEST_CASE( "test hello", "[hello]" ) {
ChainTester tester(true);
tester.set_native_apply("hello"_n, hello_native_apply);
tester.deploy_contract("hello"_n, HELLO_WASM, HELLO_ABI);
tester.push_action(
"hello"_n,
"hello"_n,
"sum"_n,
std::make_tuple(uint64_t(1), uint64_t(2))
);
tester.produce_block();
}Enable debugging by setting a shared native contract lib. Debugging a shared native contract require attaching to the debugging server.
TEST_CASE( "test hello", "[hello]" ) {
ChainTester tester(true);
tester.enable_debugging(true);
tester.set_native_contract("hello"_n, HELLO_NATIVE_LIB);
tester.deploy_contract("hello"_n, HELLO_WASM, HELLO_ABI);
tester.push_action(
"hello"_n,
"hello"_n,
"sum"_n,
std::make_tuple(uint64_t(1), uint64_t(2))
);
tester.produce_block();
}Add -fprofile-arcs -ftest-coverage options to native library like below to support generating coverage report for smart contract code.
target_compile_options(hello_native PRIVATE
-fprofile-arcs -ftest-coverage
...
)
target_link_options(hello_native PRIVATE -fprofile-arcs -ftest-coverage)cpp-coverage-example shows how to integrating cpp-chaintester in a smart contract project for debugging and generating code coverage report.
constructor, if initialize set to true, ChainTester will initialize test chain for you so you don't need to deploy contracts such as eosio.system and eosio.token any more. The process includes:
- Create accounts such as
eosio.bpay,eosio.msig,eosio.names,eosio.ram,eosio.ramfee,eosio.saving,eosio.stake,eosio.token,eosio.vpay,eosio.rex,eosio.reserv,hello,alice,bob, - deploy contracts to
eosio.token,eosio,eosio.msig - activate all the features:
ONLY_LINK_TO_EXISTING_PERMISSION FORWARD_SETCODE WTMSIG_BLOCK_SIGNATURES GET_BLOCK_NUM REPLACE_DEFERRED NO_DUPLICATE_DEFERRED_ID RAM_RESTRICTIONS WEBAUTHN_KEY BLOCKCHAIN_PARAMETERS DISALLOW_EMPTY_PRODUCER_SCHEDULE CRYPTO_PRIMITIVES ONLY_BILL_FIRST_AUTHORIZER RESTRICT_ACTION_TO_SELF GET_CODE_HASH ACTION_RETURN_VALUE CONFIGURABLE_WASM_LIMITS2 FIX_LINKAUTH_RESTRICTION GET_SENDER - issue token to
eosio,hello,alice,bob
The initialization code locate at chaintester.py
Set initialize to false for implementing custom initialization code
ChainTester(bool initialize=true);specifies a native apply function for a contract
void set_native_apply(name contract, fn_native_apply apply);specifies a native shared library for a contract for debugging and profiling. attaching to the debugging server for debugging.
bool set_native_contract(name contract, const string& dylib);push an action to the test chain
template<typename... Ts>
std::shared_ptr<JsonObject> push_action(const vector<permission_level>& permissions, const name account, const name action, Ts... arguments);
template<typename... Ts>
std::shared_ptr<JsonObject> push_action(const name signer, const name account, const name action, Ts... arguments);push actions to the test chain
std::shared_ptr<JsonObject> push_actions(const std::vector<action>& actions);deploy a wasm contract to the test chain
std::shared_ptr<JsonObject> deploy_contract(const name account, const string& wasm_file, const string& abi_file);import private key for signing transaction
bool import_key(const string& pub_key, const string& priv_key);create an account
std::shared_ptr<JsonObject> create_account(const name creator, const name account, const string& owner_key, const string& active_key, int64_t ram_bytes=10*1024*1024, int64_t stake_net=100000, int64_t stake_cpu=1000000);produce one block
void produce_block(int64_t next_block_delay_seconds = 0);produce n blocks
void produce_blocks(int n);Get test chain information
std::shared_ptr<JsonObject> get_info();return value likes below:
{
"server_version": "00000000",
"chain_id": "fafc23d24da8824276b6949e065db4597f2b80cbd460ae2348db5faf7f5b9958",
"head_block_num": 6,
"last_irreversible_block_num": 5,
"last_irreversible_block_id": "000000055b8a9d5b3a1ea3c729feb40796b7d7c61a8dc917a7aaaa1410d3bb79",
"head_block_id": "00000006304b291f27b99ecf4b5cfcb910ad4b3fdb1cf890711a0b93d8f4d52a",
"head_block_time": "2018-06-01T12:00:02.500",
"head_block_producer": "eosio",
"virtual_block_cpu_limit": 451802,
"virtual_block_net_limit": 1053831,
"block_cpu_limit": 449900,
"block_net_limit": 1048576,
"server_version_string": "Unknown",
"fork_db_head_block_num": 6,
"fork_db_head_block_id": "00000006304b291f27b99ecf4b5cfcb910ad4b3fdb1cf890711a0b93d8f4d52a",
"server_full_version_string": "Unknown",
"total_cpu_weight": 0,
"total_net_weight": 0,
"earliest_available_block_num": 1,
"last_irreversible_block_time": "2018-06-01T12:00:02.000"
}return a crypto key pair
std::shared_ptr<JsonObject> create_key(const char* key_type="K1");return value likes below:
{
"public": "EOS5EJKkpgHacHmnzqXj48AaZRL6zYTpKAPFreerTRhin3QtnByvt",
"private": "..."
}get account information
std::shared_ptr<JsonObject> get_account(const name account);return value likes below:
{
"account_name": "hello",
"head_block_num": 6,
"head_block_time": "2018-06-01T12:00:02.500",
"privileged": false,
"last_code_update": "1970-01-01T00:00:00.000",
"created": "2018-06-01T12:00:00.500",
"core_liquid_balance": "5000000.0000 EOS",
"ram_quota": -1,
"net_weight": -1,
"cpu_weight": -1,
"net_limit": {
"used": -1,
"available": -1,
"max": -1,
"last_usage_update_time": "2018-06-01T12:00:00.500",
"current_used": -1
},
"cpu_limit": {
"used": -1,
"available": -1,
"max": -1,
"last_usage_update_time": "2018-06-01T12:00:00.500",
"current_used": -1
},
"ram_usage": 2724,
"permissions": [
{
"perm_name": "active",
"parent": "owner",
"required_auth": {
"threshold": 1,
"keys": [
{
"key":
"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",
"weight": 1
}
],
"accounts": [],
"waits": []
},
"linked_actions": []
},
{
"perm_name": "owner",
"parent": "",
"required_auth": {
"threshold": 1,
"keys": [
{
"key":
"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",
"weight": 1
}
],
"accounts": [],
"waits": []
},
"linked_actions": []
}
],
"total_resources": null,
"self_delegated_bandwidth": null,
"refund_request": null,
"voter_info": null,
"rex_info": null,
"eosio_any_linked_actions": []
}std::shared_ptr<JsonObject> get_table_rows(bool json,
const name code, const name scope, const name table,
const name lower_bound, const name upper_bound,
int64_t limit,
const string& key_type = "",
const string& index_position = "",
bool reverse = false,
bool show_payer = true);get account token balance
int64_t get_balance(const name account, const name token_account="eosio.token"_n, const string& symbol="EOS");return string value in a json object
template<typename... Ts>
string get_string(Ts... args)Example:
std::shared_ptr<JsonObject> ret = this->get_table_rows(false, token_account, account, "accounts", symbol, "", 1);
if (!ret->has_value("rows", 0, "data")) {
return;
}
string s = ret->get_string("rows", 0, "data");convert JsonObject to a json string
const string& to_string() constconvert JsonObject to a pretty formatted json string
string to_pretty_string() constExample:
ChainTester tester(true);
tester.set_native_apply("hello"_n, hello_native_apply);
tester.deploy_contract("hello"_n, HELLO_WASM, HELLO_ABI);
ActionSender sender = tester.new_action_sender();
sender.add_action(
"hello"_n,
"hello"_n,
"sum"_n,
std::make_tuple(uint64_t(1), uint64_t(2))
);
sender.send();
tester.produce_block();add an action to the ActionSender
template<typename... Ts>
ActionSender& add_action(const name signer, name account, name action, Ts... args);
template<typename... Ts>
ActionSender& add_action(const vector<permission_level>& permissions, name account, name action, Ts... args);call ChainTester.push_actions to send the action(s)
std::shared_ptr<JsonObject> send()