终极端对端 EOS dApp 开发教程 — 第二部分

原文链接,作者:Infinite X Labs 译者:濯缨

你准备好做一些更重要的事情了吗?

让我们总结一下在前面的两个教程中我们已经学到了什么。 我们知道怎样配置和构建EOSIO,怎样使用nodeos开始一个本地testnet节点,了解了怎样创建账户,并使用该账户部署合约。 我们学过的最重要的事情是,怎样写一个EOS智能合约或者说写一个智能合约的基础版本。我们已经做了很多新的事情,但是在教程的第二部分,我们将会做更多。

如果你刚刚接触到这个系列,你应该首先学习一下前面两个教程:

  1. EOS区块链开发的第一步
  2. 终极端对端EOS dApp开发教程 第一部分

在第一部分中,我们已经开始开发我们的Oasis dApp了。 我们创建了一个简单的项目结构,实现了Player智能合约的一部分功能。 但是我们还有很多东西要做,让我们开始吧.

注意,EOSIO现在仍然在开发阶段,有些步骤在未来可能会有所变化,我们团队将会尽力保持这个教程跟上最新的变化。

就像我上次说的那样,我们已经创建了一个简单的项目结构。但是,当你在现实世界开发一个可以应用的EOS dApp的时候,事情并不这么简单。 我们想要我们做的任何事情都可以自动完成,而且可以通过最好的方法来生成我们所需要的文件来开发dApp。 这就是为什么我们组又发布了这个新教程的原因 “How to setup VS Code and/or CLion for EOS dApp Development“。

我们认为,如果配置教程与这个开发教程互相独立,条理将会更加清晰。所以在开始之前,请按照这个教程去配置你的IDE吧。

项目升级

配置好你的IDE了么? 配置好了?非常棒,让我们开始吧!

现在你已经有了开发你的dApp的基本框架。首先你需要把你旧的项目中的文件夹移动到新的项目中。更精确的说,PLayersMarkerplaceGamesOasis 需要在你的contracts文件夹中,当你完成后,就是我们开始构建应用的时候了。

注意在构建你的项目之前,你的每个合约都应该已经生成了.abi文件。如果你在使用VS Code 和我们定制的命令的话,那么你只要在Players文件夹中运行⌘+I 命令,就会生成Player.abi。⌘+I 命令是我们设置的用于生成.abi文件的快捷键。

现在运行⌘+R 来开始构建流程了,由于这是我们第一次构建,所以这会花一段时间来构建所有的东西。

当构建结束之后,你将能看到,这又帮你生成了一些新的文件。

更重要的是,这里包含了我们Players 智能合约所需要的文件,你可以在 build/contracts/Players目录中找到它们:

你应当已经对一些文件比较熟悉了,比如Players.abiPlayers.wast。 上个教程中我们使用了他们在区块链上部署我们的智能合约。 其他的文件我们将在单元测试中用到。

更新和扩展智能合约

1.更新智能合约

在第一部分教程中,我们已经创建了两个文件—Players.hppPlayers.cpp, 这两个文件分别是接口(.cpp)和 实现(.cpp)。我们写的几乎所有的代码都在Players.cpp文件中,在Players.hpp中我们只是用来导入我们需要的头文件。 但是在这里我们将会做出些许改变,我们这么做有以下两个原因:

  1. 首先,头文件(.hpp)负责声明所有的变量,常量和函数,这些将会被.cpp文件引用。 头文件基本上相当于一个接口。另一方面,实现文件(.cpp)保存了所有函数的真正实现。
  2. 第二个更新我们合约的原因来源于C++和编程惯例。当你想要在一个合约中使用另一个合约的时候,你应该导入它。但是最好的导入方法是只导入合约头文件(.hpp)。一个人可以使用这个合约,但是不需要知道合约的具体实现。换句话说,我们不应该导入.cpp文件,这是个糟糕的方法。

所以,我们需要像下面这样更新这两个文件:

Players.hpp

#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>
#include <string>

