EOS的智能合约里面有一个action(动作)和transaction(交易)的概念。
对于我们开发以太坊开发者来说,基本上只有transaction的概念。如果我只要执行一种操作,而且是只读操作,就不需要签名。如果需要划资金,有一些写的操作,那就需要用户用私钥对这个操作去签名,然后pos的一个transaction,这是以太坊的概念。
对于EOS,它多了一个action的概念,action其实它也是对一个智能合约中的某个函数的调用。transaction是由一个或者多个action组合而成的关系,就是在一个transaction里,可以包含多个action,这样你可以在一个transaction里签一次名,就可以调多个函数,做一组操作。
部署智能合约
载入基础IO智能合约
现在我们拥有了一个钱包default,该钱包内部包含一个默认主密钥的账户eosio,默认的智能合约eosio.bios已经可以使用,这个合约是EOS很多基本action的基础系统,所以要保证这个合约的有效执行。这个合约可以让你能够直接控制资源分配,并且有权限访问API。在公链上,这个合约将管理已募集和待募集token,以储备带宽给CPU、内存以及网络活动使用。我们提取一下重点:
这个默认合约eosio.bios可以在EOS源码位置contracts/eosio.bios找到。可以通过cleos来指定这个合约执行:
部署智能合约的示例代码如下:
$ cleos set contract eosio build/contracts/eosio.bios -p eosio
其中,eosio是要部署的账号,就是你用哪个账号去部署智能合约;
build/contracts/eosio.bios表示的是路径;
eos.bios是生成一个智能合约的目录。
运行结果:
cleos set contract eosio build/contracts/eosio.bios -p eosio
Reading WAST/WASM from build/contracts/eosio.bios/eosio.bios.wasm...
Using already assembled WASM...
Publishing contract...
Error 3080006: transaction took too long
----------------------
出现上面问题,如何解决
经过分析,nodeos程序启动时添加max-transaction-time即可解决这个问题
nodeos -e -p eosio --max-transaction-time=1000
#加入--max-transaction-time=1000 //可以解决问题
再次运行
cuijb@cuijb-VirtualBox:~/eos$ cleos set contract eosio build/contracts/eosio.bios -p eosio
Reading WAST/WASM from build/contracts/eosio.bios/eosio.bios.wasm...
Using already assembled WASM...
Publishing contract...
executed transaction: 00979d5d016d61308905e5fc74365eef81a26b4d715791de1de0550d3468fecf 3728 bytes 3130 us
# eosio <= eosio::setcode {"account":"eosio","vmtype":0,"vmversion":0,"code":"0061736d01000000016 21260037f7e7f0060057f7e7e7e7e...
# eosio <= eosio::setabi {"account":"eosio","abi":"0e656f73696f3a3a6162692f312e30050c6163636f756 e745f6e616d65046e616d650f7065...
对一些命令做一些阐释:
- 命令行工具仍旧是使用cleos,通过set contract来执行指定合约,后面跟着账户名称(这里是默认的eosio,我们刚刚导入(他的私钥,私钥在config.ini
中。)),然后是指定合约的路径。- 命令最后的“-p eosio”的含义是:使用账户eosio(使用的是账户的私钥)来为这个action签名。
- 读取 WAST/WASM文件(这个文件是被新部署到build目录下的)
- 装配 WASM
- 发布合约
- 执行交易(合约也是一个交易),这里通过两个动作来生成一个交易,
- setcode,code描述了合约是如何运行的。
- setabi,abi描述了如何在二进制文件和json文件(代表了各种参数)中转换。 从技术角度来讲,abi是可选的,所有的EOSIO工具取决于它的易用性。
eosio <= eosio::setcode {...}
上面一行的阅读方式为:action setcode是eosio命名空间下的,同时它是通过eosio账户授权来执行的,带的参数有…
注意,action 是可以被多个合约执行的。
我们通过eosio超级账户,部署系统合约eosio.system
命令如下:
cuijb@cuijb-VirtualBox:~/eos$ cleos set contract eosio build/contracts/eosio.system -p eosio
Reading WAST/WASM from build/contracts/eosio.system/eosio.system.wasm...
Using already assembled WASM...
Publishing contract...
executed transaction: cb59ffd674df7c4c6ef98fff0fe052700a95521c7d36a92b38684dc17a478fc3 36768 bytes 13096 us
# eosio <= eosio::setcode {"account":"eosio","vmtype":0,"vmversion":0,"code":"0061736d0100000001b0022e60037f7e7e0060017f006002...
# eosio <= eosio::setabi {"account":"eosio","abi":"0e656f73696f3a3a6162692f312e30050c6163636f756e745f6e616d65046e616d650f7065...
为了管理Token,我们可以创建eosio.token账号来专门用来执行token智能合约。
cleos create account eosio eosio.token EOS641B9XFbXQjobCdQKLPDw5sZzvv5ZieMbyabiHVu88SFgo8tBf EOS5yJk7uY34QPoBuQx8WAZfFmybEACVnEkfg8fnRbWd1d5Q86MMV
executed transaction: e4a3673f5e89c844c61e903e4dd67e635e86a9aa13463b204de0b6fdedbee9fb 200 bytes 197 us
# eosio <= eosio::newaccount {"creator":"eosio","name":"eosio.token","owner":{"threshold":1,"keys":[{"key":"EOS641B9XFbXQjobCdQKL...
查询我创建的账户
cuijb@cuijb-VirtualBox:~/eos$ cleos get accounts EOS641B9XFbXQjobCdQKLPDw5sZzvv5ZieMbyabiHVu88SFgo8tBf
{
"account_names": [
"eosio.token",
"jambeau",
"jambeau1",
"jambeau2"
]
}
我刚才创建的eosio.token创建成功。
接下来,我们使用这个账户部署eosio.token智能合约,同样通过上面学习到的方式:指定路径,指定加密账户{-p eosio.token}:
cuijb@cuijb-VirtualBox:~/eos$ cleos set contract eosio.token build/contracts/eosio.token -p eosio.token
Reading WAST/WASM from build/contracts/eosio.token/eosio.token.wasm...
Using already assembled WASM...
Publishing contract...
executed transaction: d4a169320ea8200d0ab438a111b7baac4caebcbb2d9860d648523b36d5de37e4 8104 bytes 1001 us
# eosio <= eosio::setcode {"account":"eosio.token","vmtype":0,"vmversion":0,"code":"0061736d01000000017e1560037f7e7f0060057f7e...
# eosio <= eosio::setabi {"account":"eosio.token","abi":"0e656f73696f3a3a6162692f312e30010c6163636f756e745f6e616d65046e616d65...
我们对部署合约做一些阐释:
cleos set contract 部署合约账号 部署合约路径 -p 部署合约账号签名:例如:
cleos set contract eosio.token build/contracts/eosio.token -p eosio.token
创建EOS的代币
就像以太坊token那样,我们在EOS上可以更加方便的创建一个基于EOS的代币。首先,去token合约中的头文件eosio.token.hpp,查看一下token相关的接口都有哪些,其中有一个create函数,我们正是将要使用这个函数来创建token,所以我们可以留意一下它的参数都包括哪些。
我们可以通过命令行来调用该create函数:
cuijb@cuijb-VirtualBox:~/eos$ cleos push action eosio.token create '["eosio","100000000000000.0000 SYS"]' -p eosio.token
executed transaction: e5e3d25b6de3aac9863051eaa486be06fe835bb5ff211a91365e7e1c1644b405 120 bytes 240 us
# eosio.token <= eosio.token::create {"issuer":"eosio","maximum_supply":"100000000000000.0000 SYS"}
直接按参数顺序传入值比较方便,如果你需要更加准确的传值,可以将以上动作的参数内容改写为“Key,Value”的形式改造一下,会比较冗余。
代币发放
我们已经发行了一种代币EOS,下面我们可以将这个代币EOS发放给账户eosio
引用块内容
(我们上面创建的)。继续查看那个eosio.token.hpp头文件中关于issue(发放)操作的参数。
void issue( account_name to, asset quantity, string memo );// memo:备注,一般可以不填写。
我们分析一下 issue这个Action一些内容
void token::issue( account_name to, asset quantity, string memo )
{
auto sym = quantity.symbol; //获取代币对象
eosio_assert( sym.is_valid(), "invalid symbol name" );
eosio_assert( memo.size() <= 256, "memo has more than 256 bytes" );
auto sym_name = sym.name();//获取代币名称(例如EOS,...)
stats statstable( _self, sym_name );
auto existing = statstable.find( sym_name );
eosio_assert( existing != statstable.end(), "token with symbol does not exist, create token before issue" );
const auto& st = *existing;
require_auth( st.issuer );// 检查发行人权限,是否有足够
eosio_assert( quantity.is_valid(), "invalid quantity" );
eosio_assert( quantity.amount > 0, "must issue positive quantity" );
eosio_assert( quantity.symbol == st.supply.symbol, "symbol precision mismatch" );
eosio_assert( quantity.amount <= st.max_supply.amount - st.supply.amount, "quantity exceeds available supply");
statstable.modify( st, 0, [&]( auto& s ) {
s.supply += quantity;
});
add_balance( st.issuer, quantity, st.issuer );//实现转账
if( to != st.issuer ) {
SEND_INLINE_ACTION( *this, transfer, {st.issuer,N(active)}, {st.issuer, to, quantity, memo} );
> 引用块内容
//调用内联函数 transfer函数,将发送action事件,将交易写入blcok中。
}
}
然后,我们继续使用命令行工具cleos来push action到智能合约eosio.token中这个issue函数中:
cuijb@cuijb-VirtualBox:~/eos$ cleos push action eosio.token issue '["eosio","20000000000000.0000 SYS","issue"]' -p eosio
executed transaction: 32cb94b523ec6061521a397cfcbc706cf3dc9c01430ca5b4e08278a588686482 128 bytes 388 us
# eosio.token <= eosio.token::issue {"to":"eosio","quantity":"20000000000000.0000 SYS","memo":"issue"}
注意,在命令行结尾处,我们仍要使用账户来签名授权,这里是用户eosio(因为上面eosio是发行者,全都发行到它的兜里啦,所以要从它兜里取钱)。
执行发放动作,通过日志可以看到包括了几个步骤:
实际上,eosio.token (合约账号),可以直接修改账户余额而不使用“内联调用transfer”。但是这种情况下,eosio.token智能合约会要求我们的token必须有所有的账户余额,通过计算引用过他们的所有交易动作的总和。它还需要发送者和接收者的存款情况,以支持他们可以自动处理充值和提现。
如果你想看到广播出去的真实交易的情况,可以使用-d -j选项来表达“不要广播”以及“以json格式返回交易”:
“不要广播”的意思是这条动作无效,只是用来做测试的。(这与上面的广播出去的“真实交易”不同)
cuijb@cuijb-VirtualBox:~/eos$ cleos push action eosio.token issue '["eosio","100.0000 SYS","issue"]' -p eosio -d -j
{
"expiration": "2018-07-22T03:16:02",
"ref_block_num": 19140,
"ref_block_prefix": 1093201444,
"max_net_usage_words": 0,
"max_cpu_usage_ms": 0,
"delay_sec": 0,
"context_free_actions": [],
"actions": [{
"account": "eosio.token",
"name": "issue",
"authorization": [{
"actor": "eosio",
"permission": "active"
}
],
"data": "0000000000ea305540420f00000000000453595300000000056973737565"
}
],
"transaction_extensions": [],
"signatures": [
"SIG_K1_KkQYRenPfx2hWYY36tHkBJXJWN4MAqXfes3dNZTacczpFRVWGh84Jy4kGP1t5kMNeMA2WEqiiBMacYwgraHA9orZrjZphf"
],
"context_free_data": []
}
代币交易
现在eosio账户已经存在20000000000000.0000个EOS代币了,我们使用上面建立的另一个账户jambeau,用来测试代币交易:从eosio账户中转出一部分EOS到jambeau账户。
同样的,我们还是先来看一下源码中设计的transfer函数的参数列表:
void token::transfer( account_name from, //转账人
account_name to, //接收人
asset quantity, //数量
string memo ) //备注
我们做一笔转账,从eosio转账到jambeau 100.0000 SYS
cuijb@cuijb-VirtualBox:~/eos$ cleos push action eosio.token transfer '["eosio","jambeau","100.0000 SYS","I love you"]' -p eosio
executed transaction: 6d849aa36c884ab8c98d0ef239b63bac5d9945bf83a92e626e01f0f339bcca64 136 bytes 546 us
# eosio.token <= eosio.token::transfer {"from":"eosio","to":"jambeau","quantity":"100.0000 SYS","memo":"I love you"}
# eosio <= eosio.token::transfer {"from":"eosio","to":"jambeau","quantity":"100.0000 SYS","memo":"I love you"}
# jambeau <= eosio.token::transfer {"from":"eosio","to":"jambeau","quantity":"100.0000 SYS","memo":"I love you"}
我们现在查询一下jambeau的账户的余额。
cuijb@cuijb-VirtualBox:~/eos$ cleos get table eosio.token jambeau accounts
{
"rows": [{
"balance": "100.0000 SYS"
}
],
"more": false
}
我们分析一下查询账户信息的命令
cleos get table ‘{合约名称}’ '{账户名称}’ accounts
我再通过jambeau账户给jamebau1 转账50.0000 SYS,通过jambeau做签名。
cuijb@cuijb-VirtualBox:~/eos$ cleos push action eosio.token transfer '["jambeau","jambeau1","50.0000 SYS","by jambeau sign"]' -p jambeau
executed transaction: 8cf97e5a31ffdfbf69d242167112b98f16b42103634c7ff9b3199ed5346efc9d 144 bytes 444 us
# eosio.token <= eosio.token::transfer {"from":"jambeau","to":"jambeau1","quantity":"50.0000 SYS","memo":"by jambeau sign"}
# jambeau <= eosio.token::transfer {"from":"jambeau","to":"jambeau1","quantity":"50.0000 SYS","memo":"by jambeau sign"}
# jambeau1 <= eosio.token::transfer {"from":"jambeau","to":"jambeau1","quantity":"50.0000 SYS","memo":"by jambeau sign"}
我们现在查询一下jambeau1的账户的余额。
cuijb@cuijb-VirtualBox:~/eos$ cleos get table eosio.token jambeau1 accounts
{
"rows": [{
"balance": "50.0000 SYS"
}
],
"more": false
}
jambeau1果真得到了50.0000 EOS代币,你的转账,签名都是OK的。
查询余额
我们需要整体研究一下cleos的所有子命令,列举的方式比较枯燥,这里不展开,只是使用到哪里就展示哪里。我们上面进行了代币发放和代币交易,此时三个测试账户eosio,jambeau和ajmbeau1的EOS余额都发生了变化。下面我们要利用cleos查询一下这两个账户的代币EOS的余额状况:
cuijb@cuijb-VirtualBox:~/eos$ cleos get currency balance eosio.token eosio SYS
19999999999900.0000 SYS
cuijb@cuijb-VirtualBox:~/eos$ cleos get currency balance eosio.token jambeau SYS
50.0000 SYS
cuijb@cuijb-VirtualBox:~/eos$ cleos get currency balance eosio.token jambeau1 SYS
50.0000 SYS
我们可以清晰的查出我三个账户的余额情况
我们再创建一个CAC代码,代币数量1000000000.0000 CAC
cuijb@cuijb-VirtualBox:~/eos$ cleos push action eosio.token create '["eosio","1000000000.0000 CAC"]' -p eosio.token
executed transaction: 982b4004708efe840cd30b5dd5cfd65775fee808ae51a0378eef262a347d2cff 120 bytes 326 us
# eosio.token <= eosio.token::create {"issuer":"eosio","maximum_supply":"1000000000.0000 CAC"}
我们再次分发给 jambeau 10000000.0000 CAC 代币
cuijb@cuijb-VirtualBox:~/eos$ cleos puch action issue '["jamebau,"200000000.0000 CAC","issue"]' -p eosio.token
Host and port options (-H, --wallet-host, etc.) have been replaced with -u/--url and --wallet-url
Use for example -u http://localhost:8888 or --url https://example.invalid/
分发成功,我们先查询一下当前账户jamebau的余额
cuijb@cuijb-VirtualBox:~/eos$ cleos get table eosio.token jambeau accounts
{
"rows": [{
"balance": "10000000.0000 CAC"
},{
"balance": "50.0000 SYS"
}
],
"more": false
}
我们可以清晰的查询到我们分发的资产 ;
我们下一步插叙一下余额试试看
#查询所有的币种的余额
cuijb@cuijb-VirtualBox:~/eos$ cleos get currency balance eosio.token jambeau
10000000.0000 CAC
50.0000 SYS
#查询币种的余额
cuijb@cuijb-VirtualBox:~/eos$ cleos get currency balance eosio.token jambeau CAC
10000000.0000 CAC
调试智能合约
现在user官方网站推荐的一个调试方法就是print,把信息打印出来。这个必须要我们搭建本地节点,因为如果没有本地节点,相当于你print打印在别人的节点上,你根本看不到这个打印信息是什么,所以说你必须要搭建一个本地节点。搭建本地节点后,你运行智能合约,就会看到print出来的输出结果。
EOS智能合约的RPC接口
其实智能合约整个只完成了DApp最核心的一部分,就是基本上和资金有关系的一些关键操作,其实大部分的接口、界面,还得我们用JavaScript、HTML去做。
那我们这些DApp其它写界面的操作,怎么去调用智能合约呢?都是通过user智能合约RPC接口调用,智能合约的RPC接口文档链接是:https://eosio.github.io/eos/group__eosiorpc.htm。
RPC的接口我们除了用C++或者用编程的方法去调用一些接口,我们可以用curl这种最简单的方法去调用这个接口。curl它相当于模拟了一个浏览器的操作,我可以向我的运行节点的RPC端口发消息。
这里面我可以给大家展示,我列了3个。
1.get_info:获得节点信息。通过调用这个接口,它会返回我运行节点,比如说server version,就是我运行节点这个节点的版本号;head blocknum,是我当前挖到哪个块了。
如下命令:
curl http://127.0.0.1:8888/v1/chain/get_info
2 . get_block:获得一个块的信息。调用该接口,指定块号(blocknum),就可以获得指定块的详细信息。
如下命令:
$ curl http://127.0.0.1:8888/v1/chain/get_block -X POST -d'{"block_num_or_id":5}'
3.get_account:获得某个账号的信息。调用这个接口,可以获得当前我的一个账号信息
命令如下:
$ curlhttp://127.0.0.1:8888/v1/chain/get_account -X POST -d'{"account_name":"inita"}'
EOS智能合约编程示例:HelloPDJ
下面有一个编程示例,给大家展示一下我怎么样写智能合约的。它这个智能合约可以用C语言(一种计算机程序语言)来写,也可以用C++(一种计算机程序语言)来写,这里面我就用C++来写。示例代码如下:
// hello.cpp源代码
#include
#include
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) )
如果我自己写了一个智能合约,怎么去编译和部署呢?编译的步骤大概是这样的:
第一步,编译hello文件
$ eosiocpp -o hello.wasthello.cpp
$ eosiocpp -g hello.abihello.cpp
#或则一个办法
$ sudo ./eosio_build.sh
第二步,创建账号
$cleos create account eosio hello.codeEOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4 ...
第三步,部署合约
cleos set contract hello.code../hello -p hello.code
第四步,调用合约
cleos push action hello.codehi '["user"]' -p user
恭喜你,你已经成功部署了智能合约。整个过程,都是基于命令行完成的。目前没有太多机构开发相关套件支持EOS合约开发。所以,需要些一些复杂的合约,还是有点困难的。希望更多人架构参与进来。
2018年7月11日整理于深圳。