智能合约

智能合约

Sandwich 2017年12月11日 编辑了这个页面,共4修订

EOS智能合约

6部署和更新智能合约

1.EOS智能合约简介

1.1。所需的背景知识

C / C ++经验

基于EOS.IO的区块链使用Web Assembly(WASM)执行用户生成的应用程序和代码。WASM是一个新兴的网络标准,得到了Google,微软,苹果等公司的广泛支持。目前,最成熟的搭建编译WASM应用的工具是clang / llvm携带的C / C ++编译器。

其他由第三方开发的工具包括:Rust,Python和Solidiity。虽然这些其他语言可能看起来更简单,但它们的性能可能会影响您可以搭建的应用程序的规模。我们认为C ++将成为开发高性能和安全智能合约的最佳语言。

Linux / Mac OS体验

EOS.IO软件仅正式地支持以下环境

  • Ubuntu 16.10或以上,或
  • MacOS Sierra或以上

命令行知识

EOS.IO提供了各种各样的工具,需要您具有基本的命令行知识才能与之交互。

1.2。EOS智能合约基础知识

通信模式

EOS Smart Contract以消息和共享访问内存数据库的形式彼此通信,例如,

只要合约包含在具有异步环境的交易的读取范围内,它就可以读取另一个合约的数据库的状态。异步通信可能导致spam,资源限制算法将解决这个问题。在合约中可以定义两种通信模式:

  • 内联(inline)。内联保证当前交易的执行或展开; 无论成功还是失败,都不会传达通知。内联操作的范围和权限与原始事务具有的范围和权限相同。。
  • 延期(deferred)。延期会在随后由生产者决定进行安排;它可以传达通信的结果,也可以简单地做超时处理。延期可以延伸到不同范围,并承担发送合约的权力。*此功能在STAT中不可用。

消息vs交易

消息表示单个操作,而交易是一个或多个消息的集合。合约和帐户以消息的形式进行通信。消息可以单独发送,如果它们打算作为一个整体来执行,也可以组合形式发送。

包含一条消息的交易。

{

“ref_block_num”: “100”,

“ref_block_prefix”: “137469861”,

“expiration”: “2017-09-25T06:28:49”,

“scope”: [“initb”,”initc”],

“messages”: [

{

“code”: “eos”,

“type”: “transfer”,

“authorization”: [

{

“account”: “initb”,

“permission”: “active”

}

],

“data”: “000000000041934b000000008041934be803000000000000”

}

],

“signatures”: [],

“authorizations”: []

}

包含多个消息的交易,这些消息要么全部成功要么全部失败。

{

“ref_block_num”: “100”,

“ref_block_prefix”: “137469861”,

“expiration”: “2017-09-25T06:28:49”,

“scope”: […],

“messages”: [{

“code”: “…”,

“type”: “…”,

“authorization”: […],

“data”: “…”

}, {

“code”: “…”,

“type”: “…”,

“authorization”: […],

“data”: “…”

}, …

],

“signatures”: [],

“authorizations”: []

}

消息名称限制

消息类型实际上是base32编码的64位整数。这意味着它们前12名字符仅限于字符a-z,1-5和’.’。如果有第13个字符,则它的前16个字符被限制在(’.’和a-p)之间。

交易确认

接收到交易hash并不意味着交易已经被确认,它只是意味着节点不出错地接收了它,这也意味着其他生产者很有可能也接收它。

通过确认,您可以在交易历史记录中看到包含其区块号的交易。

1.3。技术限制

  • 不支持浮点运算合约不会接受浮点运算,因为它是CPU级别的非确定性行为,可能会导致意外的分叉。
  • 交易在1毫秒内执行。一个交易的执行时间需要小于或等于1毫秒,否则交易将失败。
  • 最大30 笔交易/秒。目前公共测试网的设置是限制每个账户每秒最多发起30笔交易。

2智能合约文件

为了简单起见 ,我们创建了一个名为eoscpp的工具,它可以用来辅助一个新的合约。eoscpp也将为您创建3个基本框架的智能合约文件,让你入门。

$ eoscpp -n ${contract}

上面的命令将在“./${project}”文件夹中创建一个带有三个合约文件的新的空项目:

${contract}.abi ${contract}.hpp ${contract}.cpp

2.1  HPP

HPP是包含由CPP文件引用的变量,常量和函数的头文件。

2.2  CPP

CPP文件是包含合约函数的源文件。

如果使用eoscpp工具生成CPP文件,则生成的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 events to this contract

