EOS 智能合约

article/2025/9/13 5:32:12

1. EOS智能合约的介绍

1.1. 所需背景知识

C / C++ 经验

基于EOS.IO的区块链使用Web Assembly(WASM)执行开发者提供的应用代码。WASM是一个已崭露头角的web标准,受到Google, Microsoft, Apple及其他大公司的广泛支持。目前为止,最成熟的用于构建应用及WASM代码编译的工具链是clang/llvm及其C/C++编译器。

其他由第三方开发中的工具链包括:Rust, Python, and Solidiity。尽管用其他语言更简单,但是他们的性能很可能制约你所构建的应用规模。我们希望C++ 将成为开发高性能及安全智能合约的最佳语言。

Linux / Mac OS 经验

EOS.IO软件仅官方支持如下环境

  • Ubuntu 16.10 或更高
  • MacOS Sierra 或更高

命令行知识

EOS.IO提供了一系列工具,需要基本的命令行知识来操作它们。

1.2. EOS智能合约基础知识

通信模型

EOS智能合约通过messages 及共享内存数据库(比如只要一个合约被包含在transaction的读取域中with an async vibe,它就可以读取另一个合约的数据库)相互通信。异步通信导致的spam问题将由资源限制算法来解决。下面是两个在合约里可定义的通信模型:

  • Inline. Inline保证执行当前的transaction或unwind;无论成功或失败都不会有通知。Inline 操作的scopes和authorities和原来的transaction一样。

  • Deferred. Defer将稍后由区块生产者来安排;结果可能是传递通信结果或者只是超时。Deferred可以触及不同的scopes,可以携带发送它的合约的authority*此特性在STAT不可用

Message vs Transaction

一个message代表单个操作, 一个transaction是一个或多个messages的集合。合约和账户通过messages通信。Messages可以单个地发送,如果希望一次执行批处理也可以集合起来发送。

单message的Transaction

多messages的Transaction,这些messages将全部成功或全部失败。

Message名的限定

Message的类型实际上是base32编码的64位整数。所以Message名的前12个字符需限制在字母a-z, 1-5, 以及'.' 。第13个以后的字符限制在前16个字符('.' and a-p)。

Transaction确认

获得一个transaction哈希并不等于transaction完成,它只表示该节点无报错地接受了,而其他区块生产者很可能也会接受它。

但要确认该transaction,你需要在transaction历史中查看含有该transaction的区块数。

1.3. 技术限制

  • 无浮点数. 合约不接受浮点小数计算因为这在CPU层级上是一个不确定的行为,会导致意想不到的分叉。
  • Transaction需要在1 ms内执行. transaction的执行时间需要在*小于等于1ms否则transaction将会失败。
  • 最大 30 tps. 目前根据测试公网设置,每个账户最多每秒可发布30个transactions.

2  智能合约文件

为简单起见我们创造了一个工具叫 eoscpp,它可以用来引导产生一个新合约。eoscpp将创造三个智能合约文件,他们是你起步开发的框架。

以上将在'./${project}'文件夹下创建一个新项目,包含三个文件:

2.1. HPP

HPP是包含CPP文件所引用的变量、常量、函数的头文件。

2.2. CPP

CPP文件是包含合约功能的源文件。

如果您通过eoscpp工具产生CPP文件,产生的文件将和如下相似:

这里您可以看到我们创建了两个函数,initapply。他们所做的是记录所有提交的messages 到日志中且并不作检查。只要区块生产者同意,任何人在任何时间都可以提交任何message。如果缺少所需的签名,合约将会被收取消耗带宽的费用。

init

init仅在被初次部署的时候执行一次。它是用于初始化合约变量的,例如货币合约中提供token的数量。

apply

apply是message处理器,它监听所有输入的messages并根据函数中的规定进行反馈。apply函数需要两个输入参数,codeaction

code filter

为了响应特定message,您可以如下构建您的apply函数。您也可以忽略code filter来构建一个响应通用messages的函数。

在其中您可以定义对不同actions的响应。

action filter

