EOSForce.io Mainnet Contract Development Tutorial & Documentation

EOSForce
14 min readOct 26, 2018

--

This developer guide is compiled by EOSForce.io Chief Architect FanYang
Translated from Chinese by JC Zhang

Chinese Documentation link: https://eosforce.github.io/Documentation/#/zh-cn/contract/overview

EOSForce.io is designed to provide a more inclusive platform than any previously developed protocol, and we will build a layered architecture with a single settlement layer mainchain and multiple computation layer sidechains. The settlement layer mainchain assumes the main functions of the settlement layer base currency, and supports the issuance settlement layer secondary token and the computation layer sidechain coin. The billing layer will focus on asset accounting and cross-chain interoperability logic. The computation layer will extend the network technology stack to support multiple underlying chain technologies and smart contract technologies. At present, the EOSForce.io mainchain has already supported smart contract development, and developers are welcome to discuss and develop together.

Introduction

A contract in the real world is simply a protocol that gives a set of inputs that specify behavioral outputs. A contract can be a formal legal contract (such as a financial transaction) or simply a game rule. Typical actions include transfer (in financial trading settings) and game moves (in game contract settings).

The EOSForce Smart Contract is a software that is registered on the blockchain and executed on the EOS Force node to implement the semantics of the contract. The action request is stored in the blockchain. The smart contract defines the interface (actions, parameters, data structures) and the code that implements the interface. The code is compiled into a standard bytecode format that the node can acquire and execute. The blockchain stores the transactions of the contract (eg, transfer, game move). Each smart contract is accompanied by a Ricardian Contract that defines the terms and conditions of the contract binding.

Prerequisites

C / C++ experience for executing user-generated WebAssembly (WASM) application and code based on the EOSForce blockchain:

WASM is an emerging web standard that is widely supported by companies such as Google, Mirosoft, and Apple. The most mature toolchain for building applications compiled into WASM is the C/C++ compiler that comes with clang/llvm. For optimal compatibility, the EOSForce toolchain is recommended.

Linux / Mac operating system experience EOS Force Software supports the following environments:

Amazon 2017.09 and higher
Centos 7
Fedora 25 and higher (Fedora 27 recommended)
Mint 18
Ubuntu 16.04 (Ubuntu 16.10 recommended)
Ubuntu 18.04 LTS
MacOS Darwin 10.12 and higher (MacOS 10.13.x recommended)

Command Line Knowledge: EOSForce comes with a lot of tools for command line. You need to have basic command line knowledge in order to interact with them.

Communication Model

The EOS Smart Contract protects a set of action and type definitions. Action defines the specification and the implementation of the contract behavior. Type defines the content and structure required by the specification. EOSForce mainly operates under a message-based communication framework. The client triggers actions by sending (pushing) a message to nodeos. This can be done with the cleos command. You can also use the EOSForce.io send method (such as eosio::action::send). Nodeos dispatches an action request to the WASM code that implements the contract. The contract code runs as a whole and then proceeds to the next action process.

The EOSForce.io smart contracts can communicate with each other, such as letting another contract perform certain operations after the current transaction is completed, or triggering a future transaction outside the current transaction scope.

EOSForce.io supports two basic communication models, inline and deferred. The action performed within the current transaction is an example of an inline action, and triggering a future transaction is an example of a deferred action.

Inter-contract communication should be considered asynchronous. The asynchronous communication model can lead to spam, which is solved by resource-constrained algorithms.

Inline communication

The inline communication is rendered in the form of requesting other actions to be executed as part of the calling action. The inline actions and the original transaction run under the same scope and authorization and are guaranteed to execute with the current transaction. These can be effectively seen as calling nested transactions within a transaction. If any part of the transaction fails, the inline actions will be unsigned with the rest of the transaction. Calling the inline action will not generate any notifications other than returning success or failure.

Deferred communication

The deferred communication concept is presented in the form of a notification sent to the peer tranaction. Deferred actions are determined by the producer to run at a later time. There is no guarantee that the deferred action will be executed.

As mentioned earlier, the deferred communication is scheduled by the producer to be executed later. From the perspective of initiating a transaction (such as a transaction that creates a deferred transaction), it can only determine whether the creation request was successfully submitted or not successfully submitted (if it is not successfully submitted, it will immediately return a failure). The deferred transaction carries the authorization information for the contract. The transaction can cancel the deferred transaction.

Transactions VS. Actions

Action represents a single operation, and transaction is a combination of one or more actions. Contracts and accounts communicate in the form of actions. The actions can be sent separately or in combination if they are expected to be executed as a whole.

