CMake + EOSIO

in eos •  6 years ago 

Purpose

This tutorial describes how to use CMake (and CLion) to build smart contracts for the eosio blockchain. This work was motivated by the absence of build tools that can be used directly from an IDE. Also it was inspired from a previous work done by InfiniteXLabs and try to solve the following shortcomings:

  • build multiple contracts within the same project
  • do not have to copy eosio header files in your project directory
  • do not have to recompile eosio sdk libs within your project
  • provide build configuration similar to c++/CMake project (eg, add_library, target_include_directories, target_link_libraries, etc)

Prerequisites

To complete this tutorial, you need to be familiar with CMake, install eosio sdk for your environment, install clang, optional but recommended CLion (or another C++ IDE).

Limitation

This tutorial is a work in progress hence has been tested only on macosx, lacks some useful features (like CMake tests) and does not cover contract deployment to an eosio blockchain.

Tutorial Source

The tutorial source repositories are : eosio-cmake and eosio-cmake-tutorial:

How it works

Create a new project

Tutorial folder structure

  • Create a new folder for our tutorial eosio-cmake-tutorial:

  • Copy scripts/eosiosdk.cmake scripts/eosiosdk-util.cmake scripts/FindEOSIOSDKLibs.cmake from eosio-cmake to eosio-cmake-tutorial/cmake.

  • Create include/contracts/hello.hpp inside eosio-cmake-tutorial

  • Create include/contracts/hello.cppinside eosio-cmake-tutorial

Your folder should look like :

|---- eosio-cmake-tutorial
             |---- CMakeLists.txt
             |---- include
                        |---- contracts
                                   |----hello.hpp
             |---- src
                        |---- hello.cpp
             |---- cmake
                        |---- eosiosdk.cmake
                        |---- eosiosdk-util.cmake
                        |---- FindEOSIOSDKLibs.cmake

Editing our first contract

  • Edit include/contracts/hello.hpp:
//
// MIT License.
//

#ifndef EOSIO_HELLO_WORLD_HELLO_HPP
#define EOSIO_HELLO_WORLD_HELLO_HPP

#include <contracts/hello.hpp>
#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>

using namespace eosio;

class hello : public eosio::contract {
 public:
  hello(account_name n);
  /// @abi action
  void hi( account_name user );
};
#endif //EOSIO_HELLO_WORLD_HELLO_HPP

  • Edit src/hello.cpp:
//
// MIT License.
//
#include "contracts/hello.hpp"

using namespace eosio;

hello::hello(account_name n) : contract(n) {}

void hello::hi(account_name user) {
  print("Hello, EOS", name{user});
}

EOSIO_ABI(hello, (hi))
  • Edit CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(eosio-cmake-tutorial)

################################
## Project
################################
set(PROJECT_VERSION_MAJOR "0")
set(PROJECT_VERSION_MINOR "1")
set(PROJECT_VERSION_PATCH "0")
set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
set(VERSION ${PROJECT_VERSION})

message("\n-- Configuring ${PROJECT_NAME} ${PROJECT_VERSION}...\n")

################################
## Cmake dependencies
################################
include(cmake/eosiosdk.cmake)
include(cmake/eosiosdk-util.cmake)
include(cmake/FindEOSIOSDKLibs.cmake)

################################
## Compiler Flags
################################
set(CMAKE_CXX_STANDARD 14)

################################
# Library Build
################################
# target hello
add_eosio_wasm_library(hello CONTRACT SOURCES src/hello.cpp)
target_eosio_wasm_compile_definitions(hello PUBLIC -DBOOST_DISABLE_ASSERTS -DBOOST_EXCEPTION_DISABLE)
target_eosio_wasm_include_directories(hello
        PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
        $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>)
# $<INSTALL_INTERFACE:include>)
target_eosio_wasm_link_libraries(hello eosio libc++ libc musl Boost)

add_eosio_wasm_abi(hello HEADERS include/contracts/hello.hpp)

################################
## Install
################################

