EOS智能合约开发(四)EOS智能合约部署及调试(附编程示例)

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,它的功能是控制资源分配。

这个默认合约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"}
  • 命令行使用push action来执行这个动作
  • 动作是eosio.token账户create这个合约的
  • 动作的参数包括:eosio是发行人的账号,发行量是100000000000000EOS。
  • 动作的执行人(签名者)是eosio.token,用来授权这个动作的执行(必须和部署合约的账户一致

直接按参数顺序传入值比较方便,如果你需要更加准确的传值,可以将以上动作的参数内容改写为“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命名空间(就是代码中的eosio包内)下的发放函数issue,该操作由用户eosio授权(因为正是eosio授权的代币发行!)
  • 这三行transfer都是eosio命名空间下的transfer函数,他们都是内联交易:是由上面的发放函数issue自动触发的
    • 第一行由账户eosio授权,执行issue函数。executed transaction: 32cb94b523ec6061521a397cfcbc706cf3dc9c01430ca5b4e08278a588686482 128 bytes 388 us 说明执行这笔交易,占用128bytes 花费了 388 uss 时间
    • 第二行由账户eosio授权,执行transfer函数。”>> transfer”就是该函数的输出。# eosio.token <= eosio.token::issue {“to”:”eosio”,”quantity”:”20000000000000.0000 SYS”,”memo”:”issue”}
      这行信息清晰的描述了这笔资产是发放到eosio这个账户里,数量,备注信息issue。
      transfer内部调用(notified)sub_balance和add_balance

实际上,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 <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) )

如果我自己写了一个智能合约,怎么去编译和部署呢?编译的步骤大概是这样的:

第一步,编译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日整理于深圳。

猜你喜欢

转载自blog.csdn.net/jambeau/article/details/80998278
今日推荐