Smart Contracts

The EOS C++ SDK Developer Hub

Welcome to the EOS C++ SDK developer hub. You'll find comprehensive guides and documentation to help you start working with EOS C++ SDK as quickly as possible, as well as support if you get stuck. Let's jump right in!

Get Started    

Debugging a Smart Contract

In order to be able to debug your smart contract, you will need to setup local nodeos node. This local nodeos node can be run as separate private testnet or as an extension of public testnet. This local node also needs to be run with the contracts-console option on, either --contracts-console via the command line or contracts-console = true via the config.ini and/or by setting up logging on your running nodeos node and checking the output logs. See below for details on logging.

When you are creating your smart contract for the first time, it is recommended to test and debug your smart contract on a private testnet first, since you have full control of the whole blockchain and can easily add suitable logging. This enables you to have unlimited amount of eos needed and you can just reset the state of the blockchain whenever you want. When it is ready for production, debugging on the public testnet (or official testnet) can be done by connecting your local nodeos to the public testnet (or official testnet) so you can see the log of the testnet in your local nodeos.

The concept is the same, so for the following guide, debugging on the private testnet will be covered.

If you haven't set up your own local nodeos, please follow the setup guide. By default, your local nodeos will just run in a private testnet unless you modify the config.ini file to connect with public testnet (or official testnet) nodes.

Method

The main method used to debug smart contract is Caveman Debugging, where we utilize the printing functionality to inspect the value of a variable and check the flow of the contract. Printing in smart contract can be done through the Print API. The C++ API is the wrapper for C API, so most often we will just use the C++ API.

Print

Print C API supports the following data type that you can print:

  • prints - a null terminated char array (string)
  • prints_l - any char array (string) with given size
  • printi - 64-bit signed integer
  • printui - 64-bit unsigned integer
  • printi128 - 128-bit signed integer
  • printui128 - 128-bit unsigned integer
  • printsf - single-precision floating point number
  • printdf - double encoded as 64-bit unsigned integer
  • printqf - quadruple encoded as 64-bit unsigned integer
  • printn - 64 bit names as base32 encoded string
  • printhex - hex given binary of data and its size

While Print C++ API wraps some of the above C API by overriding the print() function so user doesn't need to determine which specific print function he needs to use. Print C++ API supports

  • a null terminated char array (string)
  • integer (128-bit unsigned, 64-bit unsigned, 32-bit unsigned, signed, unsigned)
  • base32 string encoded as 64-bit unsigned integer
  • struct that has print() method

Example

Let's write a new contract as example for debugging

debug.hpp

#include <eoslib/eos.hpp>
#include <eoslib/db.hpp>

namespace debug {
    struct foo {
        account_name from;
        account_name to;
        uint64_t amount;
        void print() const {
            eosio::print("Foo from ", eosio::name(from), " to ",eosio::name(to), " with amount ", amount, "\n");
        }
    };
}

debug.cpp

#include <debug.hpp>

extern "C" {

    void apply( uint64_t code, uint64_t action ) {
        if (code == N(debug)) {
            eosio::print("Code is debug\n");
            if (action == N(foo)) {
                 eosio::print("Action is foo\n");
                debug::foo f = eosio::unpack_action_data<debug::foo>();
               if (f.amount >= 100) {
                    eosio::print("Amount is larger or equal than 100\n");
                } else {
                    eosio::print("Amount is smaller than 100\n");
                    eosio::print("Increase amount by 10\n");
                    f.amount += 10;
                    eosio::print(f);
                }
            }
        }
    }
} // extern "C"

debug.abi

{
  "structs": [{
      "name": "foo",
      "base": "",
      "fields": {
        "from": "account_name",
        "to": "account_name",
        "amount": "uint64"
      }
    }
  ],
  "actions": [{
      "action_name": "foo",
      "type": "foo"
    }
  ]
}

Let's deploy it and send a message to it. Assume that you have debug account created and have its key in your wallet.

$ eosio-cpp -abigen debug.cpp -o debug.wasm
$ cleos set contract debug CONTRACT_DIR/debug -p youraccount@active
$ cleos push action debug foo '{"from":"inita", "to":"initb", "amount":10}' --scope debug

When you check your local nodeos node log, you will see the following lines after the above message is sent.