eosio_wasm_install(hello CONTRACT
        CODE DESTINATION "${CMAKE_INSTALL_FULL_CODEDIR}/hello"
        ABI DESTINATION "${CMAKE_INSTALL_FULL_ABIDIR}/hello")                                                                                                                                                                                                 

Building our first contract

> cd eosio-cmake-tutorial
> mkdir build && cd build
> mkdir install && -DCMAKE_INSTALL_PREFIX=install ..
> make contracts

Scanning dependencies of target hello_link
[ 16%] Building LLVM bitcode src/hello.cpp.bc
[ 33%] Linking LLVM bitcode executable lib/hello.bc
[ 50%] Generating textual assembly hello.s
[ 66%] Generating WAST wast/hello.wast
[ 83%] Generating WASM wast/hello.wast
[ 83%] Built target hello_link
Scanning dependencies of target hello_abi_gen
[100%] Generating ABI abi/hello.abi
2018-09-20T11:17:15.814 thread-0   abi_generator.hpp:68          ricardian_contracts  ] Warning, no ricardian clauses found for 

[100%] Built target hello_abi_gen
Scanning dependencies of target hello
Target hello output wasm to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/bin
Target hello outputllvm bytecode to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/lib
Target hello output wast and asm to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/code
Target hello output abi to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/abi
[100%] Built target hello
Scanning dependencies of target contracts
[100%] Built target contracts

This step should output the following folders abi``code bin lib respectively containing hello.abi, hello.wast, hello.wasm, hello.bc.

Installing our first contract

> mkdir hello_install
Scanning dependencies of target hello_install
Installing contracts abi
/Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/abi:/Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/install/abi/hello
Installing contracts wasm
/Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/bin:/Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/install/code/hello
Installing contracts wast
/Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/code:/Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/install/code/hello
[100%] Built target hello_install

This step will install abi and code in install directory.

Editing our second contract

Our second contrat is the tic_tac_toe contract from eosio tutorial.

  • Create and edit include/contracts/tic_tac_toe.hpp
/**
 *  @file
 *  @copyright defined in eos/LICENSE.txt
 */
#ifndef EOSIO_HELLO_WORLD_TIC_TAC_TOE_HPP
#define EOSIO_HELLO_WORLD_TIC_TAC_TOE_HPP
#include <eosiolib/eosio.hpp>

/**
 *  @defgroup tictactoecontract Tic Tac Toe Contract
 *  @brief Defines the PvP tic tac toe contract example
 *  @ingroup examplecontract
 *
 *  @details
 *  For the following tic-tac-toe game:
 *  - Each pair of player can have 2 unique game, one where player_1 become host and player_2 become challenger and vice versa
 *  - The game data is stored in the "host" scope and use the "challenger" as the key
 *
 *  (0,0) coordinate is on the top left corner of the board
 *  @code
 *                 (0,2)
 *  (0,0) -  |  o  |  x      where - = empty cell
 *        -  |  x  |  -            x = move by host
 *  (2,0) x  |  o  |  o            o = move by challenger
 *  @endcode
 *
 *  Board is represented with number:
 *  - 0 represents empty cell
 *  - 1 represents cell filled by host
 *  - 2 represents cell filled by challenger
 *  Therefore, assuming x is host, the above board will have the following representation: [0, 2, 1, 0, 1, 0, 1, 2, 2] inside the game object
 *
 *  In order to deploy this contract:
 *  - Create an account called tic.tac.toe
 *  - Add tic.tac.toe key to your wallet
 *  - Set the contract on the tic.tac.toe account
 *
 *  How to play the game:
 *  - Create a game using `create` action, with you as the host and other account as the challenger.
 *  - The first move needs to be done by the host, use the `move` action to make a move by specifying which row and column to fill.
 *  - Then ask the challenger to make a move, after that it's back to the host turn again, repeat until the winner is determined.
 *  - If you want to restart the game, use the `restart` action
 *  - If you want to clear the game from the database to save up some space after the game has ended, use the `close` action
 *  @{
 */