void apply( uint64_t code, uint64_t action ) {

eosio::print( “Hello World: “, eosio::name(code), “->”, eosio::name(action), “\n” );

}

 

} // extern “C”

在那里,你可以看到有2个函数已经被创建,分别为init和apply。他们所做的只是记录传递的信息,并没有进行其他检查。只要区块生产者允许,任何人都可以随时发送任何消息。在没有任何所需的签名的情况下,合同将按所消耗的带宽计费。

 

init函数

该初始化方法只在最开始部署时执行一次。它用于初始化合约变量,等等,例如货币(currency)合约的代币数量。

apply函数

apply是消息处理器,它监听所有传入的消息,并根据函数内的规范作出反应。该apply函数需要两个输入参数,分别是code和action。

 

代码过滤器(code filter)

为了响应一个特定的消息,你可以像下面那样构造你的apply函数。你也可以省略掉代码过滤器,来创建对一般消息的响应。

if (code == N(${contract_name}) {

//your handler to response to particular message

}

在那里你也可以定义对各个动作的回应。

动作过滤器(action filter)

为了响应特定的动作(action),你可以像下面这样创建你的apply函数。这通常与代码过滤器结合使用。

if (action == N(${action_name}) {

//your handler to response to particular action

}

2.3。WAST

任何想要部署到EOS.IO区块链的程序都必须先编译成WASM格式。这是EOS区块链接受的唯一格式。

一旦你准备好CPP文件后,就可以使用eoscpp工具将其编译为WASM(.wast)的文本版本。

$ eoscpp -o ${contract}.wast ${contract}.cpp

2.4。ABI

应用程序二进制接口( ABI)是基于json的描述文件,描述如何在它们的JSON和二进制表示之间转换用户动作。ABI还介绍了如何将数据库状态转换为JSON或如何把JSON转为数据库状态。一旦使用ABI转化了合约,开发人员和用户就可以通过JSON无缝地与您的合约进行交互。

使用eoscpp工具可以从HPP文件生成ABI文件:

$ eoscpp -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定义了一个transfer类型的动作transfer。这告诉EOS.IO,当看到${account}->transfer消息的时候,有效负荷是的transfer的类型。该transfer的类型在对象的structs数组中定义,名字(name)为transger。

“structs”: [{

“name”: “transfer”,

“base”: “”,

“fields”: {

“from”: “account_name”,

“to”: “account_name”,

“quantity”: “uint64”

}

},{

它有几个字段,包括from,to和quantity。这些字段有相应类型为account_name和uint64。accoun_tname是内建(built-in)的类型,用于将base32字符串表示为uint64的类型。要查更多关于内建(built-in)类型的内容,请点击此处

{

“types”: [{

“new_type_name”: “account_name”,

“type”: “name”

}

],

在上面的types数组中,我们定义了现有类型的别名列表。在这里,我们将其定义name为别名account_name。

3.清单

在开始使用EOS智能合约之前,请务必做到以下几点:

创建最新的build

确保你的环境中有最新的build,你将需要它来访问eoscpp和eosc 。有关获取最新内置的说明可以在环境部分找到  。

一旦安装了最新的eosio / eos代码,请确保$ {CMAKE_INSTALL_PREFIX} / bin在您的路径中,没有的话您得通过运行以下命令来安装它。

cd build

make install

连接到EOS.IO区块链

您可以使用该命令连接到一个节点

$ eosc -H ${node_ip} -p ${port_num}

node_ip可以是一个私有节点的IP,您可以使用这里的公共节点ip 连接到公共的测试网。

port_num是8888或8889,具体值取决于配置。

创建一个钱包,并访问一个帐户

为了将合约部署到区块链,您需要在EOS.IO区块链上创建一个帐户。每份合约都需要一个关联的账户

如果您已经持有EOS代币,那么您应该在公共测试网上拥有了一个帐户。如果您需要创建一个新的帐户进行测试,请按照以下说明操作:

4.与智能合约示例交互

在深入构建智能合约之前,您可以参考一些智能合约示例,以了解EOS智能合约如何运作。

为了与这些示例合同进行交互,您需要先完成上述清单中的所有项目,并将示例合约部署到EOS.IO区块链。

4.1。货币(currency)合约

部署示例合约

这里可以找到示例货币合约,如果您已经下载了EOSIO存储库,您应该可以在本地中找到它。

该文件夹包含.abi,.cpp和.hpp文件,您需要先生成.wast文件,然后才能部署合约。

$ eoscpp -o currency.wast currency.cpp

一旦成功生成.wast文件,可以使用set contract命令进行部署。

$ eosc set contract ${contract_account_name} ../contracts/currency.wast ../contracts/currency.abi

Reading WAST…

Assembling WASM…

Publishing contract…

{

“transaction_id”: “1abb46f1b69feb9a88dbff881ea421fd4f39914df769ae09f66bd684436443d5”,

“processed”: {

“ref_block_num”: 144,

“ref_block_prefix”: 2192682225,

“expiration”: “2017-09-14T05:39:15”,

“scope”: [

“eos”,

“${account}”

],

}

确保您的钱包已解锁,并且有active密钥用于$ {contract_account_name}导入。

了解合约

现在,我们已经部署了合约,任何人都可以使用eosc的get code 去检索合约的.abi,并了解哪些接口可用于本合约。

$ eosc get code currency -a currency.abi

code hash: 86968a9091ce32255777e2017fccaede8cea2d4978b30f25b41ee97b9d77bed0

saving abi to currency.abi

$ cat currency.abi

{

“types”: [{

“newTypeName”: “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”,

“indextype”: “i64”,

“keynames”: [

“account”

],

“keytype”: [],

“type”: “account”

}

]

}

结论

  • 该合约接受一个名为transfer的行为(action),该行为接受一条使用为from,to以及quantity字段的消息。
  • 有一个存储数据的account表

现在我们已经发现了可以用来检查余额的转帐行为(action)和帐户表,我们可以使用eosc来于它们进行交互。

 

读取帐户余额

要从表中查询数据,只需使用get table命令即可,语法为eosc get table ${account} ${contract} ${table}。

$ eosc get table ${account} currency account

{

“rows”: [{

“key”: “account”,

“balance”: 1000000000

}

],

“more”: false

}

转账

任何人都可以随时向任何合约发送任何消息,对于没有得到必需许可的消息,合同可能会拒绝该消息。消息不会‘从’任何人发送,而是持有一个或多个帐户和权限级别和许可才能发送消息。

以下命令将在货币合约中将50 代币从account_a 转移到account_b。

$ eosc push message currency transfer ‘{“from”:”${account_a}”,”to”:”${account_b}”,”quantity”:50}’ –scope ${account_a},${account_b} –permission ${account_a}@active

 

我们指定–scope参数来为这些用户提供货币合约的读/写权限,以便修改其余额。将来的发布版本将自动确定这个权限。

您将收到包含transaction_id字段的json输出,作为成功提交的交易的确认。

1589302ms thread-0   currency.cpp:271  operator()  ] Converting argument to binary…

1589304ms thread-0   currency.cpp:290  operator()  ] Transaction result:

{

“transaction_id”: “1c4911c0b277566dce4217edbbca0f688f7bdef761ed445ff31b31f286720057”,

“processed”: {

“refBlockNum”: 1173,

“refBlockPrefix”: 2184027244,

“expiration”: “2017-08-24T18:28:07”,

“scope”: […],

“signatures”: [],

“messages”: […]

}

}

一旦您收到成功的结果,您可以通过从帐户表中读取帐户余额来检查帐户的状态,就像刚刚一样。

4.2。Tic-Tac-Toe

 

Tic-Tac-Toe合约是一个由两方玩的纸和铅笔(paper-and-pencil)游戏,轮流在3×3网格的空间里画上X和O。成功将他们的三个标记放置在垂直或对角线上的玩家获胜。

游戏规则

  • 每个玩家对可以有多达2个游戏,其中玩家1成为主人(host),玩家2成为挑战者,反之亦然
  • 游戏数据以“挑战者”为关键字存储在“主人”范围的游戏表内

坐标 0 1 2
0 Ø X
1 X
2 X Ø Ø

棋盘用数字表示:

  • 0代表空单元格
  • 1表示由主人填充的单元
  • 2代表由挑战者填补的单元

因此,假设x是主人,o是挑战者,上面的棋盘将具有以下表示方式:游戏对象里的[0,2,1,0,1,0,1,2,2]。

部署合约示例

如果您已经下载了EOSIO存储库,那么可以在这里找到tic_tac_toe合约示例,您应该能够在本地驱动器中找到它。

该文件夹包含.abi,.cpp和.hpp文件,您需要先生成.wast文件,然后才能部署该合约。

$ eoscpp -o tic_tac_toe.wast tic_tac_toe.cpp

成功生成.wast文件后,可以使用set contract命令进行部署。对于这个例子,我们将部署tic.tac.toe账户。请注意,EOS.IO区块链仅支持base32 类型字符的帐户名称,因此下划线被替换为“.”。如果您要将其部署到其他非tic.tac.toe账户,请将hpp,.cpp和.abi中的tic.tac.toe.替换为您的帐户名称。

$ eosc set contract tic.tac.toe tic_tac_toe.wast tic_tac_toe.abi

Reading WAST…

Assembling WASM…

Publishing contract…

{

“transaction_id”: “1abb46f1b69feb9a88dbff881ea421fd4f39914df769ae09f66bd684436443d5”,

“processed”: {

“ref_block_num”: 144,

“ref_block_prefix”: 2192682225,

“expiration”: “2017-09-14T05:39:15”,

“scope”: [

“eos”,

“tic.tac.toe”

],

}

了解合约

现在,我们已经部署了合约,任何人都可以使用eosc的get code去获取合约的.abi并了解哪些接口可用于本合约。

$ eosc get code tic.tac.toe. -a tic_tac_toe.abi

code hash: c78d16396a5a63b1be47fd570633084cb5fe2eaa9980ca87ec25061d68299294

saving abi to tic_tac_toe.abi

$ cat tic_tac_toe.abi

{

“types”: [{

“new_type_name”: “account_name”,

“type”: “name”

}

],

“structs”: [{

“name”: “game”,

“base”: “”,

“fields”: {

“challenger”: “account_name”,

“host”: “account_name”,

“turn”: “account_name”,

“winner”: “account_name”,

“board”: “uint8[]”

}

},{

“name”: “create”,

“base”: “”,

“fields”: {

“challenger”: “account_name”,

“host”: “account_name”

}

},{

“name”: “restart”,

“base”: “”,

“fields”: {

“challenger”: “account_name”,

“host”: “account_name”,

“by”: “account_name”

}

},{

“name”: “close”,

“base”: “”,

“fields”: {

“challenger”: “account_name”,

“host”: “account_name”

}

},{

“name”: “movement”,

“base”: “”,

“fields”: {

“row”: “uint32”,

“column”: “uint32”

}

},{

“name”: “move”,

“base”: “”,

“fields”: {

“challenger”: “account_name”,

“host”: “account_name”,

“by”: “account_name”,

“movement”: “movement”

}

}

],

“actions”: [{

“action_name”: “create”,

“type”: “create”

},{

“action_name”: “restart”,

“type”: “restart”

},{

“action_name”: “close”,

“type”: “close”

},{

“action_name”: “move”,

“type”: “move”

}

],

“tables”: [{

“table_name”: “games”,

“type”: “game”,

“index_type”: “i64”,

“key_names” : [“challenger”],

“key_types” : [“account_name”]

}

]

}

观察结果

  • 该合同接受名为create,restart,close和move行动(action),每个行动包含有不同的字段的消息。
  • 有一个存储数据的games表格,

如何玩这个游戏:

  • 使用create行动创建一个游戏,您作为主人,其他帐户作为挑战者。

$ eosc push message tic.tac.toe create ‘ {“challenger”:“$ {challenger_account_name}”,“host”:“$ {your_account_name}”} ‘– permission $ {your_account} @active

  • 第一步需要由主人完成,使用move 动作指定移动需要填充的行和列

$ eosc push message tic.tac.toe move ‘ {“challenger”:“$ {challenger_account_name}”,“host”:“$ {your_account_name}”,“by”:“$ {your_account_name}”,“ ‘ ‘ {”行“:0,”列“:1}”} “– permission $ {your_account} @active

  • 然后让挑战者走一步,之后再轮到主人了。重复这个动作直到确定谁获胜。

$ eosc push message tic.tac.toe move ‘{“challenger”:”${challenger_account_name}”,”host”:”${your_account_name}”,”by”:”${your_account_name}”,””{“row”:1,”column”:1}”}’ –permission ${challenger_account}@active

  • 要重新启动游戏,请使用restart动作。

$ eosc push message tic.tac.toe restart ‘ {“challenger”:“$ {challenger_account_name}”,“host”:“$ {your_account_name}”,“by”:“$ {your_account_name}”} ‘– permission $ {your_account} @active

  • 要从数据库中清除游戏,请使用close操作。游戏结束后,这将释放空间。

$ eosc push message tic.tac.toe close ‘ {“challenger”:“$ {challenger_account_name}”,“host”:“$ {your_account_name}”} ‘– permission $ {your_account} @active

5.编写你的第一个EOS智能合约

Hello World

在这一节中,我们将逐步建立一个Hello World(简单的)合约。

在开始之前,您需要先完成上面清单中的所有项目。

让我们开始 首先,我们eoscpp用来生成智能合约的框架。这将使用abi,hpp和cpp文件在hello文件夹中创建一个新的空项目。

$ eoscpp -n hello

CPP文件应包含一个示例代码,它将在接收到消息时打印文本Hello World:$ {account} – > $ {action}

void apply( uint64_t code, uint64_t action ) {

eosio::print( “Hello World: “, eosio::name(code), “->”, eosio::name(action), “\n” );

}

我们现在应该从这个cpp文件生成.wast。

$ eoscpp -o hello.wast hello.cpp

现在您已有了.wast和.abi文件,您就可以将合约部署到区块链。

假设您的钱包已解锁并且有${account}的密钥,您现在可以使用以下命令将此合约上传到区块链:

$ eosc set contract ${account} hello.wast hello.abi

Reading WAST…

Assembling WASM…

Publishing contract…

{

“transaction_id”: “1abb46f1b69feb9a88dbff881ea421fd4f39914df769ae09f66bd684436443d5”,

“processed”: {

“ref_block_num”: 144,

“ref_block_prefix”: 2192682225,

“expiration”: “2017-09-14T05:39:15”,

“scope”: [

“eos”,

“${account}”

],

“signatures”: [

“2064610856c773423d239a388d22cd30b7ba98f6a9fbabfa621e42cec5dd03c3b87afdcbd68a3a82df020b78126366227674dfbdd33de7d488f2d010ada914b438”

],

“messages”: [{

“code”: “eos”,

“type”: “setcode”,

“authorization”: [{

“account”: “${account}”,

“permission”: “active”

}

],

“data”: “0000000080c758410000f1010061736d0100000001110460017f0060017e0060000060027e7e00021b0203656e76067072696e746e000103656e76067072696e7473000003030202030404017000000503010001071903066d656d6f7279020004696e69740002056170706c7900030a20020600411010010b17004120100120001000413010012001100041c00010010b0b3f050041040b04504000000041100b0d496e697420576f726c64210a000041200b0e48656c6c6f20576f726c643a20000041300b032d3e000041c0000b020a000029046e616d6504067072696e746e0100067072696e7473010004696e697400056170706c790201300131010b4163636f756e744e616d65044e616d6502087472616e7366657200030466726f6d0b4163636f756e744e616d6502746f0b4163636f756e744e616d6506616d6f756e740655496e743634076163636f756e740002076163636f756e74044e616d650762616c616e63650655496e74363401000000b298e982a4087472616e736665720100000080bafac6080369363401076163636f756e7400076163636f756e74”

}

],

“output”: [{

“notify”: [],

“deferred_transactions”: []

}

]

}

}

如果您正在监视eosd进程的输出,您应该看到:

…] initt generated block #188249 @ 2017-09-13T22:00:24 with 0 trxs  0 pending

Init World!

Init World!

Init World!

你会注意到“Init World!” 被执行3次。这不是出错。因为当区块链正在处理交易时,会发生以下事情:

第一:eosd收到一个新的交易(确认交易)

  • 创建一个临时会话
  • 尝试应用交易
  • 成功并打印“Init World!”
  • 或者失败,取消更改(打印“Init World!”后可能会失败)

第二:eosd开始产生一个区块

  • 撤消所有待处理的状态
  • 创建区块时推送(push)所有交易,
  • 第二次打印“Init World!”
  • 搭建区块完成
  • 在创建区块时撤销所有临时更改

第三:eosd推送生成的区块,就像它是从网络接收到的

  • 第三次打印“Init World!”

此时您的合约已准备好开始接收消息。默认消息处理程序接受所有消息,我们可以发送任何我们想发的。让我们尝试发送一个空的消息:

$ eosc push message ${account} hello ‘”abcd”‘ –scope ${account}

该命令将用十六进制字符串“abcd”来发送“hello”的二进制表示数据消息。请注意,稍后我们将演示如何定义ABI,以便您可以用十分易读的JSON对象替换十六进制字符串。现在,我们只想演示如何将消息类型“hello”发送到帐户。

结果:

{

“transaction_id”: “69d66204ebeeee68c91efef6f8a7f229c22f47bcccd70459e0be833a303956bb”,

“processed”: {

“ref_block_num”: 57477,

“ref_block_prefix”: 1051897037,

“expiration”: “2017-09-13T22:17:04”,

“scope”: [

“${account}”

],

“signatures”: [],

“messages”: [{

“code”: “${account}”,

“type”: “hello”,

“authorization”: [],

“data”: “abcd”

}

],

“output”: [{

“notify”: [],

“deferred_transactions”: []

}

]

}

}

如果你在eosd中跟踪,那么你应该看到的屏幕滚动输出下面的内容:

Hello World: ${account}->hello

Hello World: ${account}->hello

Hello World: ${account}->hello

在第三次应用到所生成的区块的一部分之前,再次执行和撤消两次您的合同。

 

如果我们查看ABI文件,您会注意到它定义了一个transfer类型的动作transfer。这告诉EOS.IO,当看到${account}->transfer消息时,可用负荷是transfer类型的。该transfer类型在对象中的structs数组中定义,它的name设置为”transfer”。

“structs”: [{

“name”: “transfer”,

“base”: “”,

“fields”: {

“from”: “account_name”,

“to”: “account_name”,

“quantity”: “uint64”

}

},{

现在我们已经学习了框架定义的ABI,我们可以搭建一个消息调用来进行转账:

eosc push message ${account} transfer ‘{“from”:”currency”,”to”:”inita”,”quantity”:50}’ –scope initc

2570494ms thread-0   main.cpp:797                  operator()           ] Converting argument to binary…

{

“transaction_id”: “b191eb8bff3002757839f204ffc310f1bfe5ba1872a64dda3fc42bfc2c8ed688”,

“processed”: {

“ref_block_num”: 253,

“ref_block_prefix”: 3297765944,

“expiration”: “2017-09-14T00:44:28”,

“scope”: [

“initc”

],

“signatures”: [],

“messages”: [{

“code”: “initc”,

“type”: “transfer”,

“authorization”: [],

“data”: {

“from”: “currency”,

“to”: “inita”,

“quantity”: 50

},

“hex_data”: “00000079b822651d000000008040934b3200000000000000”

}

],

“output”: [{

“notify”: [],

“deferred_transactions”: []

}

]

}

}

如果你观察eosd的输出,你应该看到:

Hello World: ${account}->transfer

Hello World: ${account}->transfer

Hello World: ${account}->transfer

根据ABI,转账消息的格式为:

“fields”: {

“from”: “account_name”,

“to”: “account_name”,

“quantity”: “uint64”

}

我们还知道account _ name – > uint64,这意味着消息的二进制表示与以下内容相同:

struct transfer {

uint64_t from;

uint64_t to;

uint64_t quantity;

};

EOS.IO C API通过Message API提供对消息可用负荷的访问:

uint32_t message_size();

uint32_t read_message( void* msg, uint32_t msglen );

让我们修改hello.cpp来打印出消息的内容:

#include <hello.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” );

}

 

struct transfer {

uint64_t from;

uint64_t to;

uint64_t quantity;

};

 

/// The apply method implements the dispatch of events to this contract

void apply( uint64_t code, uint64_t action ) {

eosio::print( “Hello World: “, eosio::name(code), “->”, eosio::name(action), “\n” );

if( action == N(transfer) ) {

transfer message;

static_assert( sizeof(message) == 3*sizeof(uint64_t), “unexpected padding” );

auto read = read_message( &message, sizeof(message) );

assert( read == sizeof(message), “message too short” );

eosio::print( “Transfer “, message.quantity, ” from “, eosio::name(message.from), ” to “, eosio::name(message.to), “\n” );

}

}

 

} // extern “C”

然后我们可以重新编译并部署它:

eoscpp -o hello.wast hello.cpp

eosc set contract ${account} hello.wast hello.abi

由于重新部署,eosd会再次调用init()

Init World!

Init World!

Init World!

然后我们可以执行转账:

$ eosc push message ${account} transfer ‘{“from”:”currency”,”to”:”inita”,”quantity”:50}’ –scope ${account}

{

“transaction_id”: “a777539b7d5f752fb40e6f2d019b65b5401be8bf91c8036440661506875ba1c0”,

“processed”: {

“ref_block_num”: 20,

“ref_block_prefix”: 463381070,

“expiration”: “2017-09-14T01:05:49”,

“scope”: [

“${account}”

],

“signatures”: [],

“messages”: [{

“code”: “${account}”,

“type”: “transfer”,

“authorization”: [],

“data”: {

“from”: “currency”,

“to”: “inita”,

“quantity”: 50

},

“hex_data”: “00000079b822651d000000008040934b3200000000000000”

}

],

“output”: [{

“notify”: [],

“deferred_transactions”: []

}

]

}

}

在eosd上,我们应该看到以下输出:

Hello World: ${account}->transfer

Transfer 50 from currency to inita

Hello World: ${account}->transfer

Transfer 50 from currency to inita

Hello World: ${account}->transfer

Transfer 50 from currency to inita

使用C ++ API读取消息

到目前为止,我们使用了C API,因为它是EOS.IO直接向WASM虚拟机暴露的最底层API。幸运的是,eoslib提供了一个更高级的API,去除了大部分底层的东西。

/// eoslib/message.hpp

namespace eosio {

template<typename T>

T current_message();

}

我们可以更新hello.cpp变得更简洁如下:

#include <hello.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” );

}

 

struct transfer {

eosio::name from;

eosio::name to;

uint64_t quantity;

};

 

/// The apply method implements the dispatch of events to this contract

void apply( uint64_t code, uint64_t action ) {

eosio::print( “Hello World: “, eosio::name(code), “->”, eosio::name(action), “\n” );

if( action == N(transfer) ) {

auto message = eosio::current_message<transfer>();

eosio::print( “Transfer “, message.quantity, ” from “, message.from, ” to “, message.to, “\n” );

}

}

 

} // extern “C”

您会注意到我们更新了transfer结构体去直接使用eosio::name类型,然后将涉及read _Message的检查压缩为对current-Message的单个调用。

编译并上传之后,您应该得到与C版本相同的结果。

 

需要发送者权限进行转账

任何合约的最常见的要求之一是定义谁可以执行行动。在转账的情况下,我们希望要求由from参数定义的帐户在消息上签名。

EOS.IO软件将负责执行和验证签名,只需获得必要的授权即可。

void apply( uint64_t code, uint64_t action ) {

eosio::print( “Hello World: “, eosio::name(code), “->”, eosio::name(action), “\n” );

if( action == N(transfer) ) {

auto message = eosio::current_message<transfer>();

eosio::require_auth( message.from );

eosio::print( “Transfer “, message.quantity, ” from “, message.from, ” to “, message.to, “\n” );

}

}

在搭建和部署之后,我们可以尝试再次转账:

$ eosc push message ${account} transfer ‘{“from”:”initb”,”to”:”inita”,”quantity”:50}’ –scope ${account}

1881603ms thread-0   main.cpp:797                  operator()           ] Converting argument to binary…

