“EOS to the ground!”昨天老板看了我的文章,上来就劈头盖脸说,“什么to the moon,心情差,招你来是炒币的?!我们是一个最落地的DApp开发团队,打造牛逼的产品,然后跟社区分享技术心得! %@¥#@%¥!%@*……(此处打码数千字)。”老板还说,创业团队的人要脚踏实地,还要有老板的心态。其实我觉得我就很踏实啊,还特了解我们的老板,就是凡事都要梭哈,才能有最大收获(1美金扫货eos的主不懂18美金吃进的苦......),好了好了,我们要去火星落地,今天我们会学会eos的系统初始化、转发EOS代币、智能合约开发、EOS智能合约消息通知机制、数据持久化存储。。。内容有点多,请同学们系好安全带,发!车!咯!
上篇我们详细介绍了如何在虚拟机编译eos。编译安装完eos之后,我们就可以运行nodeos了:
$ nodeos-e -p eosio --plugin eosio::wallet_api_plugin --plugin eosio::chain_api_plugin--plugin eosio::history_api_plugin
可以看到nodeos加载wallet_api_plguin, chain_api_plugin, 和history_api_plugin这三个插件, 这个三个插件的功能简单罗列如下:
Wallet:提供操作钱包的命令,比如创建新钱包,导入私钥,解锁钱包等等。
Chain:获取链上信息,以及提交交易等
History:获取指定账号的交易历史记录
Nodeos启动后,系统默认有个初始账号,叫eosio,可以认为这是一个root账号。EOS系统的账号必须由系统内已有的账号创建,这是EOS有别于其他区块链的一个地方。
要创建账号,我们需要指定账号owner和active权限的公钥,我们可以使用系统提供的 cleos create key来创建一对公私钥:
$ cleos create key
Private key:5Jmsawgsp1tQ3GD6JyGCwy1dcvqKZgX6ugMVMdjirx85iv5VyPR
Public key: EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4
接下来我们创建钱包:
$ cleos wallet create
Creating wallet: default
Save password to use in the future tounlock this wallet.
Without password imported keys will not beretrievable.
"PW5K5gvNHJ48n6fVh2nG7Hdqmf115DzQy4D2ZtTEQmic6whZGbVDT"
创建钱包时,会提示用户保存钱包的密码,这个密码很重要,钱包过一段时间会自动上锁,需要使用这个密码解锁,才能使用里面的私钥对发起的交易消息进行签名。
钱包创建好之后,我们导入刚才创建的私钥:
$ cleos wallet import5Jmsawgsp1tQ3GD6JyGCwy1dcvqKZgX6ugMVMdjirx85iv5VyPR
imported private key for: EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4
这样,钱包里就保存了公钥对应的私钥了。接下来,我们初始化系统,用eosio账号加载esoio.bios合约;创建eosio.token账号,加载eosio.token合约。依次执行如下命令:
# 部署eosio.bios合约
$ cleos set contract eosio~/eos/build/contracts/eosio.bios -p eosio
# 创建eosio.token账号
$ cleos create account eosio eosio.token EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4 EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4
# 部署eosio.token合约
$ cleos set contract eosio.token~/eos/build/contracts/eosio.token -p eosio.token
接下来创建EOS系统中的EOS代币,我们创建10亿个EOS,代币的发行者是eosio账号。
$ cleos push action eosio.token create '["eosio", "1000000000.0000 EOS", 0, 0, 0]' -p eosio.token
EOS的一个合约必须与一个账号关联,而且,一个账号只能部署一个合约。此外,EOS系统的合约能够升级,假如修改了合约,可以通过cleos set contract命令重新部署。
完成了系统初始化,创建charity合约账号和使用合约的账号alice和bob备用。为了简化起见,我们新创建的账号owner权限和active权限都使用刚才产生的公钥。
# 创建charity账号
$ cleos create account eosio charityEOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4
# 创建alice账号
$ cleos create account eosio aliceEOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4
# 创建bob账号
$ cleos create account eosio bobEOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4
然后由eosio给alice和bob分别发行1万个EOS:
$ cleos push action eosio.token issue '["alice", "10000.0000 EOS", "" ]' -p eosio
$ cleos push action eosio.token issue '["bob", "10000.0000 EOS", "" ]' -p eosio
新建一个测试目录,在该目录下使用eosiocpp产生智能合约的初始文件:
$ eosiocpp -n charity
可以看见,命令新建了一个目录charity,在chairty目录自动生成了charity.abi、charity.hpp和charity.cpp这三个文件,其中abi文件是描述智能合约的接口。打开这个三个文件,发现自动生成的合约实现了一个接口hi,但是abi描述不对。
修改charity.abi文件:
{
"types": [{
"new_type_name": "account_name",
"type": "name"
}
],
"structs": [{
"name": "hi",
"base": "",
"fields": [
{"name":"user","type":"account_name"}
]
}
],
"actions": [{
"name": "hi",
"type": "hi",
"ricardian_contract": ""
}
],
"tables": [
],
"ricardian_clauses": []
}
然后在charity的目录中执行部署合约的命令:
$ cleos set contract charity . charity.wast charity.abi -p charity
合约部署成功之后,我们可以看见合约的hash值:
$ cleos get code charity
code hash:1d0070ffc528f1268ad1f6c5ba998245811318bdcfd8827432d56e24c69a476c
若没有部署合约,code hash是全为0.
现在调用charity的hi接口,我们看看合约会输出什么:
$ cleos push action charity hi '["alice"]'-p alice
executed transaction:4e0c500d27fdd4d2a093b5f0c60e5023c9f1d0addc185db0e1bac37a2f 643fcd 104 bytes 1242 us
# charity <= charity::hi {"user":"alice"}
>> Hello, alice
我们可以看到,合约简单的把hi接口传入的字符串输出,完成合约的调用。考察命令输出提示信息:
# charity <= charity::hi {"user":"alice"}
其中<= 指向左边的charity是receiver,就是接受调用的合约账号。<=右边的charity::hi指的是调用charity合约的hi接口。聪明如你一定会问了,难道<=左右两边receiver和contract还会不一样?你说对了,合约中有消息通知机制,可以把一条合约的调用发给另一个合约receiver。我们下一步给charity转账就会发现了:
$ cleos push action eosio.token transfer '{"from":"alice","to":"charity","quantity":"10.0000EOS","memo":""}' -p alice
executed transaction:ba422c8f8f2efb540a9e3a91e6d615235dcf90d0d6f750c41ac8b0b0c93bf1cc 128 bytes 989 us
# eosio.token <= eosio.token::transfer {"from":"alice","to":"charity","quantity":"10.0000EOS","memo":""}
>> transfer from alice to charity10.0000 EOS
# alice <= eosio.token::transfer {"from":"alice","to":"charity","quantity":"10.0000EOS","memo":""}
# charity <= eosio.token::transfer {"from":"alice","to":"charity","quantity":"10.0000EOS","memo":""}
从上面我们可以看到,调用eosio.token的账号的transfer接口转账,会有消息通知发送方和接收方。聪明的你可能又想到了,在charity合约中把接收到消息内容记录下来不就OK了?
说干就干,模仿eosio.token合约的transfer接口,给charity也实现一个,现在仅仅是简单的输出信息:
编译、部署、再给charity账号转账,可是没有我们期望的输出。哪里出了问题?查看EOS源代码下contracts/eosiolib/dispather.hpp中的EOSIO_ABI的宏定义:
展开是个C语言的apply调用,其中receiver和code就是合约名称转成的Uint64的数值,加上action,刚看看到的<= 输出其实是:
receiver <= code::action
考察代码,发现只有receiver等于code才能继续调用合约接口。哈哈,问题找到了,稍作修改,重新定义一下宏,命名为EOSIO_ABI_EX:
其中增加判断条件若是由eosio.token合约转发过来的消息,也可以继续处理。
修改好之后,编译、部署,然后再给charity账号转账,查看结果:
果然,在最后一句输出transfer的跟踪消息。
调用问题解决了之后,我们要把和charity交易的消息记录在EOS的table之中。首先在头文件定义table记录的数据结构:
这个数据结构包括transfer接口传进的参数,还加了一个自增id作为主键。还需在charity.abi文件中对这个table进行描述:
增加一个record结构描述,此结构作为table的type。
然后再增加table的描述,这样,我们的table在abi文件中就定义完整了。
接下来,我们在charity::transfer中添加代码保存每次转账时候的记录:
编译、部署,我们再通过eosio.token合约给charity转账:
重复执行一次,然后使用cleos get table查查看record表中的数据:
哈哈,查出来,两笔alice转账的记录!至此,我们初步实现了一个合约接收转账消息,并把它记录在表格中的功能。
代码已经上传至github,https://github.com/ofo/charity,any commentsare welcome!
转自:https://www.jianshu.com/p/d8d03562b3d5