在我们开始使用代码之前,我们首先需要一个想法。你看过Steven Spielberg的“Ready Player One”电影吗?如果你确实已经知道了绿洲是什么!精彩,对吧?让我们想象一下,我们是将要建立下一个Oasis的团队的一部分,我们的任务是将区块链技术集成到其中,或者至少表明可以完成。我们要做的是一个小型的dApp,它会有玩家。每个玩家都可以玩游戏,并且可以从中获得硬币。随着硬币,他可以去市场购买物品,这会给体力,能力,生命 ,升级。当然,Oasis合同是我们的主要起点。
开发前准备
为了能够开发EOS dApps,您需要使用C / C ++的背景,因为这是用于EOS Smart Contracts的编程语言
- 会使用C / C ++ - ✓
一个IDE,您可以在其中编写智能合约。我们使用VS Code和C / C ++扩展,但是你可以选择最适合你的。CLion也是一个非常不错的选择
- IDE - **Visual Studio代码 ** - ✓
当然,你需要一个本地testnet节点,但你知道如何构建一个。
- 一个工作的本地测试网络节点 - ✓
配置开发环境
在编写我们的第一份合同之前,我们需要在开发过程中设置一些我们需要的东西。
步骤1 - 启动节点
nodeos -e -p eosio --plugin eosio :: wallet_api_plugin --plugin eosio :: chain_api_plugin --plugin eosio :: account_history_api_plugin --resync
步骤2 - 创建一个电子钱包
它将存储我们在签署交易时使用的密钥
# 1. Create new wallet with the name "oasis"
cleos wallet create -n oasis
# 2. Generate two pair of keys (use the command twice)
cleos create key
# 3. Import the generated private keys in the wallet(you need to specify the wallet at the end)
# {private_key_1} - This will be the OwnerKey
# {private_key_2} - This will be the ActiveKey
cleos wallet import {private_key_1} -n oasis
cleos wallet import {private_key_2} -n oasis
# 4. Add the private key of the "eosio" account into your wallet
# Note: With the latest version of EOSIO the private key is automatically added to your wallet
cleos wallet import 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。是的,我们也要写tests。将会有四个子文件夹:Players, Games, Marketplace , Oasis。
Player智能合约
玩家将成为我们的第一个EOS智能合约。每个玩家将拥有用户名,等级,生命值,能量值,金币,背包和天赋。他将能够用金币从商店购买物品,并将其添加到他的背包,生命值,能量值或天赋中。为了获得金币,他需要与绿洲的其他玩家一起玩游戏
在Players文件夹中创建Players.hpp和Players.cpp文件
Player.hpp是包含由.cpp文件引用的变量,常量和函数的头文件。
Player.cpp文件是包含合同功能的源文件。
我们来深入看一下EOS合约的基本结构
Players.hpp
#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>
#include <string>
Players.cpp
在合约开始时,我们设置了我们要使用的所有包含。在我们的例子中,Players.hpp已经拥有了它们,所以在我们的合同实现中只包含头文件就足够了。大多数情况下,当你使用头文件时你会这样做。
将所有内容都包装在名称空间中是一种很好的做法,因为它与Oasis一起显示在这里。
#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) {
}
//@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<N(player), player> playerIndex;
};
EOSIO_ABI(Players, (add)(update)(getplayer))
}
该Players 继承contract ,并使用它的构造函数(using contract::contract)。
在开发EOS dApp时,您应该了解的一件重要事情是,智能合约以操作和共享内存数据库访问的形式相互通信。一个合同可以读取另一个合同数据库的状态,只要它包含在具有异步可达的事务的读取范围内。这可以通过使用两种通信模式之一来实现 - inline 和deferred。您可以将它们视为同步和异步
从EOS.IO文档:
inline - 内联保证与当前交易函数一起执行或展开; 无论成功或失败,都不会收到任何通知。内联与原始交易函数具有相同的范围和权限
deferred - 延期将在稍后由生产者自行决定; 可以传达通信结果或者可以简单地超时。延期可以延伸到不同的范围,并承担发送它们的合同的权力。
在我们合同的类体中,有两种类型的访问修饰符 - public和private。在公共部分是构造函数和所有动作。一个动作表示智能合约中的单个操作。在我们的合同中,我们add,update和getplayer。我们稍后会看看他们。与此同时,您应该已经注意到,在每个函数之前,我们都有“ // @ abi action ”。它是eosiocpp脚本的指示标志,它将为我们的智能合约生成.abi文件。
在private中,我们保存我们不希望从Player合约外部访问的所有内容。这里我们正在初始化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<N(player), player> playerIndex;
Actions 实现
add 负责创建一个玩家
/@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;
});
}
update 负责更新玩家level, health and energy points . 实现和add相似,只是在更新玩家状态时第一个参数时迭代器
//@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;
}
});
}
getplayer 是一个查询操作,任何人都可以访问 ,不需要认证
//@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);
}
Player智能合约部署
生成wast和abi
//生成wast
eosiocpp -o Players.wast Players.cpp
//生成abi
eosiocpp -g Players.abi Players.cpp
部署
# 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...
Assembling WASM...
Publishing contract...
executed transaction: b3fd7968d08f3e1cece14fed9632c8c678e89f5832b356a3c4c651aa4d3fe159 5668 bytes 10000 cycles
# eosio <= eosio::setcode "000000004073e9340000cf590061736d0100000001cf011d60037f7e7f0060057f7e7e7e7e0060027f7e0060000060057e7...
# eosio <= eosio::setabi "000000004073e934000406706c6179657200050c6163636f756e745f6e616d650675696e74363408757365726e616d65067...
测试
创建一个玩家
# cleos push action {account} {action_name} '{data}' -p {account}@active
cleos push action anorak add '["anorak","art3mis"]' -p anorak@active
executed transaction: 9b099bd072d090424bff9cff3e0473a2ccde98d3554cbffee2392172e4221055 252 bytes 1000 cycles
# anorak <= anorak::add {"account":"anorak","username":"art3mis"}
查询玩家
# 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"]'
executed transaction: 5ecdf264952837e61671b31e935eec6365af8c6430f00f3eafd274e7ca81910d 156 bytes 1000 cycles
# anorak <= anorak::getplayer {"account":"anorak"}
>> Username: art3mis Level: 1 Health: 1000 Energy: 1000
更新玩家状态
cleos push action anorak update '["anorak",4,75,110]' -p anorak@active
executed transaction: 0da802b784e9bc2d56d84582e269eae213f6255ca2bbe83c32b6baf9fafc1f54 268 bytes 1000 cycles
# anorak <= anorak::update {"account":"anorak","level":4,"healthPoints":75,"energyPoints":110}
验证更新
cleos push action anorak getplayer '["anorak"]' -p anorak@active
executed transaction: a82c8356aaaea39ddb544d0fc5486f567c4bbdbb887b752aaf21ff0dd9ee8772 156 bytes 1000 cycles
# anorak <= anorak::getplayer {"account":"anorak"}
>> Username: art3mis Level: 4 Health: 925 Energy: 890
总结
from:https://infinitexlabs.com/eos-development-tutorial-part-1/