1881630ms thread-0   main.cpp:851                  main                 ] Failed with error: 10 assert_exception: Assert Exception

status_code == 200: Error

: 3030001 tx_missing_auth: missing required authority

Transaction is missing required authorization from initb

{“acct”:”initb”}

thread-0  message_handling_contexts.cpp:19 require_authorization

如果您查看eosd,您将看到以下内容:

Hello World: initc->transfer

1881629ms thread-0   chain_api_plugin.cpp:60       operator()           ] Exception encountered while processing chain.push_transaction:

这表明它试图应用你的交易,打印了初始的“Hello World”,然后在eosio::require_auth找不到帐户授权时中止initb。

我们可以通过告诉eosc添加所需的权限来解决这个问题:

$ eosc push message ${account} transfer ‘{“from”:”initb”,”to”:”inita”,”quantity”:50}’ –scope ${account} –permission initb@active

该–permission命令定义了帐户和权限级别,这个例子中,我们使用默认的active权限。

这一次转账应该像我们之前看到的那样进行。

在错误中中止消息

合约开发的很大一部分是验证前提条件,使得转账的quantity(数量)大于0。如果用户试图执行无效操作,则合约必须中止,并且所做的任何更改都会自动恢复。

void apply( uint64_t code, uint64_t action ) {

eosio::print( “Hello World: “, eosio::name(code), “->”, eosio::name(action), “\n” );

if( action == N(transfer) ) {

auto message = eosio::currentMessage<transfer>();

assert( message.quantity > 0, “Must transfer a quantity greater than 0” );

eosio::requireAuth( message.from );

eosio::print( “Transfer “, message.quantity, ” from “, message.from, ” to “, message.to, “\n” );

}

}