class tic_tac_toe : public eosio::contract {
 public:
  tic_tac_toe( account_name self ):contract(self){}

  /**
       * @brief Information related to a game
       * @abi table games i64
       */
  struct game {
    static const uint16_t board_width = 3;
    static const uint16_t board_height = board_width;
    game() {
      initialize_board();
    }
    account_name          challenger;
    account_name          host;
    account_name          turn; // = account name of host/ challenger
    account_name          winner = N(none); // = none/ draw/ name of host/ name of challenger
    std::vector<uint8_t>  board;

    // Initialize board with empty cell
    void initialize_board() {
      board = std::vector<uint8_t>(board_width * board_height, 0);
    }

    // Reset game
    void reset_game() {
      initialize_board();
      turn = host;
      winner = N(none);
    }

    auto primary_key() const { return challenger; }
    EOSLIB_SERIALIZE( game, (challenger)(host)(turn)(winner)(board))
  };

  /**
       * @brief The table definition, used to store existing games and their current state
       */
  typedef eosio::multi_index< N(games), game> games;

  /// @abi action
  /// Create a new game
  void create(const account_name& challenger, const account_name& host);

  /// @abi action
  /// Restart a game
  /// @param by the account who wants to restart the game
  void restart(const account_name& challenger, const account_name& host, const account_name& by);

  /// @abi action
  /// Close an existing game, and remove it from storage
  void close(const account_name& challenger, const account_name& host);

  /// @abi action
  /// Make movement
  /// @param by the account who wants to make the move
  void move(const account_name& challenger, const account_name& host, const account_name& by, const uint16_t& row, const uint16_t& column);

};
/// @}
#endif //EOSIO_HELLO_WORLD_TIC_TAC_TOE_HPP
  • Create and edit src/tic_tac_toe.cpp
/**
 *  @file
 *  @copyright defined in eos/LICENSE.txt
 */
#include <contracts/tic_tac_toe.hpp>

using namespace eosio;

/**
 * @brief Check if cell is empty
 * @param cell - value of the cell (should be either 0, 1, or 2)
 * @return true if cell is empty
 */
bool is_empty_cell(const uint8_t& cell) {
  return cell == 0;
}

/**
 * @brief Check for valid movement
 * @detail Movement is considered valid if it is inside the board and done on empty cell
 * @param row - the row of movement made by the player
 * @param column - the column of movement made by the player
 * @param board - the board on which the movement is being made
 * @return true if movement is valid
 */
bool is_valid_movement(const uint16_t& row, const uint16_t& column, const vector<uint8_t>& board) {
  uint32_t movement_location = row * tic_tac_toe::game::board_width + column;
  bool is_valid = movement_location < board.size() && is_empty_cell(board[movement_location]);
  return is_valid;
}

/**
 * @brief Get winner of the game
 * @detail Winner of the game is the first player who made three consecutive aligned movement
 * @param current_game - the game which we want to determine the winner of
 * @return winner of the game (can be either none/ draw/ account name of host/ account name of challenger)
 */