namespace Oasis {
    using namespace eosio;
    using std::string;

    class Players : public contract {
        using contract::contract;

        public:

            Players(account_name self):contract(self) {}

            //@abi action
            void add(account_name account, string& username);

            //@abi action
            void update(account_name account, uint64_t level, int64_t healthPoints, int64_t energyPoints);

            //@abi action
            void getplayer(const account_name account);

        private:

            //@abi table player i64
            struct player {
                uint64_t account_name;
                string username;
                uint64_t level;
                int64_t health_points = 1000;
                int64_t energy_points = 1000;

                uint64_t primary_key() const { return account_name; }

                EOSLIB_SERIALIZE(player, (account_name)(username)(level)(health_points)(energy_points))
            };

            typedef multi_index<N(player), player> playerIndex;
    };

    EOSIO_ABI(Players, (add)(update)(getplayer));
}

Players.cpp

#include "Players.hpp"

namespace Oasis {
    void Players::add(account_name account, string& username) {
        // Action implementation body
    }

    void Players::update(account_name account, uint64_t level, int64_t healthPoints, int64_t energyPoints) {
        // Action implementation body
    }
        
    void Players::getplayer(const account_name account) {
        // Action implementation body
    }
}

让我们看看我们改变了什么。

Players.hpp现在包含了Players 类的声明。在这个类里,我们有player 结构和他们的action—add,update和getplayer, 但是在这里他们只是一个标志。这些action的实现在Players.cpp中。 实现文件(.cpp)的结构也有所变化。我们的action现在都在一个namespace 里面,他们可以通过Player::{action_name}来调用Player类的action。 同时,我们将EOSIO_ABI宏移动到了Players.hpp里。

你可能在想,到底为什么要这么做呢?让我们总结一下:

头文件(.hpp)允许你向别的.cpp文件公开你的接口,在这里就是Players类,同时将真正的实现放在它的.cpp文件中,在这里就是每个action的函数体。由于#inlcude声明基本上就像复制粘贴的操作,所以在编译时,编译器会将#include 这一行替换为相应的文件内容。

让我们确认一下我们没有破坏掉任何东西。 当你还在Players.cpp文件中,运行⌘+I 来生成Player.abi文件,然后运行 ⌘+R来重新构建Players 合约。当它结束的时候,你可以在终端中将这个合约再次部署到区块链上,你还记得怎么做的对吧?

给你个提示,你的当前文件夹应该是“build”:

# cleos set contract {account} {path_to_contract_folder} {path_to_.wast_file} {path_to_.abi_file}

cleos set contract anorak ./contracts/Players ./contracts/Players/Players.wast ./contracts/Players/Players.abi

如果你有段时间没使用过oasis钱包的话,你可能会得到一个错误“Error 3120003: Locked wallet“。 为了解锁你的钱包,你需要运行以下的命令。 注意你需要你的钱包密码, 在你上次创建oasis钱包的时候你保存了密码,对吧?那就是你需要的密码。

$ cleos wallet unlock -n {wallet_name} --password {password}

如果你没保存密码的话,那么有三个选择:

  1. 删除当前的oasis钱包,然后重新创建它。这次千万不要忘记保存你的密码和密钥了。
  2. 重新创建一个新的钱包。
  3. 开始一个新的区块链节点

选项1和3应该是在这个情况下最好的,因为当前所有的原型都在使用oasis钱包,如果你使用另外一个名字的话,有可能会出现错误。

再试着部署一下你的合约把。这次应该没什么问题了,然后Players合约被部署了。让我们添加一个新的player。注意你需要创建一个新的账户,因为每个账户必须只对应一个玩家。 重新创建一个新的密钥对,然后导入到oasis钱包中,将账户的名字命名为“wade”,然后创建一个新的玩家—parzival

# cleos push action {account} {action_name} '{data}' -p {account}@active
cleos push action anorak add '["wade","parzival"]' -p wade@active
executed transaction: dbd220f6a0b2cae4c216cb5a78bddd9dc50f0678424cc13d41a86b1f375731c8  112 bytes  386 us
#        anorak <= anorak::add                  {"account":"wade","username":"parzival"}