为了响应特定action,您可以如下构建您的apply函数。常和code filter一起使用。

2.3. WAST

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

一旦您完成了CPP文件的开发,您可以用eoscpp工具将它编译成一个文本版本的WASM (.wast) 文件。

2.4. ABI

Application Binary Interface (ABI)是一个基于JSON的描述文件,是关于转换JSON和二进制格式的用户actions的。ABI还描述了如何将数据库状态和JSON的互相转换。一旦您通过ABI描述了您的合约,开发者和用户就能够用JSON和您的合约无缝交互了。

ABI文件可通过eoscpp工具从HPP文件生成:

这里是一个合约的骨架ABI的例子:

您肯定注意到了这个ABI 定义了一个叫transfer的action,它的类型也是transfer。这就告诉EOS.IO当${account}->transfer的message发生时,它的payload是transfer类型的。 transfer类型是在structs的列表中定义的,其中有个对象,name属性是transfer

这部分包括fromto 和 quantity等字段。这些字段都有对应的类型:account_nameuint64account_name 是一个用base32编码来表示uint64的内置类型。 要了解更多的可用内置类型,请点击这里.

上述types列表内,我们定义了一系列现有类型的别名。这里,我们把account_name定义为name的别名。

3. 清单

在开始EOS智能合约开发之前,我们需要搞清下面的内容:

构建最新的版本

请确认您环境中的是最新的版本,您才能获取到eoscppeosc这些对于您开发非常重要的工具。 如何获取最新构建版本可以在 环境 章节找到。

一旦您安装了最新版本的eosio/eos代码,请确认您的环境变量中有${CMAKE_INSTALL_PREFIX}/bin,如果没有的话您可以用下面的命令安装。

连接到EOS.IO区块链

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

node_ip可以是私有的节点IP。如果您连接的是测试公网,您需要用节点的公共IP这里.

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

创建钱包获取账户

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

如果您已经有EOS Tokens,您应该在测试公网上已经有一个账户了。如果您需要新建一个测试账户,请参考以下信息:

  • 创建钱包
  • 创建账户
  • 将账户导入创建的钱包

4. 与智能合约交互的例子

在深入了解如何构建一个智能合约前,我们这里提供了一些智能合约的例子,供您参考以更快的理解EOS智能合约是如何工作的。

为了和这些样例合约交互,您需要先完成清单上面的步骤并且部署样例合约到EOS.IO区块链上。

4.1. 货币合约

部署样例合约

样例货币合约可在这里找到,如果您已经下载了EOSIO仓库的话,那您应当可以在本地磁盘上找到。

文件夹中包括.abi, .cpp 和 .hpp 文件,在您部署合约前您需要编译生成.wast文件。

您成功生成.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}"],  ...
}

请确认您的钱包是解锁状态,且您已经将对应${contract_account_name}的有效的key导入到钱包。

了解合约

现在我们已经部署了合约,任何人都可以用eoscget 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的transaction,此transaction接受一个有fromtoquantity字段的message。
  • 同时有一个叫account的table用于存储数据。

既然我们有一个transfer action,而这个账户table可以用来查余额,我们可以用eosc来和他们交互。

读取账户余额

要从一个表中读取数据,需使用get table命令:eosc get table ${account} ${contract} ${table}.

$ eosc get table ${account} currency account
{"rows": [{"key": "account","balance": 1000000000}],"more": false
}

资金转账

任何人都可以在任何时间向任何合约发任何message但是合约有可能拒绝那些没有特定权限的messages。Messages实际上并不是从任何“人”发出的,它们是“伴随一个或多个账户及其特定等级的permission”发出的。

下面的命令将在货币合约中从account_a到account_b转账50个token

$ eosc push message currency transfer '{"from":"${account_a}","to":"${account_b}","quantity":50}' --scope ${account_a},${account_b} --permission ${account_a}@active

我们指定了--scope参数来给与货币合约对于那些能修改自身余额的用户的读写权限。在下个版本中,scope将会自动确定。

您将会收到如下的包含transaction_id字段的JSON输出,作为本次transaction成功提交的确认信息。

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": [...]}
}

