Tutorial Getting Started With Contracts_zh.md - wilddylan/eos GitHub Wiki

EOSIO开发教程

本教程的目的是演示如何设置一个本地区块链,可以用来试验智能合约。本教程的第一部分将关注以下内容:

  1. 启动节点
  2. 创建钱包
  3. 创建账户
  4. 部署智能合约
  5. 与智能合约交互

本教程的第二部分将介绍如何创建和部署自己的智能合约。

本教程假设您已经安装了 EOSIO,而 nodeoscleos 在您的路径中。

开始一个单节点区块链

仅一行代码就可以开启单节点区块链:

$ nodeos -e -p eosio --plugin eosio::wallet_api_plugin --plugin eosio::chain_api_plugin --plugin eosio::account_history_api_plugin 
...
eosio generated block 046b9984... #101527 @ 2018-04-01T14:24:58.000 with 0 trxs
eosio generated block 5e527ee2... #101528 @ 2018-04-01T14:24:58.500 with 0 trxs

这个命令设置了很多标记并加载了一些可选的插件,这是我们接下来的教程所需要的。假设一切正常,您应该每0.5秒就看到一个块生成消息。

eosio generated block 046b9984... #101527 @ 2018-04-01T14:24:58.000 with 0 trxs

这意味着您的本地区块链是实时的,生成块,并准备使用。 有关 nodeos 参数的更多信息,请使用:

nodeos --help

创建一个钱包

钱包是在区块链上授权操作所需的私有密钥的存储库。这些密钥存储在磁盘上,为您生成的密码进行加密。这个密码应该存储在一个安全的密码管理器中。

$ 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.
"PW5JuBXoXJ8JHiCTXfXcYuJabjF9f9UNNqHJjqDVY7igVffe3pXub"

为了这个简单的开发环境,您的钱包是由您的本地 nodeos 通过 eosio::wallet_api_plugin 管理的,我们在启动 nodeos 时启用了它。当你重启 nodeos 时,你必须先解锁你的钱包,然后才能使用钥匙。

$ cleos wallet unlock --password PW5JuBXoXJ8JHiCTXfXcYuJabjF9f9UNNqHJjqDVY7igVffe3pXub
Unlocked: default

在命令行上直接使用密码是不安全的,因为它会被记录到您的 bash$ history 中,所以您也可以在交互模式下解锁:

$ cleos wallet unlock
password:

出于安全考虑,当你不使用时,通常最好把钱包锁起来。在不关机的情况下锁定你的钱包,你可以这样做:

$ cleos wallet lock
Locked: default

在本教程的剩余部分中,您将需要解锁您的钱包。

所有新的区块链从一个主键开始,这是唯一的初始账户 eosio。要与区块链交互,您需要将这个初始帐户的私钥导入到您的钱包中。

eosio 帐户的主键导入到您的钱包中。主键可以在配置中找到。为 nodeos 配置文件夹中的 ini 文件。在本例中,使用了默认的配置文件夹。在 Linux 系统上,绝对地址为 ~/.local/share/eosio/nodeos/config,在 Mac OS 上,绝对地址为 ~/Library/Application Support/eosio/nodeos/config

$ cleos wallet import 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
imported private key for: EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV

加载 Bios 合约

既然我们已经有了一个带有 eosio 账户密钥的钱包,我们可以设置一个默认的系统合约。为了开发的目的,默认的 eosio。可以使用 bios 智能合约。该合约使您能够直接控制其他帐户的资源分配,并访问其他特权API调用。在公共区块链中,该合约将管理令牌的 staking 和 unstaking,以保留 CPU 和网络活动的带宽,以及合约的内存。

eosio.bios 合约 可以在 contracts/eosio.bios EOSIO 源码目录下发现。默认是从源码位置执行,当然你也可以指定执行 ${EOSIO_SOURCE}/build/contracts/eosio.bios

$ cleos set contract eosio build/contracts/eosio.bios -p eosio
Reading WAST...
Assembling WASM...
Publishing contract...
executed transaction: 414cf0dc7740d22474992779b2416b0eabdbc91522c16521307dd682051af083  4068 bytes  10000 cycles
#         eosio <= eosio::setcode               {"account":"eosio","vmtype":0,"vmversion":0,"code":"0061736d0100000001ab011960037f7e7f0060057f7e7e7e...
#         eosio <= eosio::setabi                {"account":"eosio","abi":{"types":[],"structs":[{"name":"set_account_limits","base":"","fields":[{"n...