我们现在可以编译、部署并尝试执行数额为0的转账:

$ eoscpp -o hello.wast hello.cpp

$ eosc set contract ${account} hello.wast hello.abi

$ eosc push message ${account} transfer ‘{“from”:”initb”,”to”:”inita”,”quantity”:0}’ –scope initc –permission initb@active

3071182ms thread-0   main.cpp:851                  main                 ] Failed with error: 10 assert_exception: Assert Exception

status_code == 200: Error

: 10 assert_exception: Assert Exception

test: assertion failed: Must transfer a quantity greater than 0

现在你已经完成了Hello World教程,你已经做好准备写出你的第一个智能合约了。

6.部署和更新智能合同

正如在上面的教程中所提到的,将合约部署到区块链中可以简单地通过使用set contract命令来完成,其中set contract命令不仅上传,而且还能更新现有合约,如果您有权限这么做的话。

使用以下命令来:

  • 部署一个新的合约,并
  • 更新现有的合约

$ eosc set contract ${account} ${contract}.wast ${contract}.abi

7.命令摘要

下载并搭建最新的EOS.IO软件 命令为$ build.sh ${architecture} ${build_mode}

写智能合约

  • 使用eoscpp工具创建框架 $ eoscpp -n ${contract}
  • 在.cpp&.hpp文件中编写您的智能合约
  • 生成.abi文件$ eoscpp -g ${contract}.abi ${contract}.hpp
  • 生成.wast文件$ eoscpp -o ${contract}.wast ${contract}.cpp