cleos push action anorak getplayer '["wade"]' -p wade@active
executed transaction: 93d74d8ff08d5984ee197447409ffe10dad3855ae000431709d575035d74d3a3  104 bytes  431 us
#        anorak <= anorak::getplayer            {"account":"wade"}

>> Username: parzival Level: 1 Health: 1000 Energy: 1000

在我们的教程系列里,我们都会使用这个玩家。现在我们需要继续扩展Players合约。

2.扩展

一旦你已经更新了你的Players合约,我们将会添加一些新的东西到里面。现在,每个player都有usernamelevelheath_pointsenergy_points。再下一步,我们需要添加一个集合来保存player所有的abilityinventoryinventory包含了他从市场上买到的所有东西。最后,我们将会为我们的dApp创建货币。 如果我们需要从市场上买东西,那么我们将会需要一些货币来做这件事。

abilities

切换到Players.hpp 的player结构体中,添加新的abilities容器,就像这样:

//@abi table player i64
struct player {
    uint64_t account_name;
    string username;
    uint64_t level;
    int64_t health_points = 1000;
    int64_t energy_points = 1000;
    vector<string> abilities; // This is our new property

    uint64_t primary_key() const { return account_name; }

    EOSLIB_SERIALIZE(player, (account_name)(username)(level)(health_points)(energy_points)(abilities))
};

C++中的vector是一个长度可以改变的序列容器。这就完全是我们想要的东西,因为我们并不知道一个player可以拥有多少个ability。就像我们看到的那样,我们的vector只包含string元素。 不要忘记将ability添加到 EOSLIB_SERIALIZE里。

在Players.hpp中我们还需要做一件事。为了能正常的而工作,对于新添加的vector,我们需要删除private修饰词。换句话说,我们将这个结构体公开。所以在继续之前先去把它删掉把。.

我们已经添加了新的特性,但是我们还没有一个action来为player添加ability。这就是我们将要做的:

# Players.hpp

//@abi action
void addability(const account_name account, string& ability);

# Players.cpp

void Players::addability(const account_name account, string& ability) {
    require_auth(account);

    playerIndex players(_self, _self);

    auto iterator = players.find(account);
    eosio_assert(iterator != players.end(), "Address for account not found");

    players.modify(iterator, account, [&](auto& player) {
       player.abilities.push_back(ability);
    });
}

当我们想要为一个player添加一个新的能力时,我们执行addability action。

在测试我们的新的action之前,我们需要改变getplayer action。删除旧的print然后添加这段代码:

print("Username: ", currentPlayer.username.c_str());
print(" Level: ", currentPlayer.level);
print(" Health: ", currentPlayer.health_points);
print(" Energy: ", currentPlayer.energy_points);
        
if (currentPlayer.abilities.size() > 0) {
    print(" Abilities: ");

    for (uint32_t i = 0; i < currentPlayer.abilities.size(); i++) {
        print(currentPlayer.abilities.at(i).c_str(), " ");
    }
} else {
    print(" No Abilities");
}

让我们来测试一下吧。 你还记得我们需要怎么做么?⌘+I, ⌘+R 然后部署我们的合约。你现在准备好了么?好的。让我们为player art3mis 添加“shapeshifting”能力吧。

cleos push action anorak addability '["anorak","shapeshifting"]' -p anorak@active

executed transaction: 45dbfa10c393de363f42f35027fbeb61e5d576b0111e3da22556ca1c9221771a  120 bytes  1560 us
#        anorak <= anorak::addability           {"account":"anorak","ability":"shapeshifting"}

cleos push action anorak getplayer '["anorak"]' -p anorak@active

executed transaction: bcfa0e9ef5649b11194b7ac47c6cee7242fceff2390738bcf1bc21b27b3f79f6  104 bytes  477 us
#        anorak <= anorak::getplayer            {"account":"anorak"}
>> Username: art3mis Level: 4 Health: 925 Energy: 890 Abilities: shapeshifting