这个命令序列的结果是,cleos 生成了一个带有两个动作的事务,eosio::setcodeeosio::setabi。 代码定义了合约的运行方式,abi 描述了如何在参数的二进制和 json 表示之间进行转换。虽然 abi 在技术上是可选的,但是所有的 EOSIO 工具都依赖于它的易用性。 当您执行事务时,您将看到如下输出:

executed transaction: 414cf0dc7740d22474992779b2416b0eabdbc91522c16521307dd682051af083  4068 bytes  10000 cycles
#         eosio <= eosio::setcode               {"account":"eosio","vmtype":0,"vmversion":0,"code":"0061736d0100000001ab011960037f7e7f0060057f7e7e7e...
#         eosio <= eosio::setabi                {"account":"eosio","abi":{"types":[],"structs":[{"name":"set_account_limits","base":"","fields":[{"n...

可以被理解为:这个被 eosio 定义的动作 setcodeeosio 合约携带一些参数执行了。

#         ${executor} <= ${contract}:${action} ${args...}
> console output from this execution, if any

正如我们将会看到的,操作可以通过多个合约来处理。 这个调用的最后一个参数是 -p eosio。这告诉 cleos 用 eosio 帐户的活动权限来签署此操作,即,使用我们之前导入的eosio帐户的私钥签署该操作。

创建账户

现在我们已经建立了基本的系统契约,我们可以开始创建我们自己的帐户。我们将创建两个帐户,用户和测试人员,我们需要将一个键与每个帐户关联。在本例中,两个帐户都使用相同的密钥。 为此,我们首先为帐户生成一个密钥。

$ cleos create key
Private key: 5Jmsawgsp1tQ3GD6JyGCwy1dcvqKZgX6ugMVMdjirx85iv5VyPR
Public key: EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4

然后我们把密钥导入我们的钱包:

$ cleos wallet import 5Jmsawgsp1tQ3GD6JyGCwy1dcvqKZgX6ugMVMdjirx85iv5VyPR
imported private key for: EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4

注意: 确保使用cleos命令生成的实际键值,而不是上面示例中所示的键值!密钥不会自动添加到钱包中,因此跳过这一步可能会导致对您的帐户失去控制。

创建两个用户帐户

接下来,我们将创建两个帐户,usertester,使用我们在上面创建和导入的密钥。

$ cleos create account eosio user EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4 EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4
executed transaction: 8aedb926cc1ca31642ada8daf4350833c95cbe98b869230f44da76d70f6d6242  364 bytes  1000 cycles
#         eosio <= eosio::newaccount            {"creator":"eosio","name":"user","owner":{"threshold":1,"keys":[{"key":"EOS7ijWCBmoXBi3CgtK7DJxentZZ...

$ cleos create account eosio tester EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4 EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4
executed transaction: 414cf0dc7740d22474992779b2416b0eabdbc91522c16521307dd682051af083 366 bytes  1000 cycles
#         eosio <= eosio::newaccount            {"creator":"eosio","name":"tester","owner":{"threshold":1,"keys":[{"key":"EOS7ijWCBmoXBi3CgtK7DJxentZZ...

注意: create account 命令需要两个密钥,一个用于OwnerKey(在生产环境中应该保持高度安全),另一个用于ActiveKey。在本教程示例中,两者都使用相同的密钥。

因为我们在启动节点时配置了该插件 eosio::account_history_api_plugin,我们可以查询所有由我们的密钥控制的帐户:

$ cleos get accounts EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4
{
  "account_names": [
    "tester",
    "user"
  ]
}

创建 token 智能合约

在这个阶段区块链没有做太多,所以让我们部署 eosio.token。该合约允许在同一合约上创建许多不同的 token,由不同的用户管理。 在部署 token 合约之前,我们必须创建一个帐户来部署它。

$ cleos create account eosio eosio.token  EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4 EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4
...

在该目录下可以找到合约:${EOSIO_SOURCE}/build/contracts/eosio.token

