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 ipyeos
Then run the debugging server:
eosdebugger
Also, you can run eosdebugger in a docker container. Just follow the steps below.
docker pull ghcr.io/uuosio/ipyeos:latest
Run the debugging server:
docker run -it --rm -p 9090:9090 -p 9092:9092 -t ghcr.io/uuosio/ipyeos
a 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() const
convert JsonObject to a pretty formatted json string
string to_pretty_string() const
Example:
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()