account_name get_winner(const tic_tac_toe::game& current_game) {
  auto& board = current_game.board;

  bool is_board_full = true;



  // Use bitwise AND operator to determine the consecutive values of each column, row and diagonal
  // Since 3 == 0b11, 2 == 0b10, 1 = 0b01, 0 = 0b00
  vector<uint32_t> consecutive_column(tic_tac_toe::game::board_width, 3 );
  vector<uint32_t> consecutive_row(tic_tac_toe::game::board_height, 3 );
  uint32_t consecutive_diagonal_backslash = 3;
  uint32_t consecutive_diagonal_slash = 3;
  for (uint32_t i = 0; i < board.size(); i++) {
    is_board_full &= is_empty_cell(board[i]);
    uint16_t row = uint16_t(i / tic_tac_toe::game::board_width);
    uint16_t column = uint16_t(i % tic_tac_toe::game::board_width);

    // Calculate consecutive row and column value
    consecutive_row[column] = consecutive_row[column] & board[i];
    consecutive_column[row] = consecutive_column[row] & board[i];
    // Calculate consecutive diagonal \ value
    if (row == column) {
      consecutive_diagonal_backslash = consecutive_diagonal_backslash & board[i];
    }
    // Calculate consecutive diagonal / value
    if ( row + column == tic_tac_toe::game::board_width - 1) {
      consecutive_diagonal_slash = consecutive_diagonal_slash & board[i];
    }
  }

  // Inspect the value of all consecutive row, column, and diagonal and determine winner
  vector<uint32_t> aggregate = { consecutive_diagonal_backslash, consecutive_diagonal_slash };
  aggregate.insert(aggregate.end(), consecutive_column.begin(), consecutive_column.end());
  aggregate.insert(aggregate.end(), consecutive_row.begin(), consecutive_row.end());
  for (auto value: aggregate) {
    if (value == 1) {
      return current_game.host;
    } else if (value == 2) {
      return current_game.challenger;
    }
  }
  // Draw if the board is full, otherwise the winner is not determined yet
  return is_board_full ? N(draw) : N(none);
}

/**
 * @brief Apply create action
 */
void tic_tac_toe::create(const account_name& challenger, const account_name& host) {
  require_auth(host);
  eosio_assert(challenger != host, "challenger shouldn't be the same as host");

  // Check if game already exists
  games existing_host_games(_self, host);
  auto itr = existing_host_games.find( challenger );
  eosio_assert(itr == existing_host_games.end(), "game already exists");

  existing_host_games.emplace(host, [&]( auto& g ) {
    g.challenger = challenger;
    g.host = host;
    g.turn = host;
  });
}

/**
 * @brief Apply restart action
 */
void tic_tac_toe::restart(const account_name& challenger, const account_name& host, const account_name& by) {
  require_auth(by);

  // Check if game exists
  games existing_host_games(_self, host);
  auto itr = existing_host_games.find( challenger );
  eosio_assert(itr != existing_host_games.end(), "game doesn't exists");

  // Check if this game belongs to the action sender
  eosio_assert(by == itr->host || by == itr->challenger, "this is not your game!");

  // Reset game
  existing_host_games.modify(itr, itr->host, []( auto& g ) {
    g.reset_game();
  });
}

/**
 * @brief Apply close action
 */
void tic_tac_toe::close(const account_name& challenger, const account_name& host) {
  require_auth(host);

  // Check if game exists
  games existing_host_games(_self, host);
  auto itr = existing_host_games.find( challenger );
  eosio_assert(itr != existing_host_games.end(), "game doesn't exists");

  // Remove game
  existing_host_games.erase(itr);
}

/**
 * @brief Apply move action
 */
void tic_tac_toe::move(const account_name& challenger, const account_name& host, const account_name& by, const uint16_t& row, const uint16_t& column ) {
  require_auth(by);

  // Check if game exists
  games existing_host_games(_self, host);
  auto itr = existing_host_games.find( challenger );
  eosio_assert(itr != existing_host_games.end(), "game doesn't exists");

  // Check if this game hasn't ended yet
  eosio_assert(itr->winner == N(none), "the game has ended!");
  // Check if this game belongs to the action sender
  eosio_assert(by == itr->host || by == itr->challenger, "this is not your game!");
  // Check if this is the  action sender's turn
  eosio_assert(by == itr->turn, "it's not your turn yet!");


  // Check if user makes a valid movement
  eosio_assert(is_valid_movement(row, column, itr->board), "not a valid movement!");

  // Fill the cell, 1 for host, 2 for challenger
  const uint8_t cell_value = itr->turn == itr->host ? 1 : 2;
  const auto turn = itr->turn == itr->host ? itr->challenger : itr->host;
  existing_host_games.modify(itr, itr->host, [&]( auto& g ) {
    g.board[row * tic_tac_toe::game::board_width + column] = cell_value;
    g.turn = turn;
    g.winner = get_winner(g);
  });
}