$ cleos set contract eosio.token build/contracts/eosio.token -p eosio.token
Reading WAST...
Assembling WASM...
Publishing contract...
executed transaction: 528bdbce1181dc5fd72a24e4181e6587dace8ab43b2d7ac9b22b2017992a07ad  8708 bytes  10000 cycles
#         eosio <= eosio::setcode               {"account":"eosio.token","vmtype":0,"vmversion":0,"code":"0061736d0100000001ce011d60067f7e7f7f7f7f00...
#         eosio <= eosio::setabi                {"account":"eosio.token","abi":{"types":[],"structs":[{"name":"transfer","base":"","fields":[{"name"...

创建 EOS Token

你可以查看相关 eosio.token 的接口,定义在 contracts/eosio.token/eosio.token.hpp 中:

   void create( account_name issuer,
                asset        maximum_supply,
                uint8_t      can_freeze,
                uint8_t      can_recall,
                uint8_t      can_whitelist );


   void issue( account_name to, asset quantity, string memo );

   void transfer( account_name from,
                  account_name to,
                  asset        quantity,
                  string       memo );

为了创建一个新的令牌,我们必须用正确的参数调用 create(…) 动作。这个命令将使用最大供应的符号来唯一地从其他令牌标识这个令牌。发行者将是有权要求发行或执行其他行为的人,如冻结、召回和白名单所有者。

使用位置参数(数组,按照参数顺序传入)来调用此方法的简明方法:

$ cleos push action eosio.token create '[ "eosio", "1000000000.0000 EOS", 0, 0, 0]' -p eosio.token
executed transaction: 0e49a421f6e75f4c5e09dd738a02d3f51bd18a0cf31894f68d335cd70d9c0e12  260 bytes  1000 cycles
#   eosio.token <= eosio.token::create          {"issuer":"eosio","maximum_supply":"1000000000.0000 EOS","can_freeze":0,"can_recall":0,"can_whitelis...

或者,更详细的方法( Key:value 形式)调用此方法:

$ cleos push action eosio.token create '{"issuer":"eosio", "maximum_supply":"1000000000.0000 EOS", "can_freeze":0, "can_recall":0, "can_whitelist":0}' -p eosio.token
executed transaction: 0e49a421f6e75f4c5e09dd738a02d3f51bd18a0cf31894f68d335cd70d9c0e12  260 bytes  1000 cycles
#   eosio.token <= eosio.token::create          {"issuer":"eosio","maximum_supply":"1000000000.0000 EOS","can_freeze":0,"can_recall":0,"can_whitelis...

这个命令创建了一个新的 EOS token,4位小数点,总计 1000000000.0000 EOS。

为了创建这个 token,我们需要 eosio 的许可。eos.token,因为它 “拥有” 符号名称空间 (例如: “EOS” )。本合同的未来版本可能允许其他各方自动购买符号名称。因此我们必须通过 -p eosio.Token 授权此调用。

向帐户 User 发出 tokens

现在我们已经创建了 tokens,发行者可以向我们之前创建的帐户用户发出新的 tokens。我们将使用位置调用约定(vs命名的args):

$ cleos push action eosio.token issue '[ "user", "100.0000 EOS", "memo" ]' -p eosio
executed transaction: 822a607a9196112831ecc2dc14ffb1722634f1749f3ac18b73ffacd41160b019  268 bytes  1000 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"}

这一次输出包含几种不同的操作: 一次发放与三次交易. 虽然我们签署的唯一行是 发放 (issue), 发放 行动执行了两次 内联转移 通知发件人和接收者帐户。输出指示所有被调用的操作处理程序,调用它们的顺序,以及操作是否生成任何输出。

从技术上讲,eosio.token 可以跳过内联传输,并选择直接修改余额。然而,在这种情况下,eosio.token 遵循我们的令牌约定,要求所有帐户余额都可以通过引用它们的传输操作的和来派生。它还要求通知资金的发件人和接收者,以便他们能够自动处理存款和取款。如果您希望看到广播的实际事务,您可以使用 -d -j 选项表示 不广播返回事务为json

$ cleos push action eosio.token issue '["user", "100.0000 EOS", "memo"]' -p eosio -d -j
{
  "expiration": "2018-04-01T15:20:44",
  "region": 0,
  "ref_block_num": 42580,
  "ref_block_prefix": 3987474256,
  "net_usage_words": 21,
  "kcpu_usage": 1000,
  "delay_sec": 0,
  "context_free_actions": [],
  "actions": [{
      "account": "eosio.token",
      "name": "issue",
      "authorization": [{
          "actor": "eosio",
          "permission": "active"
        }
      ],
      "data": "00000000007015d640420f000000000004454f5300000000046d656d6f"
    }
  ],
  "signatures": [
    "EOSJzPywCKsgBitRh9kxFNeMJc8BeD6QZLagtXzmdS2ib5gKTeELiVxXvcnrdRUiY3ExP9saVkdkzvUNyRZSXj2CLJnj7U42H"
  ],
  "context_free_data": []
}