干的漂亮,我们现在为一个player添加了新能力,试着自己添加一个吧。

Inventory

Inventory是player另外一个重要的特性, 它保存了一个玩家从市场上获得的所有物品。

为了实现这个,我们需要另外一个vector容器来保存这些物品。我们还需要创建item 对象。当然,我们还需要一个action来将item添加到容器中。

让我们首先从向Players.hpp添加新的item对象开始吧。每个这个类型的对象都需要item_idname,这个物品可以给他的所有者带来多少powerheath,这个物品是否给所有者带来新的ability或者使所有者升级呢?

//@abi table item i64
struct item {
    uint64_t item_id;
    string name;
    uint64_t power;
    uint64_t health;
    string ability;
    uint64_t level_up;

    uint64_t primary_key() const { return item_id; }

    EOSLIB_SERIALIZE(item, (item_id)(name)(power)(health)(ability)(level_up))
};

在player结构体中添加新的特性inventory——inventory将会是item组成的vector。现在,创建和实现向inventory中添加物品的action吧,这个action将会把item添加到容器中:

# Players.hpp

//@abi action
void additem(const account_name account, item purchased_item);

# Players.cpp

void Players::additem(const account_name account, item purchased_item) {
    playerIndex players(_self, _self);

    auto iterator = players.find(account);
    eosio_assert(iterator != players.end(), "Address for account not found");

    players.modify(iterator, account, [&](auto& player) {
        player.energy_points += purchased_item.power;
        player.health_points += purchased_item.health;
        player.level += purchased_item.level_up;
        player.abilities.push_back(purchased_item.ability);
        player.inventory.push_back(item{
            purchased_item.item_id,
            purchased_item.name,
            purchased_item.power,
            purchased_item.health,
            purchased_item.ability,
            purchased_item.level_up
        });
    });

    print("Item Id: ", purchased_item.item_id);
    print(" | Name: ", purchased_item.name.c_str());
    print(" | Power: ", purchased_item.power); 
    print(" | Health: ", purchased_item.health);
    print(" | Ability: ", purchased_item.ability.c_str());
    print(" | Level up: ", purchased_item.level_up);
}

我们对inventory的实现就到此结束了,因为我们需要一个Marketplace智能合约,但是现在我们还没有创建它呢。

Marketplace 智能合约

我们将要创造我们的Markerplace智能合约。这里是player可以买东西的地方。

我们的合约将会有以下几种action:

  • add —这将会添加新的物品到Marketplace
  • update —这会通过产品id来更新产品的量
  • remove—这会从Marketplace中去除产品
  • getbyid—使用产品id作为参数我们将会获得该产品的相关信息
  • buy—当用户想要买一个新的物品的时候,他将使用这个action

你已经知道了EOS智能合约的骨架是什么样子了,在继续之前,你可以先创建Marketplace.hpp和Marketplace.cpp。如果你还不确定怎么创建的话,可以先参考以下Player的智能合约。不要忘记在contract文件夹的CMakeList.txt中添加add_subdirectory(Marketplace)

一旦你已经准备好了这个合约,并且添加了这些action,不要忘记把他们添加到底部的EOSIO_ABI宏里。同样,注意由于需要保存所有的产品,我们将会需要一个muti-index 容器productIndex,而且还需要一个对象模板product。我们的add action使用product结构体作为参数,这意味着在声明这个action之前你需要声明这个结构体。如果我们不这么做的话,action就不会知道这个结构体,所以也不会起作用了。

//@abi action
void buy(account_name buyer, uint64_t productId);

//@abi action
void getbyid(uint64_t productId);

/**
 * Marketplace processes
*/

//@abi table product i64
struct product {
    uint64_t product_id;
    string name;
    uint64_t power;
    uint64_t health;
    string ability;
    uint64_t level_up;
    uint64_t quantity;
    uint64_t price;