One-action transaction

{ “expiration”: “2018–04–01T15:20:44”, “region”: 0, “ref_block_num”: 42580, “ref_block_prefix”: 3987474256, “net_usage_words”: 21, “kcpu_usage”: 1000, “delay_sec”: 0, “context_free_actions”: [], “actions”: [{ “account”: “eosio.token”, “name”: “issue”, “authorization”: [{ “actor”: “eosio”, “permission”: “active” } ], “data”: “00000000007015d640420f000000000004454f5300000000046d656d6f” } ], “signatures”: [ “” ], “context_free_data”: [] }

Multi-action transaction — these actions must succeed or fail together

{ “expiration”: “…”, “region”: 0, “ref_block_num”: …, “ref_block_prefix”: …, “net_usage_words”: .., “kcpu_usage”: .., “delay_sec”: 0, “context_free_actions”: [], “actions”: [{ “account”: “…”, “name”: “…”, “authorization”: [{ “actor”: “…”, “permission”: “…” } ], “data”: “…” }, { “account”: “…”, “name”: “…”, “authorization”: [{ “actor”: “…”, “permission”: “…” } ], “data”: “…” } ], “signatures”: [ “” ], “context_free_data”: [] }

Context-free actions

Action name restriction

The action type is actually a base32 encoded 64-bit integer. This means that the first 12 characters of the name are limited to the characters a-z, 1–5, . If there is a 13th character, it is limited to the first 16 characters of the encoding table.

Transaction confirmation

When the transaction is completed, a transaction receipt is generated. The receipt is presented in hash form. Receiving a transaction hash does not mean that the transaction is answered, it only means that the node correctly accepts it, and it means that other producers will accept it with a high probability.

By confirming, you should be able to see the transaction and protected block numbers in the tranaction history.

Action handler and action “app” context

The smart contract provides the action handler to do the job of the requested action (more on this below). No action is run, for example, the action is “applied” by the apply method in the running contract implementation. EOSFORCEIO creates a new action “application” context in which the action runs. The following figure depicts the key elements of the action “application” context.

Each time an action is run, for example, the action is “applied” by the apply method in the running contract implementation, EOSFORCEIO creates a new action “application” context in which the action runs. The following figure depicts the key elements of the action “application” context.

From the global perspective of the EOSForce.io blockchain, each node in the EOSForce.io network gets a copy and runs each action for each contract. Some nodes handle the actual work of the contract, while other nodes handle it to prove the validity of the transaction block. It is therefore important that the contract can determine “who they are” or, basically, in what context they operate. The context identification information is provided in the action context, as described above, via receiver, code, action. The receiver is the account currently processing the action. Code is the account of the license agreement. Action is the ID of the currently running action.

As discussed above, actions run within transactions: If a transaction fails, the results of all actions within the transaction must be rolled back. A key part of the action context is Current Transaction Data. This includes the transaction header, the ordered array of the original actions in the transaction, the context-independent actions array in the transaction, the croppable context-independent data set defined by the contract code (provided as a blob array), and a full index of the blob array.

EOSForce.io sets clean working memory for the action before processing the action. This is to store the action work variables. The action’s working memory is only available for that action, and even for other actions in the same transaction. Variables may be set when another ction is executed, but are not available in another action context. The only way to pass state between actions is by persisting into the EOSForce.io database and then retrieving from it. See the Persistence API for details on using the EOSForce.io persistence service.

Action has a lot of side effects. Here are some of them:

Change the state of persistence in EOSForce.io persistent storage
Notify the recipient of the current transaction
Send an inline action request to the new recipient
Generate new (deferred) transactions
Cancel existing deferred transactions (for example, cancel a already submitted deferred transaction request)

Transaction limit

Each transaction must be executed in 30 milliseconds or less. If a transaction contains several actions and the execution time of these actions exceeds 30 milliseconds, the entire transaction will fail. In the scenario where there is no concurrent demand for actions, this can be circumvented by including CPU-intensive actions in a separate transaction.

ABI macros and applications

Each smart contract must provide an apply action handler. The apply action handler is a function that listens for incoming actions and performs the desired action. In response to a particular action, code is required to identify and respond to specific actions requests. Apply uses the receiver, code, and action input parameters as filters to map functions that implement a particular desired implementation-specific action. The apply function can be filtered by the code parameter like this:

