基于EOSIO的块链使用的是WebAssembly (WASM)来执行用户编写的智能合约。WASM是一种新兴的Web标准,广泛支持于谷歌、微软、苹果等。对编写WASM标准的智能合约来说使用clang/llvm和它的C/C++编译器是目前最为成熟的编译工具链。
其他的第三方工具链在开发中,包括:Rust, Python, and Solidity。虽然这些语言可能看起来相对简单,但它们可能会影响您所编写的智能性能。我们认为,对于开发高性能和安全的智能合约,C++是最好的语言,将来eos的智能合约也还会继续支持C++。
Linux / Mac OS Experience
EOSIO 支持下面的操作系统: - Amazon 2017.09 and higher - Centos 7 - Fedora 25 and higher (Fedora 27 推荐使用) - Mint 18 - Ubuntu 16.04 (Ubuntu 16.10 推荐使用) - MacOS Darwin 10.12 and higher (MacOS 10.13.x 推荐使用)
Action表示单个操作,而transaction是一个或多个action的集合。Action是合约和账户之间进行通信的方式。Action可以单独执行,或者组合组合起来作为一个整体执行。
仅有一个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": []
}
包含多个action的transaction, 这些action要么全部成功要么全部失败.
{
"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": []
}
从简单易用的角度出发,我们编写了一个工具eosiocpp ,它可以创建一个新的智能合约。eosiocpp也可以创建3个合约文件,它们仅仅包含了合约的框架。
$ eosiocpp -n ${contract}
上面的命令会在./${project}目录下创建一个空的项目,它包含3个文件
${contract}.abi ${contract}.hpp ${contract}.cpp
hpp
${contract}.hpp 这是合约的头文件,可以包含一些变量,常量和函数的声明。
cpp
The ${contract}.cpp 这是合约的源码文件,包含合约的具体实现。
如果你用eosiocpp生成了一个 .cpp, 那它的内容大概类似如下:
#include <${contract}.hpp>
/**
* The init() and apply() methods must have C calling convention so that the blockchain can lookup and
* call these methods.
*/
extern "C" {
/**
* This method is called once when the contract is published or updated.
*/
void init() {
eosio::print( "Init World!\n" ); // Replace with actual code
}
/// The apply method implements the dispatch of actions to this contract
void apply( uint64_t code, uint64_t action ) {
eosio::print( "Hello World: ", eosio::name(code), "->", eosio::name(action), "\n" );
}
} // extern "C"
在这个例子里,我们可以看到两个函数,init和apply。它们会打印log并且不做任何检查。任何人都可以在任何时刻执行BP允许的所有action。在不需要任何签名的情况下,合约将被计入带宽消耗。(Absent any required signatures, the contract will be billed for the bandwidth consumed.)
init
init 仅当合约第一次被部署的时候执行。 在这个函数里可以初始化变量, 比如,在currency合约中总体的token的供应量。
apply
apply 是一个中转函数, 他监听所有传入的action,并且根据action调用合约相应的函数。apply函数需要两个参数, code 和 action。
code filter
这个参数是为了对action做出回应,比如下面的apply函数,你可以构造一个通用响应去忽略code。 (In order to respond to a particular action, structure the apply function as follows. You may also construct a response to general actions by omitting the code filter.)
if (code == N(${contract_name}) {
// your handler to respond to particular action
}
当然你也可以为每个action构造各自的一个响应。
action filter
为了响应每一个action,比如构造比如下面的apply函数。通常和code filter一起使用
if (action == N(${action_name}) {
//your handler to respond to a particular action
}
wast
任何合约程序想要部署到EOSIO的区块链网络中都必须编译成WASM格式。这是EOS的支持唯一个的格式。
一旦你的CPP文件写好了,有就可以用eosiocpp把它编译成WASM (.wast)文件了
$ eosiocpp -o ${contract}.wast ${contract}.cpp
abi
ABI( Application Binary Interface)文件是一个JSON格式的描述文件,说明了如何在他们的JSON和二进制之间转化用户的action。ABI文件也同时说明了如何转换数据库的状态。一旦你用了ABI描述了你的合约,开发人员就和用户就可以和你的合约通过JSON进行交互。
ABI可以通过.hpp文件用eosiocpp生成。
$ eosiocpp -g ${contract}.abi ${contract}.hpp
下面这个例子展示了一个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"]
}
]
}
你会注意到这个ABI定义了一个actoin名字是transfer,类型是transfer。这就是告诉EOSIO,当调用的action是transfer时,它的格式是transfer,定义如下:
...
"structs": [{
"name": "transfer",
"base": "",
"fields": {
"from": "account_name",
"to": "account_name",
"quantity": "uint64"
}
},{
...
ABI文件有很多的部分组成,比如from,to和quantity。每个部分都有自己的类型,比如account_name和uint64。account_name是一个内建类型用base32字符串表示为uint64。想要看到更多的内建类型可以点击这里
{
"types": [{
"new_type_name": "account_name",
"type": "name"
}
],
...
在上面types 数组里,我们为已经存在的account_name类型定义了一个别名name 。
为了能够调试智能合约,你需要在你的本地环境中启动一个nodeos。这个本地的nodeos可以是一个EOS私有的测试网络或者是公网的测试网络。
当你第一次创建智能合约的时候,推荐你最好在你自己的私有测试网络中调试好,因为你对你自己的私有测试网络有完全的掌控权。这可以让你无限制的使用EOS(币)也可以随时复位它的状态。当合约调试完毕,就可以部署到公共测试网络了,本地先运行一个连接到公共测试网络的nodeos,然后连接到这个节点就可以获得log输出了。
步骤是一样的,所以下面这个手册也适用于私有测试网络中的测试。
如果你还没有一个本地的nodeos环境,可以参考这个连接。默认情况下,你的本地nodes会运行在一个私有网络中,除非你修改了config.ini文件,让他去连接公共测试网络,如何修改可以参考这里。
方法
调试最主要的方法就是用Caveman Debugging,我们增强了printing的功能,他可以去输出变量的值并且检查合约的流程。Printing可以通过下面API被合约使用: 这是c 这是 C++). C++的API是对C的封装,所以大多数我们使用C++的API。
Print
Print C API 支持如下数据类型: - prints - a null terminated char array (string) - prints_l - any char array (string) with given size - printi - 64-bit unsigned integer - printi128 - 128-bit unsigned integer - printd - double encoded as 64-bit unsigned integer - printn - base32 string encoded as 64-bit unsigned integer - printhex - hex given binary of data and its size
同时 Print C++ API 对上面的C API进行了封装,所以用户不需要指定应该使用哪种类型的Print。Print C++ API 支持 - 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