EOSIO_ABI( tic_tac_toe, (create)(restart)(close)(move))
  • Modify CMakeLists.txt

# target tic tac toe
add_eosio_wasm_library(tic_tac_toe CONTRACT SOURCES src/tic_tac_toe.cpp)
target_eosio_wasm_compile_definitions(tic_tac_toe PUBLIC -DBOOST_DISABLE_ASSERTS -DBOOST_EXCEPTION_DISABLE)
target_eosio_wasm_include_directories(tic_tac_toe
        PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
        $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>)
       # $<INSTALL_INTERFACE:include>)
target_eosio_wasm_link_libraries(tic_tac_toe eosio libc++ libc musl Boost)

add_eosio_wasm_abi(tic_tac_toe HEADERS include/contracts/tic_tac_toe.hpp)

Building both contracts

> cd build
> rm -rf *
> mkdir install && cmake -DCMAKE_INSTALL_PREFIX=install ..
> make contracts
Scanning dependencies of target hello_link
[  8%] Building LLVM bitcode src/hello.cpp.bc
[ 16%] Linking LLVM bitcode executable lib/hello.bc
[ 25%] Generating textual assembly hello.s
[ 33%] Generating WAST wast/hello.wast
[ 41%] Generating WASM wast/hello.wast
[ 41%] Built target hello_link
Scanning dependencies of target hello_abi_gen
[ 50%] Generating ABI abi/hello.abi
2018-09-20T13:03:36.315 thread-0   abi_generator.hpp:68          ricardian_contracts  ] Warning, no ricardian clauses found for 

[ 50%] Built target hello_abi_gen
Scanning dependencies of target hello
Target hello output wasm to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/bin
Target hello outputllvm bytecode to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/lib
Target hello output wast and asm to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/code
Target hello output abi to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/abi
[ 50%] Built target hello
Scanning dependencies of target tic_tac_toe_link
[ 58%] Building LLVM bitcode src/tic_tac_toe.cpp.bc
[ 66%] Linking LLVM bitcode executable lib/tic_tac_toe.bc
[ 75%] Generating textual assembly tic_tac_toe.s
[ 83%] Generating WAST wast/tic_tac_toe.wast
[ 91%] Generating WASM wast/tic_tac_toe.wast
[ 91%] Built target tic_tac_toe_link
Scanning dependencies of target tic_tac_toe_abi_gen
[100%] Generating ABI abi/tic_tac_toe.abi
2018-09-20T13:03:39.947 thread-0   abi_generator.hpp:68          ricardian_contracts  ] Warning, no ricardian clauses found for 

[100%] Built target tic_tac_toe_abi_gen
Scanning dependencies of target tic_tac_toe
Target tic_tac_toe output wasm to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/bin
Target tic_tac_toe outputllvm bytecode to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/lib
Target tic_tac_toe output wast and asm to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/code
Target tic_tac_toe output abi to /Users/awalga/Data/project/eosio/eosio-cmake-tutorial/build/abi
[100%] Built target tic_tac_toe
Scanning dependencies of target contracts
[100%] Built target contracts

Installing both contracts

> make hello_install
> make tic_tac_toe_install


Use from Clion

Clion is a C++ IDE with built in support for CMake projects and more. Open the previous project from Clion. Clion will create build dependancies based on our CMakeLists.txt

Targets and dependancies in Clion

Next step

The next step will provide features to test and deploy contracts to an eosio contract.

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Congratulations @copernix! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Do not miss the last post from @steemitboard:

SteemitBoard Ranking update - A better rich list comparator
Vote for @Steemitboard as a witness to get one more award and increased upvotes!

Hi! I am a robot. I just upvoted you! I found similar content that readers might be interested in:
https://github.com/EOSIO/eos/blob/master/contracts/tic_tac_toe/tic_tac_toe.cpp

Congratulations @copernix! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 1 year!

Click here to view your Board

Support SteemitBoard's project! Vote for its witness and get one more award!