前文 《EOS智能合约开发点滴记录-第一篇开发环境搭建》
开发合约前,我们先选择下将要用的编辑工具
我常用的有 clion 和vscode,电脑os为mac,不过其他系统差别不大,如果你习惯用于Windows,那建议选择 Windows Subsystem for Linux,不建议用其他三方封装的工具,以免更新不及时,或者不兼容,导致生产上出问题.
下面我们讲用以vscode 做演示.
配置智能合约项目
先用vscode打开eosio.cdt项目代码目录,按提示安装相应的扩展插件.一般只需要安装个 c/c++
等vscode加载索引完,会根据eosio.cdt项目下的cmakelists.txt 配置好开发环境及其include路径.
所以我一般习惯是直接将自己的智能合约项目直接clone 在 eosio.cdt/examples
目录下,省的自己做相关依赖配置.
我们先假定一个智能合约项目,项目名为bcskill.game
(项目名与合约名一致,省的后面弄混)
新建bcskill.game目录(目录名也要与合约名一致,方便后面部署),路径为eosio.cdt/examples/bcskill.game
目录结构如下
eosio.cdt/examples/bcskill.game
eosio.cdt/examples/bcskill.game/common //存放一些公用的源码文件
eosio.cdt/examples/bcskill.game/bcskill.game.hpp //智能合约头文件
eosio.cdt/examples/bcskill.game/bcskill.game.cpp // 智能合约源文件
eosio.cdt/examples/bcskill.game/README.md // 帮助文档
习惯的代码结构
bcskill.game.hpp
#pragma once
//一些常用的头文件依赖 根据实际所需添加
#include <eosiolib/eosio.hpp>
#include <eosiolib/time.hpp>
#include <eosiolib/transaction.hpp>
#include <eosiolib/asset.hpp>
#include <eosiolib/crypto.h>
namespace bcskillgame // 为自己的项目创建单独的命名空间
{
// 一些用到的命名空间 根据实际需要添加
using eosio::time_point;
using eosio::microseconds;
using eosio::name;
using namespace std;
// 定义下一些用到的宏,比如系统EOS代币符号
#define EOS_SYMBOL symbol(symbol_code("EOS"),4)
一些内部会用到的结构体
struct play_hero_info{
uint8_t hero_id;
uint8_t hero_grade;
uint8_t count;
};
...
// 定义所需的table表
struct [[eosio::table("system"), eosio::contract("bcskill.game")]] system{
uint64_t id;
bool upgrading; //升级或维护中,暂时停止合约内所有的业务
time_point zero_time;
auto primary_key() const { return id; }
};
// `eosio::table("system")`中的system为合约内table的名字,后面可以用 get table 这个名字查看链上此table数据
// eosio::contract("bcskill.game")]] 中的 "bcskill.game" 为合约的名字,也就是我们之前定好的,也就是说这个表属于"bcskill.game" 合约
...
// 为上面定义好的table 创建实例化对象
typedef eosio::multi_index<"system"_n, system> system_tables;
// multi_index<"system" 中的 system 为table的表名, 第二个system 为表的定义名.system_tables为实例化后的对象名.
//下面开始创建合约类
class [[eosio::contract("bcskill.game")]] bcskill_contract : public eosio::contract // 从系统合约对象继承下
{
public:
using eosio::contract::contract; // 引入父命名空间
// 创建合约action接口 后面可以用push action调用
ACTION upgrading(bool upgrading);
...
private:
// 添加一些私有的数据类型
// 一些整数类型可以用 enum 枚举
enum GAME_STATUS_TYPE{
GAME_STATUS_PADDING = 1,
GAME_STATUS_RUNNING,
GAME_STATUS_FINISHED
};
// 如果是小数,可以用class
class RACE_PWOER_TYPE{
public:
static constexpr auto RACE_PWOER_ORC_HUM_TA = 0.3;
static constexpr auto RACE_PWOER_UD_VS_HUM_ORC_TA = 0.3;
static constexpr auto RACE_PWOER_HUM_ORC_TA_VS_NE = 0.3;
static constexpr auto RACE_PWOER_NE_VS_UD = 1.3;
};
// 合约内不建议使用小数操作,一些场景可以先统一增大倍数,转为整数
class UPDATE_RATE_TYPE{
public:
static constexpr auto PROBABILITY_ACCURACY = 100;
static constexpr auto UPDATE_RATE_1_SUCCESS = 0.7 * PROBABILITY_ACCURACY;
...
};
// 定义写私有的方法,合约内部使用,这里的方法不会被 push action调用到
bool is_upgrading();
};
}
bcskill.game.cpp
#include "bcskill.game.hpp" //引入头文件
// 引入一些其他的头文件,比如
#include "common/utils.hpp"
// 引入所需的命名空间
using namespace bcskillgame;
using namespace eosio;
// 为头文件中各个action添加实现方法
void bcskill_contract::upgrading(bool upgrading){
require_auth( _self.value );
system_tables system_table(_self, _self.value);
auto itr = system_table.begin();
if(itr == system_table.end()){
system_table.emplace( _self, [&]( auto& s ) {
s.id = system_table.available_primary_key();
s.upgrading = upgrading;
});
}else{
system_table.modify( itr, _self, [&]( auto& s ) {
s.upgrading = upgrading;
});
}
}
...
// 一些私有方法实现
bool bcskill_contract::is_upgrading(){
bool result = false;
system_tables system_table(_self, _self.value);
auto itr = system_table.begin();
if(itr != system_table.end()){
result = itr->upgrading;
}
return result;
}
....
// 为action 申明调用
extern "C" {
[[noreturn]] void apply(uint64_t receiver, uint64_t code, uint64_t action) {
if(code=="eosio.token"_n.value && action=="transfer"_n.value) {
execute_action( name(receiver), name(code), &bcskill_contract::transfer);
}
else if(code==receiver){
switch(action)
{
EOSIO_DISPATCH_HELPER( bcskill_contract, (upgrading) //只有添加后,action才能被外部 push action
default:
eosio_assert(false, "it is not my action"); // 为安全,防止被恶意调用,影响合约响应
break;
}
}
eosio_exit(0);
}
};
此时合约的基本代码结构已完成
编译合约
进入代码目录执行
eosio-cpp -o bcskill.game.wasm bcskill.game.cpp --abigen
执行完毕后,会生成
bcskill.game.abi 和 bcskill.game.wasm
我们可以简单的理解为 abi 为(.h)头文件,wasm 为dll或so 库.
我们想执行某个账号下的合约时,先会获取这个合约的abi信息,也就是先获取合约内所有的action接口,然后根据所指定的接口在发起交易,执行合约内对应的逻辑.
合约部署
cleos -u https://api.eoslaomao.com set contract bcskillsurou ../bcskill.game/ -p bcskillsurou
合约执行
cleos -u https://api.eoslaomao.com push action bcskillsurou upgrading '{"upgrading": 1}' -p bcskillsurou
查看链上table
cleos -u https://api.eoslaomao.com get table bcskillsurou bcskillsurou system
类似返回数据如下
{
"rows": [{
"id": 0,
"upgrading": 1,
"zero_time": "2019-03-30T16:00:00.000"
}
],
"more": false
}
建议
对于数据类型的选取,编写前,最好专门花时间确定下,避免不必要的RAM浪费或者后面数据溢出.
比如uint8_t 到 uint64_t的选取,如果小于255 就用 uint8_t节约内存
对于一些数据table的划分,最好能实现评估下,如果前端没有全局(_self)查找或排序要求,创建对应合适的scope下,降低find时的消耗.
尽量避免后面做数据迁移.迁移会存在一些人为的操作失误风险,以及期间可能需要暂停dapp,影响用户体验.
本文结束,全文演示了我目前习惯的合约目录及代码结构,以及基本的合约使用,后面有时间再补充,如果大家有什么更好的方法或者建议可以留言,一起学习~