If (code == N(${contract_name}) { // your handler to respond to particular code }

At a given code, you can respond to a specific action by filtering the action parameter. This is usually used in conjunction with code filtering.

If (action == N(${action_name}) { //your handler to respond to a particular action }

EOSIO_ABI macro

To simplify the work of contract developers, the EOSIO_ABI macro encapsulates the underlying action mapping details of the apply function, allowing developers to focus on application implementations.

#define EOSIO_ABI( TYPE, MEMBERS ) \ extern “C” { \ void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \ auto self = receiver; \ if( code == self ) { \ TYPE thiscontract( self ); \ switch( action ) { \ EOSIO_API( TYPE, MEMBERS ) \ } \ / does not allow destructor of this contract to run: eosio_exit(0); / \ } \ } \ } \

The developer only needs to specify the name of the code and action from the contract, and all C code mapping logic is automatically generated by the macro. Examples of macros used above, such as EOSIO_ABI( hello, (hi) ), hello and hi come from the contract.

This example shows that there is a function apply . All that it does is register the submitted actions, there are no other checks. Anyone can submit any action at any time, as long as the block producer allows it. Without any required signatures, the contract will be billed for consuming bandwidth.

Apply

Apply is the action handler, which listens for all incoming actions and responds according to the specifications within the function. The apply function takes two input parameters, code and action.

Code filtering

In response to a specific action, the structured apply function is as follows. You can also omit code filtering to build a generic actions response.

If (code == N(${contract_name}) { // your handler to respond to particular code }

You can also define the response of a single actions in a code block.

Action filtering

In response to a specific action, the structured apply function is as follows. This is usually used in conjunction with code filtering.

If (action == N(${action_name}) { //your handler to respond to a particular action }

Wast

Any program deployed to the EOSForce.io blockchain must be compiled into WASM format. This is the only format that block links are subject to.

Once the CPP file is complete, you can compile it into a text version of WASM (.wast) using the eosiopcp tool. Eosiocpp is deprecated as of v1.2.0 and v1.3.0 will be removed. Will be replaced with eosio-cpp in the eosio.wasmsdk library. The parameters may also change accordingly.

eosio−cpp−o{contract}.wast ${contract}.cpp

ABI

The Application Binary Interface (ABI) is a JSON-based description that describes how to convert user actions between JSON and binary representation. ABI also describes how to convert database state to/from JSON. Once you describe the contract through ABI, developers and users will be able to interact seamlessly with the contract through JSON.

ABI files can be passed from the .hpp file using the eosio-cpp tool.

eosio−cpp−o{contract}.wast ${contract}.cpp — abigen

Here is an example of a skeleton contract ABI:

{ “types”: [{ “new_type_name”: “account_name”, “type”: “name” } ], “structs”: [{ “name”: “transfer”, “base”: “”, “fields”: { “from”: “account_name”, “to”: “account_name”, “quantity”: “uint64” } },{ “name”: “account”, “base”: “”, “fields”: { “account “:”name”, “balance”: “uint64” } } , “actions”: [{ “action”: “transfer”, “type”: “transfer” } ], “tables”: [{ “table” : “account”, “type”: “account”, “index_type”: “i64”, “key_names” : [“account”], “key_types” : [“name”] } ] }

You will notice that this ABI defines an action transfer of type transfer. This tells EOSFORCEIO that the load is of type transfer when seeing ${account}->transfer. The transfer type is defined in the object whose name is transfer in the structs array.

“structs”: [{ “name”: “transfer”, “base”: “”, “fields”: { “from”: “account_name”, “to”: “account_name”, “quantity”: “uint64” } },{ …

ABI has several fields, including from, to, and quantity. These fields have the corresponding types account_name and uint64. Account_name is a built-in type that represents a string as a base32 encoded uint64. To learn more about the built-in types, click here.

{ “types”: [{ “new_type_name”: “account_name”, “type”: “name” } ], …

In the types array above we define a list of aliases for the existing type. Here, we define name as the alias of account_name .

Multi-index database programming interface

Overview

EOSFORCEIO provides a set of services and interfaces that enable contract developers to persist state across cross-domain actions and trasactions. If there is no persistence, the state generated during the actions and transactions processing will be lost when the scope is processed. Persistence components include:

1. service that persists state to a database

2. Enhanced ability to find and retrieve database content

3. C++ APIs for these services, targeted for contract developers

4. C APIs for core services, targeted for interested libraries and system developers

This document covers the first three topics.

Persistence service requirements

Actions performs the work of the EOSForce.io contract. Actions run in an environment called the action context. As described in the Action “application” context diagram, the action context provides a few things that the action must do. One of them is action working memory. This is where the action maintains the working state. EOSForce.io sets a piece of working memory for the action before processing an action. Variables set by other action executions are not available for the new action context. The only way to pass state between actions is to access them from the EOSForce.io database.

EOSForce.io Multi-Index programming interface

The EOSForce.io Multi-Index programming interface provides a C++ interface to access the EOSFORCEIO database. The EOSFORCEIO Multi-Index programming interface mimics the Boost Multi-Index container. The programming interface provides an object storage model with rich retrieving capabilities that allows multiple indexes with different sorting and access semantics to be used. The Multi-Index programming interface is in the eosio::multi_index C++ class in the eosforce/eosforce GitHub repository contracts/eosiolib directory. This class allows C++ contracts to read and modify the persistence state in the EOSFORCEIO database.

Multi-Index container interface eosio::multi_index provides a container of any C++ type of the same kind (not necessarily an old-fashioned or fixed-size one), can have multiple indexes, and each sorts by key that inherits from the type of the object. Can be compared to rows, columns, and indexes of traditional database tables. It is also easy to analogize with Boost Multi-index. In fact, the member function signature of eosio::multi_index is modeled as boost::multi_index, although there are important differences.

Eosio::multi_index can be viewed as a table in a traditional database. A row is a single object in a container. A column is a member property of an object in a container. The index provides a quick lookup object based on the primary key of the compatible object member property.

Traditional database tables allow indexing through custom functions on columns of a table. Similarly, eosio::multi_index also allows indexes to be custom functions (as a member function of the element type class), but the return type is limited to one of the supported primary key types.

Traditional database tables typically have a single unique primary key that identifies a particular row in the table without ambiguity and provides a standard sort order for the rows in the table. Eosio::multi_index supports similar semantics, but the primary key of an object in the eosio::multi_index container must be a unique unsigned 64-bit integer. Objects in the eosio::multi_index container are sorted in ascending order by primary key index.

EOSForce.io Multi-Index Iterator

One key point of the EOSFORCEIO persistence service over other blockchain infrastructures is the Multi-Index iterator. Unlike other blockchains that only provide built-value-pair storage, the EOSFORCEIO Multi-Index table allows contract developers to maintain multiple collections of objects sorted by different key types, and key values ​​can be derived from the object data. This adds a wealth of search capabilities. Up to 16 indexes can be defined, each with its own way of sorting and retrieving table contents.

The EOSFORCEIO Multi-Index iterator follows the same pattern as the C++ iterator. All iterators are bidirectional const, or const_iterator or const_reverse_iterator. An iterator can be referenced indirectly to access objects in the Multi-Index table.

Comprehensives

How to create your EOSFORCEIO Multi-Index table

Here is a summary of the steps to create your own persistent data with the EOSFORCEIO Multi-Index table:

Define your object with a C++ class or struct. Each type of object has its own Multi-Index table.
Define a const member function in class / struct named primary_key with a return type of uint64_t object primary key value.
Decide on the secondary index. Support up to 16 additional indexes. The secondary index supports the following key types.
Idx64 - original unsigned 64-bit integer key
Idx128 - original unsigned 128-bit integer key, or 128-bit fixed-size dictionary-compiled key
Idx256 - 256-bit fixed-size dictionary compiled key
Idx_double - Double precision floating point key
Idx_long_double - quadruple precision floating point key
Define a key extractor for each secondary index. The key extractor is a function that extracts keys from a Multi-Index table element. See the Multi-Index constructor and the indexed_by section below.

How to user your EOSForce.io Multi-Index table

Instantiate the Multi-Index table
Call emplace, modify, and erase to insert, modify, and delete objects, respectively.
Call get, find, and iterator operations to locate and traverse objects

Naming conventions

Standard account name

Can only contain the characters .abcdefghijklmnopqrstuvwxyz12345 . Must start with a letter must contain 12 characters

Table name, result, function, class

Can only contain up to 12 alpha characters

symbol

Must be an uppercase alpha character (A — Z) up to 7 characters

Principle

Macro and function related naming

N (base32 X)

The encoding parameter is EOSFORCEIO name (base32) as a fixed name

If converting from a variable to EOSFORCEIO name, you should use eosio::string_to_name()

Technique

  1. The encoding script is EOSForce.io name, see eosio::string_to_name 2. Decode from base32 to string, with name::to_string()
auto user_name_obj = eosio::name{user}; // account_name user
std::string user_name = user_name_obj.to_string();

--

--

EOSForce
EOSForce

Written by EOSForce

Decentralized high-performance smart contract platform www.eosforce.io

No responses yet