原文连接:醒者呆的博客园,www.cnblogs.com/Evsward/p/e…html
EOS,智能合约,abi,wasm,cleos,eosiocpp,开发调试,钱包,帐户,签名权限node
本文旨在针对EOS智能合约进行一个完整的实操演练,过程当中深刻熟悉掌握整个EOS智能合约的流程,过程当中出现的问题也会及时研究并入咱们本身的知识体系。本文会主要跟随EOS官方Wiki的智能合约部分进行研究学习,主要分为ios
EOS的智能合约采用C++ 编写,由于C++ 的高效性,没有C++ 编程基础的同窗能够先学习《Efficient&Elegant:Java程序员入门Cpp》。EOS中用户开发的应用程序或代码都是经过WebAssembly(WASM)来与主链进行交互的,它的编译工具是clang.llvm。关于EOS相关的基础准备请先过目《区块链3.0:拥抱EOS》,这里面介绍了包括EOS概念,安装部署以及工具等基础内容,其中包括了上面提到的《开启一个私有链》。这里还有一些准备知识须要过目:c++
这些都了解了之后,咱们继续智能合约的准备。git
首先,先肯定区块链中钱包的概念:程序员
钱包是一个私钥库,用来受权发生在区块链上的动做(action,记住这个概念)。这些私钥使用密码生成,被加密存储在磁盘上。这个密码应该被储存在一个安全的密码管理器中。
复制代码
提取一下重点:github
操做流程:算法
liuwenbin@liuwenbin-H81M-DS2:~$ cleos wallet create
Creating wallet: default
Save password to use in the future to unlock this wallet.
Without password imported keys will not be retrievable.
"PW5KeMG82A6zEmmHK4sBj3HE8pxBYBFw4CXVoQGt24Zy7AoRgMWxn"
复制代码
default改成自定义钱包名字wbs:数据库
liuwenbin@liuwenbin-H81M-DS2:~$ cleos wallet create -n wbs
Creating wallet: wbs
Save password to use in the future to unlock this wallet.
Without password imported keys will not be retrievable.
"PW5KPAwucXR66NqqzW5R5wdkHCMNGyHLrCWVPaE1nhj7hfacP7ZaL"
复制代码
wbs钱包解密:编程
liuwenbin@liuwenbin-H81M-DS2:~$ cleos wallet unlock -n wbs
password: Unlocked: wbs
复制代码
加密即改成:
liuwenbin@liuwenbin-H81M-DS2:~$ cleos wallet lock -n wbs
Locked: wbs
复制代码
若是不加-n参数,即操做默认钱包。
导入初始帐户eosio的主秘钥到钱包
全部新的blockchains,都是经过主秘钥启动,惟一初始帐户:eosio。要与区块链交互,须要将这个初始帐户的私钥导入到你的钱包。
复制代码
这个主秘钥咱们在上一篇文章也分析到了,是在~/.local/share/eosio/nodeos/config文件夹下的config.ini文件中自动配置的(可修改,默认安装EOS会生成一个)。
# 值为[公钥,私钥WIF编码的]
private-key = ["EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV","5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"]
复制代码
Private key should be encoded in base58 WIF。因此上篇文章中常常出现的WIF的意思是一种base58编码方式。
复制代码
$ cleos wallet import 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
imported private key for: EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
复制代码
如今咱们拥有了一个钱包default,该钱包内部包含一个默认主密钥的帐户eosio,默认的智能合约eosio.bios已经可使用,这个合约是EOS不少基本action的基础系统,因此要保证这个合约的有效执行。这个合约可让你可以直接控制资源分配,而且有权限访问API。在公链上,这个合约将管理已募集和待募集token,以储备带宽给CPU、内存以及网络活动使用。咱们提取一下重点:
这个默认合约eosio.bios能够在EOS源码位置contracts/eosio.bios找到。能够经过cleos来指定这个合约执行:
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos set contract eosio build/contracts/eosio.bios -p eosio
Reading WAST/WASM from build/contracts/eosio.bios/eosio.bios.wast...
Assembling WASM...
Publishing contract...
executed transaction: 36736dabac246732ef389fb5dd47099887854e25178a320b0e288324b5c87a9c 3288 bytes 2200576 cycles
# eosio <= eosio::setcode {"account":"eosio","vmtype":0,"vmversion":0,"code":"0061736d0100000001581060037f7e7f0060057f7e7e7e7e...
# eosio <= eosio::setabi {"account":"eosio","abi":{"types":[],"structs":[{"name":"set_account_limits","base":"","fields":[{"n...
复制代码
命令行工具仍旧是使用cleos,经过set contract来执行指定合约,后面跟着帐户名称(这里是默认的eosio,咱们刚刚导入的),而后是指定合约的路径。
命令最后的“-p eosio”的含义是:使用帐户eosio(使用的是帐户的私钥)来为这个action签名。
读取 WAST/WASM文件(这个文件是被新部署到build目录下的)
装配 WASM
发布合约
执行交易(合约也是一个交易),这里经过两个动做来生成一个交易,
从技术角度来将,abi是可选的,全部的EOSIO工具取决于它的易用性。
eosio <= eosio::setcode {...}
这一行的阅读方式为:action setcode是eosio命名空间下的,同时它是经过eosio帐户受权来执行的,带的参数有...
注意,action是能够被多个合约执行的。
复制代码
此时私链日志显示:
eosio generated block 11f6a66b... #6149 @ 2018-04-23T10:20:21.500 with 0 trxs, lib: 6148
1221781ms thread-0 abi_serializer.hpp:349 extract ] vo: {...}
复制代码
私链日志打出来合约部署的相关信息。其中vo的值不少,它的结构是:
{
"signatures": [
"EOSJzRXuKWJT77BGmBv26SoHGcKkR1XyaCBzkd8Yck5wE9fFptcgLReQZ8wZsxjizAbMxELmVnFPYkv5rT5VDxYk3UoiRPDTC"(这一看就是公钥格式)
],
"compression": "zlib",
"packed_context_free_data": "",
"packed_trx": "不少内容"
}
复制代码
这里面的结构包含一个签名串,一个加密信息,打包交易信息是核心数据,它包含了很长的交易相关的打包后的格式的内容。
上面提到了如何在钱包中导入默认帐户eosio,下面来看一下如何建立帐户。
注意:key和帐户是分开的,咱们接下来建立两个帐户,他们可使用同一套秘钥。
复制代码
建立秘钥
首先,建立秘钥对:
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos create key
Private key: 5KFGqe3Bv2maZ5jGMrufdkLhGV91ZYSysBWfxUUaR9VzbDk2UdA
Public key: EOS6nbWS7ZReiPMdMABoEmVBYanyTMb3GYRQGsTRMCYx9vijWoaS3
复制代码
已生成一对公钥和私钥。下面将私钥导入咱们本身的钱包wbs
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos wallet import -n wbs 5KFGqe3Bv2maZ5jGMrufdkLhGV91ZYSysBWfxUUaR9VzbDk2UdA
imported private key for: EOS6nbWS7ZReiPMdMABoEmVBYanyTMb3GYRQGsTRMCYx9vijWoaS3
复制代码
注意格式:
Usage: cleos wallet import [OPTIONS] key
复制代码
建立user和tester两个帐户
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos create account eosio user EOS6nbWS7ZReiPMdMABoEmVBYanyTMb3GYRQGsTRMCYx9vijWoaS3 EOS6nbWS7ZReiPMdMABoEmVBYanyTMb3GYRQGsTRMCYx9vijWoaS3
executed transaction: 927762a883e1c92a695a51c312eb5339bf911c1dbd56bab53e74d5fe20365106 352 bytes 102400 cycles
# eosio <= eosio::newaccount {"creator":"eosio","name":"user","owner":{"threshold":1,"keys":[{"key":"EOS6nbWS7ZReiPMdMABoEmVBYany...
复制代码
注意,建立帐户命令的格式是:cleos create account [OPTIONS] creator name OwnerKey ActiveKey
复制代码
观察执行结果,这时的action是newaccount,后面是action的参数,以json格式存在。下面同理生成tester帐户(只需更改上面的user为tester便可,秘钥采用同一个)。
查询帐户
咱们能够经过一个公钥来查询有它“管辖”的帐户列表,这是经过eosio::account_history_api_plugin插件完成的操做:
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos get accounts EOS6nbWS7ZReiPMdMABoEmVBYanyTMb3GYRQGsTRMCYx9vijWoaS3
{
"account_names": [
"tester",
"user"
]
}
复制代码
命令行是经过get accounts子命令来执行,后面要加入查询条件 -> 公钥。
经过以上的操做,咱们熟悉了cleos命令的一些经常使用操做,包括建立钱包、加解锁,执行基础系统级合约,以及很是经常使用的建立秘钥、经过秘钥建立帐户,经过秘钥反查帐户。过程当中,咱们涉及到不少action的学习研究。
咱们已经准备好了钱包、帐户、基本合约系统(用于支持基本action)。下面咱们能够正式展开对智能合约的学习,本章主要经过源码中已经存在的较简单的token和exchange两个合约来学习。
为了不混淆,咱们根据上面学习过的内容,从新建立一个帐户eosio.token专门用来执行token智能合约。
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos create account eosio eosio.token EOS6nbWS7ZReiPMdMABoEmVBYanyTMb3GYRQGsTRMCYx9vijWoaS3 EOS6nbWS7ZReiPMdMABoEmVBYanyTMb3GYRQGsTRMCYx9vijWoaS3
executed transaction: 745de4ad0d7e2f415a1fb962e7f072d4c036e831bdf01f06931d91bc2fcc3a91 352 bytes 102400 cycles
# eosio <= eosio::newaccount {"creator":"eosio","name":"eosio.token","owner":{"threshold":1,"keys":[{"key":"EOS6nbWS7ZReiPMdMABoE...
复制代码
接下来,咱们使用这个帐户部署eosio.token智能合约,一样经过上面学习到的方式:指定路径,指定加密帐户{-p eosio.token}:
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos set contract eosio.token build/contracts/eosio.token -p eosio.token
Reading WAST/WASM from build/contracts/eosio.token/eosio.token.wast...
Assembling WASM...
Publishing contract...
executed transaction: a95dc5be83d202730f798d2ce75df42eff518a3ed8f82e4c5b0f3c8881d75d70 8024 bytes 2200576 cycles
# eosio <= eosio::setcode {"account":"eosio.token","vmtype":0,"vmversion":0,"code":"0061736d01000000018a011660067f7e7f7f7f7f00...
# eosio <= eosio::setabi {"account":"eosio.token","abi":{"types":[],"structs":[{"name":"transfer","base":"","fields":[{"name"...
复制代码
建立EOS的代币
就像以太坊token那样,咱们在EOS上能够更加方便的建立一个基于EOS的代币。首先,去token合约中的头文件eosio.token.hpp,查看一下token相关的接口都有哪些,其中有一个create函数,咱们正是将要使用这个函数来建立token,因此咱们能够留意一下它的参数都包括哪些。
void create(account_name issuer, // 发行人,有权限调用下面的freeze、recall以及whitelist函数。
asset maximum_supply, // 最大发行量,注意单位,这个单位就是该token的名字,symbol。
uint8_t issuer_can_freeze,
uint8_t issuer_can_recall,
uint8_t issuer_can_whitelist );
复制代码
咱们能够经过命令行来调用该create函数:
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos push action eosio.token create '[ "eosio", "1000000000.0000 EOS", 0, 0, 0]' -p eosio.token
executed transaction: 5b3f974030f7311a0c65cc6d4123be18f7435d0b06b615d939ff81255059aaf8 248 bytes 104448 cycles
# eosio.token <= eosio.token::create {"issuer":"eosio","maximum_supply":"1000000000.0000 EOS","can_freeze":0,"can_recall":0,"can_whitelis...
复制代码
直接按参数顺序传入值比较方便,若是你须要更加准确的传值,能够将以上动做的参数内容改写为“Key,Value”的形式改造一下,会比较冗余。
思考,这个token没有名字么?就像EOS之于ethereum那样。
复制代码
代币发放
咱们已经发行了一种代币EOS,下面咱们能够将这个代币EOS发放给帐户user(咱们上面建立的)。继续查看那个eosio.token.hpp头文件中关于issue(发放)操做的参数。
void issue( account_name to, asset quantity, string memo );// memo:备注,通常能够不填写。
复制代码
而后,咱们继续使用命令行工具cleos来push action到智能合约eosio.token中这个issue函数中:
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos push action eosio.token issue '["user","100.0000 EOS", "memo"]' -p eosio
executed transaction: b93b9e709ce4720dd5e89789c14a785790605ec093194710736cf30bb195fae9 256 bytes 124928 cycles
# eosio.token <= eosio.token::issue {"to":"user","quantity":"100.0000 EOS","memo":"memo"}
>> issue
# eosio.token <= eosio.token::transfer {"from":"eosio","to":"user","quantity":"100.0000 EOS","memo":"memo"}
>> transfer
# eosio <= eosio.token::transfer {"from":"eosio","to":"user","quantity":"100.0000 EOS","memo":"memo"}
# user <= eosio.token::transfer {"from":"eosio","to":"user","quantity":"100.0000 EOS","memo":"memo"}
复制代码
注意,在命令行结尾处,咱们仍要使用帐户来签名受权,这里是用户eosio(由于上面eosio是发行者,全都发行到它的兜里啦,因此要从它兜里取钱)。
咱们先来看issue函数的实现源码:
void token::issue( account_name to, asset quantity, string memo )
{
print( "issue" );// >> issue
auto sym = quantity.symbol.name();// EOS
stats statstable( _self, sym );
const auto& st = statstable.get( sym );// 经过代币id获取代币对象,包含代币相关信息
require_auth( st.issuer );// 检查发行人权限,是否有足够
eosio_assert( quantity.is_valid(), "invalid quantity" );// 转移金额是否有效
eosio_assert( quantity.amount > 0, "must issue positive quantity" );// 转移金额必须是正数
statstable.modify( st, 0, [&]( auto& s ) {
s.supply.amount += quantity.amount;
});
add_balance( st.issuer, quantity, st, st.issuer );// TODO: 转帐金额竟然要加到发行者的余额中?等于发行者转帐完毕仍旧是原发行量?
if( to != st.issuer )// 别发给本身
{
//这是action的内部函数,执行转帐的意思
dispatch_inline( permission_level{st.issuer,N(active)}, _self, N(transfer), &token::transfer, { st.issuer, to, quantity, memo } );
}
}
复制代码
TODO: add_balance函数源码要好好研究
复制代码
执行发放动做,经过日志能够看到包括了几个步骤:
实际上,eosio.token能够直接修改帐户余额而不使用“内联调用transfer”。可是这种状况下,eosio.token智能合约会要求咱们的token必须有全部的帐户余额,经过计算引用过他们的全部交易动做的总和。它还须要发送者和接收者的存款状况,以支持他们能够自动处理充值和提现。
若是你想看到广播出去的真实交易的状况,可使用-d -j选项来表达“不要广播”以及“以json格式返回交易”:
“不要广播”的意思是这条动做无效,只是用来作测试的。(这与上面的广播出去的“真实交易”不一样)
复制代码
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos wallet unlock
password: Unlocked: default
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos push action eosio.token issue '["user","100.0000 EOS", "memo"]' -p eosio -d -j
{
"expiration": "2018-04-24T02:13:26",
"region": 0,
"ref_block_num": 26069,
"ref_block_prefix": 2157111066,
"max_net_usage_words": 0,
"max_kcpu_usage": 0,
"delay_sec": 0,
"context_free_actions": [],
"actions": [{
"account": "eosio.token",
"name": "issue",
"authorization": [{
"actor": "eosio",
"permission": "active"
}
],
"data": "00000000007015d640420f000000000004454f5300000000046d656d6f"
}
],
"signatures": [
"EOSKjnxnDwjjyZaZPSeQnmMFYjpgBWRzby5YFqVqZEn6uA3TUhUo4yWmfQhXdxNykgsSVvAwGnkGUyUK7jcJt5qNg8xhstRqy"
],
"context_free_data": []
复制代码
因为次日来上班,咱们的钱包已经被锁定,因此要先解锁,而后再调用上面的命令。能够看到输出的内容不少,是按照咱们的要求“以json格式返回交易”,“不要广播”。咱们能够看到交易的具体信息,包含了不少属性:有效期、地区、引用区块号、引用区块前缀、最大网络使用词数、最大cpu使用,延迟秒数、上下文的自由动做(为空说明上下文中没有动做),动做内容(包括动做执行人,动做名称,受权信息包括行动者以及权限状态、数据串、签名、上下文自由数据(为空,上下文无内容)。
代币交易
如今user帐户已经存在100个EOS代币了,咱们使用上面创建的另外一个帐户tester,用来测试代币交易:从user帐户中转出一部分EOS到tester帐户。
一样的,咱们仍是先来看一下源码中设计的transfer函数的参数列表:
void transfer( account_name from,
account_name to,
asset quantity,
string memo );
复制代码
很简单,下面使用cleos来调用:
这里咱们能够尝试使用user帐户自己来签名动做。
复制代码
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos push action eosio.token transfer '["user","tester","25.0000 EOS", "m"]' -p user
executed transaction: a31e56b49837e53fb1e7d55aa7aba934dc751938241488183e54ad52dc7804fe 256 bytes 110592 cycles
# eosio.token <= eosio.token::transfer {"from":"user","to":"tester","quantity":"25.0000 EOS","memo":"m"}
>> transfer
# user <= eosio.token::transfer {"from":"user","to":"tester","quantity":"25.0000 EOS","memo":"m"}
# tester <= eosio.token::transfer {"from":"user","to":"tester","quantity":"25.0000 EOS","memo":"m"}
复制代码
成功!仍旧是eosio.token合约发起一个transfer交易动做,输出“>> transfer”,而后内联调用帐户余额的操做分别操做发送者和接收者。
注意:咱们也能够不使用push action的方式来交易,而是直接使用cleos的自命令transfer便可,后面的命令参数与上面的差很少。可是发行和发放token仍旧须要使用合约的push action来操做。我理解的是因为交易比较经常使用且可不依赖某一个合约,因此被封装在了根命令中,而其余与合约相关的仍旧须要使用push action的方式。
复制代码
查看余额
咱们须要总体研究一下cleos的全部子命令,列举的方式比较枯燥,这里不展开,只是使用到哪里就展现哪里。咱们上面进行了代币发放和代币交易,此时两个测试帐户user和tester的EOS余额都发生了变化。下面咱们要利用cleos查询一下这两个帐户的代币EOS的余额情况:
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos get currency balance eosio.token user EOS
75.0000 EOS
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos get currency balance eosio.token tester EOS
25.0000 EOS
复制代码
能够看到user帐户的代币余额为75,而tester帐户的代币余额是25。这与咱们上面的交易动做是可匹配的。
重要发现
上面咱们留的TODO,由于我搞不懂为何要先给代币发行人余额增长转帐额,再转帐,这个操做是怎么来的。我在上面查询余额的命令发现:
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos get currency balance eosio.token eosio EOS 0.0000 EOS
发行人的余额是0!
复制代码
因此这样咱们就搞明白了为何每次发放代币的时候要先add_balance而后再sub_balance了。
咱们建立一个帐户user1,而后用该帐户部署合约exchange:
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos create account eosio user1 EOS6nbWS7ZReiPMdMABoEmVBYanyTMb3GYRQGsTRMCYx9vijWoaS3 EOS6nbWS7ZReiPMdMABoEmVBYanyTMb3GYRQGsTRMCYx9vijWoaS3
executed transaction: b2c4a1acb831a4bd12d1686c271ca49b0ed85efe5a31f5c24abe212bc44d4009 352 bytes 102400 cycles
# eosio <= eosio::newaccount {"creator":"eosio","name":"user1","owner":{"threshold":1,"keys":[{"key":"EOS6nbWS7ZReiPMdMABoEmVBYan...
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos set contract user1 build/contracts/exchange -p user1
Reading WAST/WASM from build/contracts/exchange/exchange.wast...
Assembling WASM...
Publishing contract...
executed transaction: 4e2bc4496ef25f187bb90da0f0b3398d5c1970ba62b062ab34160d09975c0591 34056 bytes 2200576 cycles
# eosio <= eosio::setcode {"account":"user1","vmtype":0,"vmversion":0,"code":"0061736d0100000001cd023160067f7e7f7f7f7f0060037f...
# eosio <= eosio::setabi {"account":"user1","abi":{"types":[{"new_type_name":"account_name","type":"name"}],"structs":[{"name...
复制代码
exchange合约能作的事情有不少,包括建立和交易currency(电子货币)。它能作的事能够参考源码位置contract/exchange/*。
msig的意思是multi-signature,多重签名的意思。这个合约是能够支持多方对同一笔交易进行异步签名,它是一个对用户友好的支持多方赞成的异步进行提案、批复以及最终发布交易的合约。
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos create account eosio user2 EOS6nbWS7ZReiPMdMABoEmVBYanyTMb3GYRQGsTRMCYx9vijWoaS3 EOS6nbWS7ZReiPMdMABoEmVBYanyTMb3GYRQGsTRMCYx9vijWoaS3
executed transaction: b156f5aa2fec6ca8ed6e55ba2be2e403cef0dd26f3c022a51e74f9ccf348fef2 352 bytes 102400 cycles
# eosio <= eosio::newaccount {"creator":"eosio","name":"user2","owner":{"threshold":1,"keys":[{"key":"EOS6nbWS7ZReiPMdMABoEmVBYan...
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos set contract user2 build/contracts/eosio.msig -p user2
Reading WAST/WASM from build/contracts/eosio.msig/eosio.msig.wast...
Assembling WASM...
Publishing contract...
executed transaction: 66c64a3f55f011d40483c627ea43fd62f303fd96f9851141df67087867d4e02f 7320 bytes 2200576 cycles
# eosio <= eosio::setcode {"account":"user2","vmtype":0,"vmversion":0,"code":"0061736d01000000016b1260017f0060047f7e7e7f006004...
# eosio <= eosio::setabi {"account":"user2","abi":{"types":[{"new_type_name":"account_name","type":"name"},{"new_type_name":"...
复制代码
部署方式与前面没有大区别,这里使用的是帐户user2,它能作的事能够参考源码位置contract/eosio.msig/*。
咱们尽可能使用与合约名字相同的帐户名字来发布合约,这样能够有效记录该帐户的功能,可快速与其余普通用户作出区分。
复制代码
以上咱们提取了eos.io合约中的三个,进行了部署、学习与操做演练,下面咱们将尝试开发本身的基于eos的智能合约。
eosiocpp:智能合约的引导程序工具。
复制代码
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos/contracts$ eosiocpp -n testcontract
created testcontract from skeleton
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos/contracts$ ls
asserter CMakeLists.txt dice eosiolib eosio.system exchange identity libc++ musl proxy skeleton stltest test_api_db test_api_multi_index test.inline
bancor currency eosio.bios eosio.msig eosio.token hello infinite multi_index_test noop simple.token social test_api test_api_mem testcontract tic_tac_toe
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos/contracts$ cd testcontract/
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos/contracts/testcontract$ tree
.
├── testcontract.abi
├── testcontract.cpp
└── testcontract.hpp
0 directories, 3 files
复制代码
咱们在源码contracts目录下执行了以上命令之后,就会获得一个空的智能合约的开发框架,其中包含了abi,cpp以及hpp三个文件。
/**
* @file
* @copyright defined in eos/LICENSE.txt
*/
#include <testcontract.hpp>
/**
* The init() and apply() methods must have C calling convention so that the blockchain can lookup and
* call these methods.
*/
extern "C" {
/// The apply method implements the dispatch of events to this contract
void apply( uint64_t receiver, uint64_t code, uint64_t action ) {
eosio::print( "Hello World: ", eosio::name(code), "->", eosio::name(action), "\n" );
}
} // extern "C"
复制代码
wast文件生成方式:
eosiocpp -o ${contract}.wast ${contract}.cpp
复制代码
abi文件,Application Binary Interface,应用程序的二进制接口,这在以太坊是相同的概念,请参照《【精解】开发一个智能合约》。
abi是一个json格式的,用来描述智能合约如何在action和二进制程序中进行转变的方法,也用来描述数据库状态。有了abi来描述你的智能合约,开发者和用户均可以经过JSON无缝地与合约进行交互。
复制代码
abi文件生成时源文件语法包括:
abi文件生成方式:
eosiocpp -g ${contract}.abi ${contract}.hpp
复制代码
abi文件生成之后,咱们能够找一个打开看一下,里面包含的内容不少,有各类属性,数据,方法功能的描述。
部署学习和操做咱们都已经学会,那么如今要开发一个helloworld智能合约,首先在eos源码中找到一个位置(由于要include相关库),创建一个目录hello,在里面建立一个 1,hello.cpp,
//
// Created by liuwenbin on 18-4-24.
//
#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>
using namespace eosio;
class hello : public eosio::contract {
public:
using contract::contract;
/// @abi action
void hi(account_name user) {
print("Hello, ", name{user});
}
};
EOSIO_ABI(hello, (hi)) // CLion代码检查,这里会报错,先不理会
复制代码
2,编译wast
在hello.cpp路径下执行:
eosiocpp -o hello.wast hello.cpp
复制代码
会有不少警告出来,不要理会,查看一下,当前目录应该已经有了hello.wast。
3,编译abi 而后继续在hello.cpp路径下执行:
eosiocpp -g hello.abi hello.cpp
Generated hello.abi ...
复制代码
查看当前目录,又生成了一个hello.abi文件。
咱们的合约开发就完成了。下面的操做与上一章节的操做是相似的。 咱们先建立一个帐户hello.a,而后用这个帐户部署合约hello。部署完成之后,咱们能够进行合约调用:
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos push action hello.a hi '["Edward"]' -p hello.a
executed transaction: 8f4b9a4fe271a7981ee40348179dcdede025ebece10c38d0a4d5a0aa5d41ffac 232 bytes 102400 cycles
# hello.a <= hello.a::hi {"user":"Edward"}
>> Hello, Edward
复制代码
经过帐户hello.a 调用hi函数,传入参数'[用户名]',使用hello.a签名该action。执行之后,会在日志中打印出“>> ...”。
以上操做都是测试用帐户,他们都是基于相同的公钥建立的,咱们如今来查看下目前该公钥有多少个帐户:
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos get accounts EOS6nbWS7ZReiPMdMABoEmVBYanyTMb3GYRQGsTRMCYx9vijWoaS3
{
"account_names": [
"eosio.token",
"hello.a",
"tester",
"tokener",
"user",
"user1",
"user2"
]
}
复制代码
目前咱们的hello合约是不限制hi参数的,也就是说其实咱们是没有“Edward”这个签名人的,也就是说这个参数中不管是否传入帐户名,均可以输出。
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos push action hello.a hi '["Edward"]' -p user
executed transaction: fc38858f89e7dfdd9dcff8db8626545f387960cee63f80e8352b7f7596a986a7 232 bytes 102400 cycles
# hello.a <= hello.a::hi {"user":"Edward"}
>> Hello, liuwenbin
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos push action hello.a hi '["Edward"]' -p user1
executed transaction: f81acea4f1ef9207afc5827608fd255bda063b0a0aeaa214f6bc2742ce480a34 232 bytes 102400 cycles
# hello.a <= hello.a::hi {"user":"Edward"}
>> Hello, liuwenbin
复制代码
另外,咱们可使用user,也可使用user1来签名hello.a部署的咱们的hello智能合约,这显然是不合理的。
咱们指望智能合约hi函数的参数必须是有效帐户名,同时只有该帐户拥有当前action的签名权。因此,咱们要修改hello.cpp文件。
/// @abi action
void hi(account_name user) {
require_auth(user);// 只有该user帐户有权签名当前action
print("Hello, ", name{user});
}
复制代码
而后重复以上编译和部署的操做。再传入非有效帐户名时,或者用其余帐户签名的时候就会报错:
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos push action hello.a hi '["Edward"]' -p hello.a
Error 3120001: Invalid name
Name should be less than 13 characters and only contains the following symbol .12345abcdefghijklmnopqrstuvwxyz
Error Details:
Name not properly normalized (name: Edward, normalized: .dward)
'["Edward"]' is invalid args for action 'hi' code 'hello.a'
复制代码
报错Edward不是有效参数。
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos push action hello.a hi '["user"]' -p hello.a
Error 3030001: missing required authority
Ensure that you have the related authority inside your transaction!;
If you are currently using 'cleos push action' command, try to add the relevant authority using -p option.
Error Details:
missing authority of user
复制代码
hello.a没法给帐户user签名。
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos push action hello.a hi '["tester"]' -p tester
executed transaction: be235f5fbd6173a4acac23b8faf4cd3de9d73721b839d3092b2db23eaa3ef51d 232 bytes 102400 cycles
# hello.a <= hello.a::hi {"user":"tester"}
>> Hello, tester
复制代码
成功!咱们传入有效用户名tester,而且用tester帐户自己去签名当前action,最终成功输出告终果。
思考,咱们可发现其余eos的智能合约都是符合以上指望的,这是什么规则?
复制代码
这是Ricardian Contract,意思是该合约符合李嘉图等价原则。
李嘉图等价的合约,会指定其合法绑定者来关联该合约的每个action。
复制代码
关于帐户和合约关系的一些心得:
liuwenbin@liuwenbin-H81M-DS2:~/work/CLionProjects/github.com/eos$ cleos get code hello.a
code hash: 7c6a300874835ad928de4f30712023758157bd50cb423ab039443f56a84167ff
复制代码
咱们编写一个智能合约,须要在本地私有链上进行调试,经过之后再上公有链。
官方声称这叫作Caveman debugging(瞬间不想再爱了,照以太坊差远了),什么意思呢?就是eosio::print能够输出log来调试,EOS目前没办法进行代码断点调试。
复制代码
本文介绍了EOS智能合约的内容,这部份内容的确比以太坊的要少不少,由于以太坊上面成熟的开发框架更多一些,功能也更强大(例如能够断点调试,本地虚机等),而EOS比较新,在这方面没有那么多工具可选。可是EOS的智能合约比起使用Solidity的以太坊合约来说,仍是很是方便的,不少想法也比较新颖。本文主要从准备、学习、实战和调试这几个步骤进行按部就班地了解与学习。这期间,咱们学习了bios、token、exchange、msig以及本身实现了helloworld合约,掌握了钱包、帐户、签名权限等不少基本功能,熟悉了cleos和eosiocpp命令的使用,掌握了智能合约的编写、编译、部署以及调试的知识。
EOS官方文档
圆方圆学院聚集大批区块链名师,打造精品的区块链技术课程。 在各大平台都长期有优质免费公开课,欢迎报名收看。
公开课地址:ke.qq.com/course/3451…