一旦您获得了这个成功结果,您就可以像刚才一样从账户table中查看账户的余额和状态了。

4.2. Tic-Tac-Toe

tic-tac-toe是一个两人玩的纸笔游戏,用X和O,两人分别轮流在3×3的格子里标记,先完成横向,纵向或对角线三个格子的标记的人获得胜利。

游戏规则

  • 每对玩家可以有最多2轮游戏,第一轮是1号玩家是host,2号玩家是challenger,第二轮反过来。
  • 游戏数据存储于“host”的游戏表格中,而"challenger"是key。

    例子

        

用数字在板上表示:

  • 0 代表空格子
  • 1 代表被host占据的
  • 2 代表被challenger占据的

因此,假设x是host,o是challenger,上面的比赛板可以在此局游戏的对象中如下表示:[0, 2, 1, 0, 1, 0, 1, 2, 2]。

部署样例合约

tic_tac_toe合约在这里,如果您已经下载了EOSIO仓库的话,那您应当可以在本地磁盘上找到。

文件夹中包括.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"],  ...
}

了解合约

现在我们已经部署了合约,任何人都可以用eoscget 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"]}]
}

注释

  • 此合约接受createrestartclosemove的actions,每个actions接受具有不同字段的messages。
  • games的table用于保存数据

如何玩游戏:

  • 使用create来创建游戏,设置您的账户为host其他人的为challenger。
$ eosc push message tic.tac.toe create '{"challenger":"${challenger_account_name}","host":"${your_account_name}"}' --permission ${your_account}@active
  • 第一步host走,用moveaction指定需要填入哪行哪列的格子来完成一次移动。
$ eosc push message tic.tac.toe move '{"challenger":"${challenger_account_name}","host":"${your_account_name}","by":"${your_account_name}","''{"row":0,"column":1}"}' --permission ${your_account}@active
  • 然后让challenger走,然后再是host走。不断重复直至决出赢家。
$ 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 action重启游戏
$ eosc push message tic.tac.toe restart '{"challenger":"${challenger_account_name}","host":"${your_account_name}","by":"${your_account_name}"}' --permission ${your_account}@active
  • close action来将此游戏从数据库清除,这样会在游戏结束后释放空间。
$ 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来生成智能合约的骨架。这将在hello文件夹里产生一个空白工程,里面有abi,hpp和cpp文件。

$ eoscpp -n hello

CPP文件含有一个当收到message后打印 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}的keys,您就可以上传用此命令把合约上传到区块链上:

$ 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!"被执行了三次,这其实并不是个bug。
区块链处理transactions的流程是:

1: eosd收到一个新transaction (正在验证的transaction)

  • 创建一个新的临时会话
  • 尝试应用此transaction
  • 成功并打印出"Init World!"
  • 失败则回滚所做的变化 (也有可能打印"Init World!"后失败)

2 : eosd开始产出区块

  • 撤销所有pending状态
  • pushes all transactions as it builds the block
  • 第二次打印"Init World!"
  • 完成区块
  • 撤销所有创造区块时的临时变化

3 : eosd如同从网络上获得区块一样将区块追加到链上。

  • 第三次打印 "Init World!"

此时,您的合约就可以开始接受messages了。因为默认message处理器接受所有messages,我们可以发送任何我们想发的东西。我们试一下发一个空的message:

$ eosc push message ${account} hello '"abcd"' --scope ${account}

此命令将"hello"message及16进制字符串"abcd"所代表的二进制文件传出。注意,后面我们将展示如何定义ABI来用一个好看易读的JSON对象替换16进制字符串。以上,我们只是想证明“hello”类型的message是如何发送到账户的。

结果是:

{"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

再一次,您的合约在transaction被第三次应用并成为产出的区块之前被执行和撤销了两次。

如果我们查看ABI文件,您将会注意到这个ABI 定义了一个叫transfer的action,它的类型也是transfer。这就告诉EOS.IO当${account}->transfer的message发生时,它的payload是transfer类型的。 transfer类型是在structs的列表中定义的,其中有个对象,name属性是transfer

..."structs": [{"name": "transfer","base": "","fields": {"from": "account_name","to": "account_name","quantity": "uint64"}},{
...

在弄清骨架ABI后,我们可以构造一个transfer类型的message:

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,transfer message应该是如下格式的:

 "fields": {"from": "account_name","to": "account_name","quantity": "uint64"}

我们也知道account_name -> uint64表示这个message的二进制表示如同:

struct transfer {uint64_t from;uint64_t to;uint64_t quantity;
};

EOS.IO的C API通过Message API提供获取message的payload的能力:

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 contractvoid 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!

然后我们执行transfer:

$ 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来读取 Messages

目前我们使用是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" {/***  此方法仅在合约发布或升级时调用一次*/void init()  {eosio::print( "Init World!\n" );}struct transfer {eosio::name from;eosio::name to;uint64_t quantity;};/// apply 方法实现了合约事件的分发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的struct,直接使用eosio::name 类型并将read_message前后的类型检查压缩为一个单个的current-Message调用。

在编译和上传后,您将看到和C语言版本同样的结果。

获取发送者的Authority来进行转账

合约最普遍的需求之一就是定义谁可以进行这样的操作。比如在货币转账的例子里,我们就需要定义为from字段的账户核准此message。

EOS.IO软件负责加强和验证签名,您需要做的是获取所需的authority。

...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 Exceptionstatus_code == 200: Error: 3030001 tx_missing_auth: missing required authorityTransaction 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:
...

这表示此操作尝试请求应用您的transaction,打印出了初始的"Hello World",然后当eosio::require_auth没能成功获取initb账户的authorization后,操作终止了。

我们可以通过让eosc增加所需的permission来修复这个问题:

$ eosc push message ${account} transfer '{"from":"initb","to":"inita","quantity":50}' --scope ${account} --permission initb@active

--permission 命令定义了账户和permission等级,此例中我们使用active authority,也就是默认值。

这次转账应该就成功了,如同我们之前看到的一样。

发生错误时终止Message

绝大多数合约开发中有非常多的前置条件,比如转账的金额要大于0。如果用户尝试进行一个非法action,合约必须终止且已做出的任何变动都必须自动回滚。

...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@active3071182ms thread-0   main.cpp:851                  main                 ] Failed with error: 10 assert_exception: Assert Exceptionstatus_code == 200: Error: 10 assert_exception: Assert Exceptiontest: 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 keys的话
  • 导入账户的key $ eosc wallet import ${private_key}
  • 解锁钱包 $ eosc wallet unlock ${wallet}
  • 部署合约 $ eosc set contract ${account} ${contract}.wast ${contract}.abi

8. 调试智能合约

为调试智能合约,您需要安装本地的eosd节点。本地的eosd节点可以以单独的调试私网运行也可以作为调试公网(或官方的调试网络)的延伸来运行。当您在第一次创建智能合约的时候,最好先在测试私网中测试调试完毕您的智能合约,因为您可以完全掌握整个区块链。这使得您有无限的eos而且可以随时重置区块链的状态。当合约可以上生产环境时,可以通过将您的本地eosd和测试公网(或官方的调试网络)连接起来以完成公网的调试,这样您就可以在本地的eosd上看到测试网络的数据了。

因为概念是一致的,所以接下来的指南中将会介绍在测试私网中的调试。

如果您还没有安装您的本地eosd请根据安装指南安装。默认情况下,您的本地eosd将只在测试私网中运行,除非您修改config.ini 文件来将其与测试公网(或官方的调试网络)节点连接,就像该指南中提到的一样。

8.1. 方法

用于调试智能合约的主要方法是 Caveman调试法,我们使用打印的方法来监控一个变量并检查合约的流程。在智能合约中打印信息可以通过打印API (C 和 C++)来完成。 C++ API 是 C API的封装,因此大多数情况下我们用的是C++ API。

8.2. 打印

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

打印时,C++ API 通过重写print()方法封装了一些上面的C API使得用户不需要关心需要调用那个打印函数。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

8.3. 例子

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

  • 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 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"
  • debug.hpp
{"structs": [{"name": "foo","base": "","fields": {"from": "account_name","to": "account_name","amount": "uint64"}}],"actions": [{"action_name": "foo","type": "foo"}]
}

让我们部署并发个message给它。假设您已经创建了debug账户,钱包中也有对应的key。

$ 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节点日志时,您将看到前一条message发送后的如下内容

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

这样您就可以确认您的message经过了正确的控制流且数据被正确地更新了。您可能会看到上面的至少两次,这很正常因为每个transaction在验证、生产区块及区块应用的阶段都会被应用一次


http://chatgpt.dhexx.cn/article/Q30wBACQ.shtml

相关文章

EOS智能合约开发(三)EOS创建和管理账户

创建好钱包和密钥后&#xff0c;我们就需要创建账户。为什么创建账户&#xff0c;为了方便人与区块链交互。以太坊40位地址&#xff0c;让我们非常难以记忆。EOS有账户概念&#xff0c;我们就可以定义账户权限。 在区块链上执行操作&#xff0c;需要使用到账号。我们使用cleos…

固化EOS智能合约,监管升级权限,净化EOS DAPP生态

最近EOS版的Fomo 3D狼人杀游戏骗局引发了大家对EOS智能合约的安全性的大讨论。 和以太坊智能合约的不可升级不同&#xff0c;EOS智能合约可升级&#xff0c;因而保存在智能合约中的数据称不上去中心化&#xff0c;因为智能合约的管理员可偷偷的升级智能合约来修改合约里的任何数…

[EOS源码分析]7.EOS智能合约开发实践之合约调用合约(inline action)

首先&#xff0c;目前dawn-4.1, dawn-4.2使用inline action是会报如下错误 transaction declares authority {"actor":"hello.code","permission":"active"}, but does not have signatures for it under a provided delay of 0 ms 这…

区块链实现智能合约

区块链实现智能合约 一、制定生成智能合约 1、首先参与智能合约的用户必须先注册成为区块链的用户&#xff0c;区块链返回给用户一对公钥和私钥。公钥做为用户在区块链上的账户地址&#xff0c;私钥做为操作该账户的唯一钥匙。 2、两个以两个以上的用户根据需要&#xff0c;…

以太坊中的智能合约

以太坊中的智能合约&#xff08;Smart Coantract&#xff09; 创建智能合约 以太坊中的智能合约是运行在区块链上的一段代码&#xff0c;代码的逻辑定义了合约的内容。合约的账户保存了合约当前的运行状态&#xff0c;主要包含了4部分内容。 balance&#xff1a;当前余额non…

智能合约(一)————智能合约入门

1、智能合约的基本组成 1.1.程序版本 1.2. 合约声明 1.3.状态变量 1.4.合约方法 在这里constant相当于他声明这个局部变量不能更改&#xff0c;但是他并没有实际作用&#xff08;实际就只是警示作用&#xff09;2、地址adress - address.balance 账户余额 - adress.transfe…

EOS 智能合约源代码解读 (10)token合约“简介”

1. 记录用户的token&#xff0c;比如有哪些代币 class [[eosio::contract("eosio.token")]] token : public contract {public:using contract::contract;[[eosio::action]]void _create( const text_name& issuer, const asset& maximum_supply);[[eosi…

区块链智能合约介绍

作者&#xff1a;qinyutong、chengyueqiang 智能合约 (smart contract) 是一种由事件驱动的、具有状态的代码合约和算法合同 [11]&#xff0c;随着以比特币为代表的区块链技术的蓬勃发展, 区块链技术已经开始逐步超越可编程货币时代而进入智能合约时代。智能合约作为区块链的核…

智能合约简介

区块链技术简史 区块链技术的第一次应用是在2008年&#xff0c;当时比特币首次亮相。抛弃传统金融机构&#xff0c;比特币引入了促进数字金融交易的新方法。然而&#xff0c;尽管在当时这是革命性的举措&#xff0c;但在这种状态下&#xff0c;区块链技术无法得到广泛应用。 区…

十分钟教你开发EOS智能合约

在CSDN、柏链道捷&#xff08;PDJ Education&#xff09;、HelloEOS、中关村区块链产业联盟主办的「EOS入门及最新技术解读」专场沙龙上&#xff0c;柏链道捷&#xff08;PDJ Education&#xff09;CTO、副总裁康烁&#xff0c;作了「如何在EOS上开发智能合约」的精彩演讲。演讲…

智能合约--如何实现可升级的智能合约

一. 什么是智能合约 智能合约通俗点说就是写在区块链上面的代码&#xff0c;代码里面编写着严谨完善的规则&#xff0c;一旦某个用户满足了合约里面的规则条件&#xff0c;就会触发里面的代码&#xff0c;执行某个方法。 二. 为什么要使智能合约达到可升级 智能合约的特点之一…

智能合约简单介绍

本学期学习了区块链的课程&#xff0c;作业是对于智能合约学习后的报告&#xff1a; 1 智能合约简单了解 1.1智能合约是什么 智能合约是由事件驱动的、具有状态的、部署于可共享的分布式数据库上的计算机程序&#xff0c;多用IF-THEN语句。狭义来说&#xff0c;智能合约是设计…

智能合约

智能合约&#xff08;英语&#xff1a;Smart contract &#xff09;是一种旨在以信息化方式传播、验证或执行合同的计算机协议。智能合约允许在没有第三方的情况下进行可信交易。这些交易可追踪且不可逆转。[1]智能合约概念于1994年由Nick Szabo首次提出。智能合同的目的是提供…

[EOS源码分析]6.EOS特殊智能合约eosio

这里说的eosio智能合约不是泛指eos的智能合约&#xff0c;它是一个特殊的具体的合约。它本事可大了&#xff0c;我们一起来看看它有哪些功能 负责智能合约部署 大家有注意到如下红色字体的log吗 $ cleos set contract hello.code ../eos-contract/hello -p hello.code Publish…

EOS智能合约开发(一)

一、 智能合约功能二、 通讯模式 2.1 运行机制2.2 内联通信2.3 延迟通信2.4 交易和动作2.5 交易确认2.6 动作处理程序和动作的apply上下文2.7 交易限制 三、文件结构 3.1 创建框架3.2 apply处理程序3.3 EOSIO_ABI 宏 四、多索引数据库API 4.1 EOSIO多索引API4.2 EOSIO多索引迭代…

EOS系列 - WASM智能合约 - 特性

构造函数 addressbook(name receiver, name code, datastream<const char*> ds):contract(receiver, code, ds) {}#单例表&#xff08;code和scope都用receiver的表&#xff09;也可在初始化列表中实例化 singleton_example( name receiver, name code, datastream<c…

EOS智能合约开发(十五)EOS 状态机架构解析

好久没有写文章了&#xff0c;最近公司事情比较忙。非常感谢上次杭州团队一位负责EOS状态机的同事精彩分享。今天查阅很多资料后&#xff0c;整理这篇文章&#xff0c;希望对大家有所帮助。 EOS状态机是什么&#xff1f; 简单讲就是数据库&#xff0c;是EOS记录智能合约执行结…

接口测试时,输入所有参数的参数值后,接口返回“参数错误:所有参数都不能为空”

问题原因&#xff1a; 参数名称输入有误&#xff0c;前台&#xff08;pwd&#xff09;和后台&#xff08;password&#xff09;的参数名称不一致 &#xff0c;保持一致后即可

matlab 错误使用 connector.internal.autostart.run输入参数的数目不足。

matlab打开后提示&#xff1a; 错误使用 connector.internal.autostart.run输入参数的数目不足 原因&#xff1a;自己写的一个函数和matlab自带的函数重名了 参考&#xff1a;警告: 在 matlabrc 中初始化 Java 首选项失败 – MATLAB中文论坛MATLAB中文论坛MATLAB 基础讨论板块…