    uint64_t primary_key() const { return product_id; }

    EOSLIB_SERIALIZE(product, (product_id)(name)(power)(health)(ability)(level_up)(quantity)(price))
};

typedef multi_index<N(product), product> productIndex;

//@abi action
void add(account_name account, product newProduct);

//@abi action
void update(account_name account, uint64_t product_id, uint64_t quantity);

//@abi action
void remove(account_name account, uint64_t productId);

现在,让我们实现我们的action吧。你应该已经知道怎么定义了,因为我们已经在Players 智能合约中做过这些事情了,有些真的和Players的一模一样的,例如add,update,getbyid。 这是我们对所有action的实现:

void Marketplace::buy(account_name buyer, uint64_t productId) {
    productIndex products(_self, _self);

    auto iterator = products.find(productId);
    eosio_assert(iterator != products.end(), "The product not found");

    auto product = products.get(productId);
    eosio_assert(product.quantity > 0, "The product is out of stock");

    asset productPrice = asset(product.price, string_to_symbol(4, "OAS"));

    action(vector<permission_level>(), N(anorak), N(transfer), make_tuple(buyer, _self, productPrice, string(""))).send();

    action(vector<permission_level>(), N(anorak), N(additem), make_tuple(buyer, 
        product.product_id,
        product.name,
        product.power,
        product.health,
        product.ability,
        product.level_up
    )).send();

    update(buyer, product.product_id, -1);
}

void Marketplace::getbyid(uint64_t productId) {
    productIndex products(_self, _self);

    auto iterator = products.find(productId);
    eosio_assert(iterator != products.end(), "Product not found");

    auto product = products.get(productId);
    print("Id: ", product.product_id);
    print(" | Name: ", product.name.c_str());
    print(" | Power: ", product.power);
    print(" | Health: ", product.health);
    print(" | Ability: ", product.ability.c_str());
    print(" | Level up: ", product.level_up);
    print(" | Quantity: ", product.quantity); 
    print(" | Price: ", product.price);
}
    
void Marketplace::add(account_name account, product newProduct) {
    require_auth(account);

    productIndex products(_self, _self);

    auto iterator = products.find(newProduct.product_id);
    eosio_assert(iterator == products.end(), "Product for this ID already exists");

    products.emplace(account, [&](auto& product) {
        product.product_id = newProduct.product_id;
        product.name = newProduct.name;
        product.power = newProduct.power;
        product.health = newProduct.health;
        product.ability = newProduct.ability;
        product.level_up = newProduct.level_up;
        product.quantity = newProduct.quantity;
        product.price = newProduct.price;
    });
}

    void Marketplace::update(account_name account, uint64_t product_id, uint64_t quantity) {
        require_auth(account);

        productIndex products(_self, _self);

        auto iterator = products.find(product_id);
        eosio_assert(iterator != products.end(), "Product not found");

        products.modify(iterator, account, [&](auto& product) {
            product.quantity += quantity;
        });
    }

    void Marketplace::remove(account_name account, uint64_t productId) {
        require_auth(account);

        productIndex products(_self, _self);

        auto iterator = products.find(productId);
        eosio_assert(iterator != products.end(), "Product not found");

        products.erase(iterator); 
    }

我们首先仔细研究一下第一个action—buy。这里有两个inline关键字修饰的action:

  • Inline Transfer — 将令牌从买家转移给合约
  • Inline Action — 从另外一个合约中调用一个action

为了获得自己想要的物品,player需要付出一些货币。 这是为什么我们使用inline transfer的原因,它帮助我们处理这个流程。然后第二个inline action将会被执行,这个action将该物品添加到player的inventory中。

# Create asset
# asset({amount}, string_to_symbol({precision}, {symbol}));
asset productPrice = asset(product.price, string_to_symbol(4, "OAS"));

# Do inline trasfer
# action({permission_level}, {contract_deployer}, {contract_action}, {data_to_pass}).send();
action(vector<permission_level>(), N(anorak), N(transfer), make_tuple(buyer, _self, productPrice, string(""))).send();