转义 Tokens 给 "Tester"

既然帐户 user 已经有了令牌,我们将把一些帐户转到 tester 账户下。我们指示用户使用权限参数 -p user 授权此操作。

$ cleos push action eosio.token transfer '[ "user", "tester", "25.0000 EOS", "m" ]' -p user
executed transaction: 06d0a99652c11637230d08a207520bf38066b8817ef7cafaab2f0344aafd7018  268 bytes  1000 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"}

Hello World Contract

我们将创建我们的第一个 hello world 智能合约。创建一个名为 hello 的新文件夹,cd 进入文件夹,然后创建一个文件 hello.cpp,输入以下内容(这里请使用最新的master分支):

hello/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) )

您可以将代码编译成 web assmebly (.wast):

$ eosiocpp -o hello.wast hello.cpp

注意: 编译器可能会生成警告。这些可以被安全地忽略。

现在生成 abi 文件:

$ eosiocpp -g hello.abi hello.cpp
Generated hello.abi

创建帐户并上传智能合约:

$ cleos create account eosio hello.code EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4 EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4
...
$ cleos set contract hello.code ../hello -p hello.code
...

现在我们可以运行智能合约了:

$ cleos push action hello.code hi '["user"]' -p user
executed transaction: 4c10c1426c16b1656e802f3302677594731b380b18a44851d38e8b5275072857  244 bytes  1000 cycles
#    hello.code <= hello.code::hi               {"user":"user"}
>> Hello, user

此时智能合约允许任何人授权,我们也可以说:

$ cleos push action hello.code hi '["user"]' -p tester
executed transaction: 28d92256c8ffd8b0255be324e4596b7c745f50f85722d0c4400471bc184b9a16  244 bytes  1000 cycles
#    hello.code <= hello.code::hi               {"user":"user"}
>> Hello, user

在这种情况下,tester 是授权它的人,而用户只是一个参数。如果我们想要我们的联系人对我们所说的 hi 用户进行身份验证,那么我们需要修改智能合约来要求认证。 在 hello.cpp 中修改 hi() 函数。cpp如下:

void hi( account_name user ) {
   require_auth( user );
   print( "Hello, ", name{user} );
}

重复上述步骤来编译 wast 文件并生成 abi,然后再次设置该契约来部署更新。 现在,如果我们试图不匹配用户和权限,智能合约会抛出一个错误:

$ cleos push action hello.code hi '["tester"]' -p user
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 tester

我们可以通过赋予 tester 的权限可来解决这个问题:

$ cleos push action hello.code hi '["tester"]' -p tester
executed transaction: 235bd766c2097f4a698cfb948eb2e709532df8d18458b92c9c6aae74ed8e4518  244 bytes  1000 cycles
#    hello.code <= hello.code::hi               {"user":"tester"}
>> Hello, tester

部署交易合约

与上面的例子类似,我们可以部署交易合约。假设这是从 EOSIO 源的根运行的。

$ cleos create account eosio exchange  EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4 EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4
executed transaction: 4d38de16631a2dc698f1d433f7eb30982d855219e7c7314a888efbbba04e571c  364 bytes  1000 cycles
#         eosio <= eosio::newaccount            {"creator":"eosio","name":"exchange","owner":{"threshold":1,"keys":[{"key":"EOS7ijWCBmoXBi3CgtK7DJxe...

$ cleos set contract exchange build/contracts/exchange -p exchange
Reading WAST...
Assembling WASM...
Publishing contract...
executed transaction: 5a63b4de8a1da415590778f163c5ed26dc164c960185b20fd834c297cf7fa8f4  35172 bytes  10000 cycles
#         eosio <= eosio::setcode               {"account":"exchange","vmtype":0,"vmversion":0,"code":"0061736d0100000001f0023460067f7e7f7f7f7f00600...
#         eosio <= eosio::setabi                {"account":"exchange","abi":{"types":[{"new_type_name":"account_name","type":"name"}],"structs":[{"n...

中文译本,2018-04-09,如有问题请 Issue。