部署智能合约

  • 连接到一个节点$ eosc -H ${node_ip} -p ${port_num}
  • 创建一个钱包$ eosc wallet create
  • 如果您尚未持有EOS密钥,请创建一个帐户
  • 导入您的帐户密钥$ eosc wallet import ${private_key}
  • 解锁你的钱包$ eosc wallet unlock ${wallet}
  • 部署合约$ eosc set contract ${account} ${contract}.wast ${contract}.abi

8.调试(debug)智能合约

为了能够调试您的智能合约,您需要设置本地eosd节点。这个本地eosd节点可以在单独的私有测试网络运行,也可以在公共测试网(或官方测试网)的扩展运行。

当您第一次创建您的智能合约时,建议先在私有测试网上测试和调试您的智能合约,因为您完全控制了整个区块链。这使您能够拥有所需的无限数量的EOS,您可以随时重置区块链的状态。在准备生产时,可以通过将本地eosd连接到公共测试网(或官方测试网)来在公共测试网(或官方测试网)上进行调试,以便您可以在本地eosd中查看测试网的日志。

这个概念是相同的,所以对于下面的指南,将在私有测试网络上进行调试。

如果您还没有设置自己的本地eosd,请按照设置指南进行操作。默认情况下,除非您修改config.ini文件以与公共测试网(或官方测试网)节点连接,否则您的本地eosd将仅运行在私有测试网络中,如接下来的指南中所述

