为了帮助EOS区块链开发社区,Infinite X Labs的开发团队决定运行一系列专门用于EOS开发的教程。在上一个教程中,我们学习了如何设置用于构建EOS dApp的开发环境。我们已经启动了我们的本地testnet,部署了一个EOS智能合约Demo并与之进行了互动。你可以找到所有从我们的EOS Blockchain开发系列教程的第一篇这里。
现在是时候升级了。如果不创建EOS dApp,学习如何构建EOS dApp的最佳方法是什么?这就是我们要做的。通过开发,您将学到很多新东西,在这4个部分教程的最后,您将能够创建您的dApp。在系列的最后将与你分享一些非常有趣的东西,但就目前来说,这是一个惊喜?
请注意,EOSIO仍处于开发阶段,未来某些步骤可能会发生变化。但是,我们的团队将尽力保持教程始终是最新的。
你可以在GitHub中找到这个部分的所有源代码
上次更新 - 2018年11月25日 - 本教程使用最新版本的EOSIO - v1.4.4进行了更新
让我们开始吧!
在我们开始使用代码之前,我们首先需要一个想法。你看过史蒂文斯皮尔伯格的《头号玩家》电影吗?如果看过,那你已经知道绿洲是什么了!令人兴奋,对吧?让我们想象一下,我们是团队的一员,他们将构建下一个Oasis,我们的任务是将区块链技术集成到其中,或者至少表明可以完成。我们要做的是一个小型dApp,它将拥有玩家。每个玩家都可以玩游戏,他可以从中接收硬币。通过硬币,他可以去市场购买物品,这将给予他力量,能力,健康和/或升级。当然还有Oasis合约 - 我们的主要起点。
为了能够开发EOS dApp,您需要具有C/C++的先前背景,因为这是用于EOS智能合约的编程语言
一个IDE,您可以在其中编写智能合约。我们正在使用带有C/C++扩展的VS Code,但您可以选择最适合您的方式。**CLion**也是一个非常好的选择
当然,您需要一个本地testnet节点,如果您不知道如何构建一个节点。**在此处**查看我们上一个教程的帮助。
在编写我们的第一份合同之前,我们需要设置一些在开发过程中需要的东西。
# 要默认情况下将合约的输出打印到控制台,请添加:
# --contracts-console
nodeos -e -p eosio --plugin eosio::wallet_api_plugin --plugin eosio::chain_api_plugin --plugin eosio::history_api_plugin --contracts-console
它将存储我们在签署交易时将使用的密钥
# 1. 创建名为“oasis”的新钱包
cleos wallet create -n oasis
# 2. 生成两对密钥(使用命令两次)
cleos create key
# 3. 在钱包中导入生成的私钥(您需要在末尾指定钱包)
# {private_key_1} - 这将是OwnerKey
# {private_key_2} - 这将是ActiveKey
cleos wallet import --private-key={private_key_1} -n oasis
cleos wallet import --private-key={private_key_2} -n oasis
# 4. 将“eosio”帐户的私钥添加到您的钱包中
# 注意: 如果您收到3090003错误: Provided keys, permissions, and delays do not satisfy declared authorizations
# 您可能应该将“eosio”帐户的私钥添加到您的钱包中。
cleos wallet import --private-key=5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3 -n oasis
注意:不要忘记保存密钥和钱包密码
EOS.IO智能合约在一个帐户上运行。因此,需要一个帐户来转移或以其他方式将交易推送到区块链。让我们将我们的帐户命名为“ anorak ”。听起来很熟悉?
# Create the account using the public keys
cleos create account eosio anorak {public_OwnerKey} {public_ActiveKey}
# "eosio" is the name of the account who will create the new one
# "anorak" is the name of the new account
是时候开始开发我们的dApp了!创建名为Oasis的项目文件夹。在里面添加两个主要的子文件夹 - contracts和tests。是的,我们也要编写测试。将有四个子文件夹名为:Players, Games, Marketplace, Oasis - 创建它们。
Players将成为我们的首个EOS智能合约。每个Players将拥有用户名,等级,健康点,能量点,平衡,库存(充满物品)和能力。他将能够从市场购买物品,这些物品将被添加到他的库存,健康点,能量点和/或能力中。为了获得金币,他需要与Oasis的其他玩家一起玩游戏
在Players文件夹中创建Players.hpp和Players.cpp文件
1. Players.hpp是头文件,包含**.cpp**文件引用的变量,常量和函数。
2. Players.cpp文件是包含合同功能的源文件。
让我们深入了解EOS合同的基本结构
#include
#include
#include
注意:我们现在只在Players.hpp中添加,因为我们还不会在其他合约中使用它。
在合约开始时,我们设置了引入我们将要使用的所有内容。在我们的案例中,Players.hpp已经拥有它们,所以它足以在我们的合约实现中只包含标题。大多数时候,当你使用标题时,你会这样做。
将所有内容包装在一个名称空间中是一个很好的做法,因为它在这里用Oasis显示。
如果您熟悉C++,您可能知道为什么在类实现之前我们有两个using子句。对于其他人 - 当我们想要从C++中的eosio命名空间调用一个动作时,我们必须这样调用它:eosio::some_action()以避免在我们简单的动作名称前面每次添加eosio::。 在开头添加using namespace eosio。
#include "Players.hpp"
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(const account_name account, string& username, uint64_t level) {
}
//@abi action
void update(const 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;
uint64_t health_points = 1000;
uint64_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 playerIndex;
};
EOSIO_ABI(Players, (add)(update)(getplayer))
}
该Players类继承了“contract”智能合同,并使用它的构造函数(using contract::contract)。
在开发EOS dApp时,您应该知道的一件重要事情是智能合约以动作和共享内存数据库访问的形式相互通信。合同可以读取另一个合同的数据库的状态,只要它包含在具有异步vibe的事务的读取范围内。这可以通过使用两种通信模式之一来实现 - 内联或延迟。您可以将它们视为同步和异步
从EOS.IO文档:
在我们合同的类主体中,有两种类型的访问修饰符 - public & private。在public是构造函数和所有操作。动作表示智能合约中的单个操作。在我们的合约中,我们有add, update & getplayer。我们稍后会看一下它们。与此同时,您应该已经注意到在每次操作之前我们都有“ //@abi action ”。它是eosiocpp脚本的指示标志,它将为我们的智能合约生成**.abi**文件。
在private部分,我们保留了我们不希望从Players合同之外访问的所有内容。这里我们正在初始化multi_index。它是什么以及为什么需要它?
//@abi table player i64
struct player {
uint64_t account_name;
string username;
uint64_t level;
uint64_t health_points = 1000;
uint64_t energy_points = 1000;
uint64_t primary_key() const { return username; }
EOSLIB_SERIALIZE(player, (account_name)(username)(level)(health_points)(energy_points))
};
typedef multi_index playerIndex;
正如我们所说,一个action代表了智能合约中的单一操作。每个操作都在其自己的环境中运行,称为操作上下文。上下文提供了执行操作所需的一些内容。其中一件事是action的工作memory。这是action保持其state的地方。在处理action之前,EOSIO会为操作设置一个干净的工作memory。在新action的上下文中,在执行另一个操作时可能已设置的变量。在操作之间传递状态的唯一方法是将其持久化并从EOSIO数据库中检索它。
这可以通过多索引实现,它允许我们在EOSIO数据库中读取和修改持久状态。
在我们的合同中,我们已经为多索引表声明了一个名为player的对象模板。重要的是要注意,当我们为表创建模板时,我们还需要添加primary_key。我们使用account_name,因为我们想要每个帐户是一个player。
我们还有一个eosiocpp脚本的指示标志- “ //@abi table player i64 ”。这说明我们表的名称是player,使用的索引类型是i64。
一旦对象准备就绪,我们需要使用以下模板键入我们的多索引:
// typedef multi_index multi_index_name;
typedef multi_index playerIndex;
您可能想知道EOSLIB_SERIALIZE和EOSIO_ABI是什么。
EOSLIB_SERIALIZE(player, (account_name)(username)(level)(health_points)(energy_points))
EOSIO_ABI(Players, (add)(update)(health)(energy)(getplayer))
这些是C++宏。EOSIO_ABI封装了apply方法的逻辑。apply是action处理程序,它监听所有传入的action并根据函数中的规范做出反应。宏的结构非常简单。第一个参数是类型(当前类的名称),下一个参数是下面示例中列出的所有action
// EOSIO_ABI(class_name, (action_1)(action_2)(action_3)...(action_n))
该EOSLIB_SERIALIZE宏提供了序列化和反序列化方法,使action可以在合约和nodeos系统之间来回传递。
// EOSLIB_SERIALIZE(struct_name, (property_1)(property_2)(property_3)...(property_n))
现在让我们仔细看看每个action及其实现。
第一个是“add”。它负责在Oasis中创建一个新玩家。
//@abi action
void add(const account_name account, string& username) {
/**
* We require that only the owner of an account can use this action
* or somebody with the account authorization
*/
require_auth(account);
/**
* We access the "player" table as creating an object of type "playerIndex"
* As parameters we pass code & scope - _self from the parent contract
*/
playerIndex players(_self, _self);
/**
* We must verify that the account doesn't exist yet
* If the account is not found the iterator variable should be players.end()
*/
auto iterator = players.find(account);
eosio_assert(iterator == players.end(), "Address for account already exists");
/**
* We add the new player in the table
* The first argument is the payer of the storage which will store the data
*/
players.emplace(account, [&](auto& player) {
player.account_name = account;
player.username = username;
player.level = 1;
player.health_points = 1000;
player.energy_points = 1000;
});
}
实现action有4个主要关键点:
下一个action是“update”。它负责更新玩家的等级,健康和能量点。该实现与“add”操作非常相似,只是我们正在修改player状态,而modify函数将iterator作为第一个参数。对该iterator所指向的对象进行更新
//@abi action
void update(account_name account, uint64_t level, int64_t healthPoints, int64_t energyPoints) {
require_auth(account);
playerIndex players(_self, _self);
auto iterator = players.find(account);
eosio_assert(iterator != players.end(), "Address for account not found");
/**
* We add the new player in the table
* The first argument is the payer of the storage which will store the data
*/
players.modify(iterator, account, [&](auto& player) {
player.level = level;
if ((player.health_points - healthPoints) < 0) {
player.health_points = 0;
} else {
player.health_points -= healthPoints;
}
if ((player.energy_points - energyPoints) < 0) {
player.energy_points = 0;
} else {
player.energy_points -= energyPoints;
}
});
}
最后一个action是“ getplayer ”,打印有关player的信息。它没有require_auth函数,因为任何人都可以调用它。
//@abi action
void getplayer(const account_name account) {
playerIndex players(_self, _self);
auto iterator = players.find(account);
eosio_assert(iterator != players.end(), "Address for account not found");
/**
* The "get" function returns a constant reference to the object
* containing the specified secondary key
*/
auto currentPlayer = players.get(account);
print("Username: ", currentPlayer.username.c_str(), " Level: ", currentPlayer.level, " Health: ", currentPlayer.health_points, " Energy: ", currentPlayer.energy_points);
}
我们确实做了很多事情,现在是时候在区块链上部署我们的智能合约了。首先,我们需要生成.wast和.abi文件。我们将使用eosiocpp脚本来完成它。转到Players文件夹并在终端中运行以下命令:
eosiocpp -o Players.wast Players.cpp
该命令将生成我们合同的Web程序集。
接下来,当您在同一目录中时,运行此命令以生成Players.abi。
eosiocpp -g Players.abi Players.cpp
现在,当我们有Players.wast&Players.abi,现在使用它们来部署我们的合同。
# 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
Reading WAST/WASM from ./contracts/Players/Players.wast...
Assembling WASM...
Publishing contract...
executed transaction: 2187a85a5e80cbd2c0e6715b6847c95cbb5da5df7ed1171be87709507678795d 6472 bytes 6582 us
# eosio <= eosio::setcode {"account":"anorak","vmtype":0,"vmversion":0,"code":"0061736d010000000182011560037f7e7f0060057f7e7e7...
# eosio <= eosio::setabi {"account":"anorak","abi":"0e656f73696f3a3a6162692f312e300002046974656d0006076974656d5f69640675696e7...
真棒!您已经部署了第一份EOS合同。现在是时候测试我们的action了。我们先添加一个新用户。您可以使用push action命令触发操作:
# cleos push action {account} {action_name} '{data}' -p {account}@active
cleos push action anorak add '["anorak","art3mis"]' -p anorak@active
executed transaction: 00ea5aa48e50c33737b253ac24cb6bef3f296399e9a7e61b81044a852bb5c709 112 bytes 3015 us
# anorak <= anorak::add {"account":"anorak","username":"art3mis"}
注意:使用最新版本的EOSIO,您应该在推送任何操作之前始终添加权限标志。
让我们看看player是否是使用“ getplayer ”来创建的
# cleos push action {account} {action} {data}
# account - The account providing the contract to execute
# action - The action to execute on the contract
# data - The arguments to the contract
cleos push action anorak getplayer '["anorak"]' -p anorak@active
executed transaction: 759ba984c4f9216d79fc473757a398ee6248c043ab6596cd8336a08512a4f465 104 bytes 2443 us
# anorak <= anorak::getplayer {"account":"anorak"}
>> Username: art3mis Level: 1 Health: 1000 Energy: 1000
一切看起来都很完美。
现在我们将更新我们的player,并将再次使用“ getplayer ”action来查看我们是否已成功:
cleos push action anorak update '["anorak",4,75,110]' -p anorak@active
executed transaction: 905c847178d47ca8a230fdf2ba1c92c42529aeebb118280fdbf9ccb1415992de 128 bytes 748 us
# anorak <= anorak::update {"account":"anorak","level":4,"healthPoints":75,"energyPoints":110}
cleos push action anorak getplayer '["anorak"]' -p anorak@active
executed transaction: 31bf65b2d69f0977474ddd0332faddf5495cf7050d4ad7d5e678fbd4f7ad4eea 104 bytes 5654 us
# anorak <= anorak::getplayer {"account":"anorak"}
>> Username: art3mis Level: 4 Health: 925 Energy: 890
我们的播放器已成功更新。做得好!
我们到此为止。我们今天做了很多新事物。花点时间了解我们所做的一切,并尝试自己做。?
到目前为止,我们已经介绍了EOS智能合约的基础知识。在下一部分中,我们将再次升级。我们将扩展玩家合同,我们将创建剩余的合同(市场,绿洲和一些游戏),然后建立他们之间的联系。当然,我们会添加一些单元测试。一旦你学会了基础知识,这将会容易得多。
检查最终目的-端EOS DAPP开发教程-第2部分 在这里。
原文链接