Code is debug
Action is foo
Amount is smaller than 100
Increase amount by 10
Foo from inita to initb with amount 20

There, you can confirm that your message is going to the right control flow and the amount is updated correctly. You might see the above message at least 2 times and that's normal because each transaction is being applied during verification, block generation, and block application.

Logging

Logging for Nodeos is controlled by the logging.json file. The logging.json file is usually located in the --config-dir, the same directory as the config.ini file. This path can be explicitly defined using the -l or --logconf options when starting nodeos.

 ./nodeos --help
  ...
  Application Command Line Options:
  ...
  --config-dir arg                      Directory containing configuration files such as config.ini
  -l [ --logconf ] arg (=logging.json)  Logging configuration file name/path for library users

The logging.json file can be used to define appenders and loggers. The json configuration is used to define and tie appenders to loggers and logging levels.

Appenders

The logging library built into EOSIO supports three appender types:

console

This will output log message to the screen. The configuration options are:

  • name - arbitrary name to identify instance for use in loggers
  • type - "console"
  • stream - "std_out" or "std_err"
  • level_colors - maps a log level to a colour.
    -- level - see logging levels below.
    -- color - may be one of ("red", "green", "brown", "blue", "magenta", "cyan", "white", "console_default")
  • enabled - bool value to enabling/disabling the appender.

Example:

{
     "name": "consoleout",
     "type": "console",
     "args": {
       "stream": "std_out",

       "level_colors": [{
           "level": "debug",
           "color": "green"
         },{
           "level": "warn",
           "color": "brown"
         },{
           "level": "error",
           "color": "red"
         }
       ]
     },
     "enabled": true
   }

gelf

This sends the log messages to Graylog. Graylog is a fully integrated platform for collecting, indexing, and analyzing log messages. The configuration options are:

  • name - arbitrary name to identify instance for use in loggers
  • type - "gelf"
  • endpoint - ip address and port number
  • host - Graylog hostname, identifies you to Graylog.
  • enabled - bool value to enabling/disabling the appender.

Example:

{
     "name": "net",
     "type": "gelf",
     "args": {
       "endpoint": "104.198.210.18:12202”,
       "host": <YOURNAMEHERE IN QUOTES>
     },
     "enabled": true
}

file

The file appender is currently disabled

Loggers

The logging library built into EOSIO currently supports five loggers:

  • default, the default logger, always enabled.
  • net_plugin_impl, detailed logging for the net plugin.
  • bnet_plugin, detailed logging for the net plugin.
  • producer_plugin, detailed logging for the producer plugin.
  • transaction_tracing, detailed log that emits verdicts from relay nodes on the P2P network.

The configuration options are:

  • name - must match one of the names described above.
  • level - see logging levels below.
  • enabled - bool value to enabling/disabling the logger.
  • additivity - true or flase
  • appenders
    -- list of appenders by name (name in the appender configuration)

Example:

{
     "name": "net_plugin_impl",
     "level": "debug",
     "enabled": true,
     "additivity": false,
     "appenders": [
       "net"
     ]
}

net_plugin_impl, bnet_plugin, producer_plugin, transaction_tracing are not enabled unless explicitly enabled in the logging.json

Logging Levels

There are six available logging levels:

  • all,
  • debug
  • info
  • warn
  • error
  • off

Sample logging.json

{
 "includes": [],
 "appenders": [{
     "name": "consoleout", 
     "type": "console",
     "args": {
       "stream": "std_out",
       "level_colors": [{
           "level": "debug",
           "color": "green"
         },{
           "level": "warn",
           "color": "brown"
         },{
           "level": "error",
           "color": "red"
         }
       ]
     },
     "enabled": true
   },{
     "name": "net",
     "type": "gelf",
     "args": {
       "endpoint": "10.10.10.10",
       "host": "test"
     },
     "enabled": true
   }
 ],
 "loggers": [{
     "name": "default",
     "level": "info",
     "enabled": true,
     "additivity": false,
     "appenders": [
       "consoleout",
       "net"
     ]
   },{
     "name": "net_plugin_impl",
     "level": "debug",
     "enabled": true,
     "additivity": false,
     "appenders": [
       "net"
     ]
   }
 ]
}