8.1。方法

  • 用于调试智能合约的主要方法是暴力测试,在这里我们利用打印功能来检查变量的值和检查合约的流程。在智能合约中打印可以通过PrintAPI(CC ++)完成。C ++ API是C API的封装,所以我们通常只使用C ++ API。

8.2。打印

Print  C API支持您可以打印的以下数据类型:

  • prints- 一个null结束字符数组(字符串)
  • prints_l – 给定大小的任何字符数组(字符串)
  • printi – 64位无符号整数
  • printi128 – 128位无符号整数
  • printd – 双编码为64位无符号整数
  • printn – base32字符串,编码为64位的无符号整数
  • printhex – 十六进制给定的二进制数据及其大小

Print c++  API通过重写Print ( )函数来对上面的C API进行封装,所以用户不需要确定他需要使用哪种特定的打印功能。prints C ++ API支持:

  • 一个null终止的字符数组(字符串)
  • 整数(128位无符号,64位无符号,32位无符号,有符号,无符号)
  • base32字符串编码为64位的无符号整数
  • 拥有print()方法的结构体

8.3例

我们来写一个新的合约作为调试的例子

  • 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”);

}

};

}

  • cpp

#include <debug.hpp>

 

extern “C” {

 

void init()  {

}

 

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::current_message<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”

  • hpp

{

“ structs ”:[{

“ name ”:“ foo ”,

“ base ”:“ ”,

“ fields ”:{

“ from ”:“ account_name ”,

“ to ”:“ account_name ”,

“ amount ”:“ uint64 ”

}

}

]

“ actions ”:[{

“ action_name ”: “ foo ”,

“ type ”: “ foo ”

}

]

}

让我们部署它并发送一条消息给它。假设你已经在debug状态创建了账户,并在你的钱包中有秘钥。

$ eoscpp -o debug.wast debug.cpp

$ eosc set contract debug debug.wast debug.abi

$ eosc push message debug foo ‘{“from”:”inita”, “to”:”initb”, “amount”:10}’ –scope debug

当您检查您的本地eosd节点日志时,上面的消息发送后的您会看到以下行:

Code is debug

Action is foo

Amount is smaller than 100

Increase amount by 10

Foo from inita to initb with amount 20

在那里,您可以确认您的消息正在进入正确的控制流程,并且金额已正确更新。您可能会看到上述消息至少2次,这是正常的,因为每个交易都是在验证、区块生成和区块应用期间应用的。

发表评论