# Execute action from another contract
# action({permission_level}, {contract_deployer}, {contract_action}, {data_to_pass}).send();
action(vector<permission_level>(), N(anorak), N(additem), make_tuple(buyer, 
    product.product_id,
    product.name,
    product.power,
    product.health,
    product.ability,
    product.level_up
)).send();

在测试我们整个逻辑之前,我们需要首先创建货币。这是eosio.token 合约便利的地方。这个合约现在并不在我们的项目里,但是我们知道它在哪。转到eosio repo,然后在build文件夹中找到eosio.token.wasteosio.token.abi文件。 我们将会使用他们和我们的anorak账户一起来部署我们的合约。

部署eosio.token

cleos set contract anorak ./contracts/eosio.token/ ./contracts/eosio.token/eosio.token.wast ./contracts/eosio.token/eosio.token.abi
Reading WAST/WASM from ./contracts/eosio.token/eosio.token.wast...
Assembling WASM...
Publishing contract...
executed transaction: b6b035b7efe07666ed76456c923c14248d535ada064c5851139de7b7f248641a  8104 bytes  1124 us
#         eosio <= eosio::setcode               {"account":"anorak","vmtype":0,"vmversion":0,"code":"0061736d01000000017e1560037f7e7f0060057f7e7e7f7...
#         eosio <= eosio::setabi                {"account":"anorak","abi":"0e656f73

使用symbol OAS创建货币

cleos push action anorak create '{"issuer":"anorak","maximum_supply":"1000000.0000 OAS","can_freeze":"0","can_recall":"0","can_whitelist":"0"}' -p anorak

executed transaction: ed26901b1e7403ef275731e2fb8fcce2c7eb1966c27cd0537750eed8a393a263  120 bytes  316 us
#        anorak <= anorak::create               {"issuer":"anorak","maximum_supply":"1000000.0000 OAS"}

给wade和anorak 账户分发一些token

# Issue tokens to account anorak
cleos push action anorak issue '{"to":"anorak","quantity":"5000.0000 OAS","memo":""}' -p anorak
executed transaction: a16724e02577178dbf1c2fab5cad17f84d5e790a8ebf29dabdb273b4babd948c  120 bytes  497 us
#        anorak <= anorak::issue                {"to":"anorak","quantity":"5000.0000 OAS","memo":""}

#Issue tokens to account wade
cleos push action anorak issue '{"to":"wade","quantity":"5000.0000 OAS","memo":""}' -p anorak
executed transaction: ca11594c408def8fe7be5be7971b8264d4db6be941e743819f55f1c704b77f3c  120 bytes  1300 us
#        anorak <= anorak::issue                {"to":"wade","quantity":"5000.0000 OAS","memo":""}
#        anorak <= anorak::transfer             {"from":"anorak","to":"wade","quantity":"5000.0000 OAS","memo":""}
#          wade <= anorak::transfer             {"from":"anorak","to":"wade","quantity":"5000.0000 OAS","memo":""}

现在我们的Oasis dApp已经有了货币了,我们可以在Marketplace使用它。 让我们回到那里去吧,

我们将会创建一个新的账户,这个账户将会操作Marktetplace 智能合约,并部署它。创建这个账户然后将它命名为market。

部署Marketplace合约,然后在这之前别忘了执行 ⌘+I, ⌘+R 。现在是测试它的时候了:

# Magic ball
cleos push action market add '{"account":"market","newProduct":{"product_id":1,"name":"magic ball","power":120,"health":10,"ability":"see the future","level_up":3,"quantity":10,"price":150}}' -p market@active
executed transaction: effdd4505745f336170dfdd1ab0e4579b7b578fada0273617de1c8204c042208  176 bytes  542 us
#        market <= market::add                  {"account":"market","newProduct":{"product_id":1,"name":"magic ball","power":120,"health":10,"abilit...

cleos push action market getbyid '[1]' -p market
executed transaction: bd10a266a494919a578bc00cbaa73e0bbed52ad5d072fcca7b52d687c24e9367  104 bytes  618 us
#        market <= market::getbyid              {"productId":1}
>> Id: 1 | Name: magic ball | Power: 120 | Health: 10 | Ability: see the future | Level up: 3 | Quantity: 10 | Price: 150

# Elf's potion
cleos push action market add '{"account":"market","newProduct":{"product_id":2,"name":"elf’s potion","power":50,"health":300,"ability":"heal fast","level_up":0,"quantity":4,"price":350}}' -p market@active
executed transaction: ce5b3b104736f9a6940c322587adafc73d283d6ce6dca68b93f68a47f8c4f8af  176 bytes  493 us
#        market <= market::add                  {"account":"market","newProduct":{"product_id":2,"name":"elf’s potion","power":50,"health":300,"ab...

# Update potion quantity to 6
cleos push action market update '["market",2,2]' -p market
executed transaction: f6403545df115ff32018b96f2cc26458d4241979dcb71d261c5e9fdcdb93a8da  120 bytes  546 us
#        market <= market::update               {"account":"market","product_id":2,"quantity":2}

cleos push action market getbyid '[2]' -p market
executed transaction: 5fee346b1724b79313cbfb1c440967ed03f29cb349ae550a49c2815aadccb410  104 bytes  494 us
#        market <= market::getbyid              {"productId":2}
>> Id: 2 | Name: elf’s potion | Power: 50 | Health: 300 | Ability: heal fast | Level up: 0 | Quantity: 6 | Price: 350

# Orb of Osuvox
cleos push action market add '{"account":"market","newProduct":{"product_id":3,"name":"Orb of Osuvox","power":5000,"health":300,"ability":"","level_up":0,"quantity":1,"price":3250}}' -p market@active
executed transaction: 214256d25856875c7e7fd6c1fb07e429ddf792480f38e7ee1a70a77ca86a35b0  168 bytes  518 us
#        market <= market::add                  {"account":"market","newProduct":{"product_id":3,"name":"Orb of Osuvox","power":5000,"health":300,"a...

超棒的物品!让我们买一个吧Parzival决定他需要一个elf’s potion, 所以他买了一个:

# Buy product from Marketplace
cleos push action market buy '["wade",1]' -p wade
executed transaction: 804d430747459c309a9489ecd0f1c155f819788a0b270bcbf299d1222850b1dc  112 bytes  2253 us
#        market <= market::buy                  {"buyer":"wade","productId":1}
#        anorak <= anorak::transfer             "000000004073e934000000006405af919600000000000000044f41530000000000"
#        anorak <= anorak::additem              {"account":"wade","purchased_item":{"item_id":1,"name":"magic ball","power":120,"health":10,"abili...
>> Item Id: 1 | Name: magic ball | Power: 120 | Health: 10 | Ability: see the future | Level up: 3

在我们测试我们的player之前,首先更新getplayer action,将下面代码块添加到这个action的最后:

if (currentPlayer.inventory.size() > 0) {
    print(" Items: ");

    for (uint32_t i = 0; i < currentPlayer.inventory.size(); i++) {
        item currentItem = currentPlayer.inventory.at(i);
        print(currentItem.name.c_str(), " == ");
    }
} else {
    print(" Empty inventory");
}

现在测试一下player吧:

cleos push action anorak getplayer '["wade"]' -p wade@active
executed transaction: 170ef809509f1110b4d56fb910e6b946b1500b7f1fbb0825435596a06068f76b  104 bytes  835 us
#        anorak <= anorak::getplayer            {"account":"wade"}
>> Username: parzival Level: 4 Health: 1010 Energy: 1120 Abilities: see the future  Items: magic ball ==

你通过了测试,在第二部分的结尾,我们的建议是花时间理解这里我们做的所有事情然后自己实现一遍。在第三部分中我们将会创建一些game,我们的player可以玩这些game并且获得新的OAS。