# 快速开始
# 入门指导
本教程的目的是为GXChain智能合约开发提供指导,包括合约开发、部署、调试以及常见错误归类等相关内容。
GXChain智能合约采用C++语言编写,通过Webassembly (opens new window)虚拟机编译后部署在GXChain区块链网络上。编译后的智能合约主要包括abi文件与wast文件,abi文件是合约定义的接口文件,wast文件为Webassembly虚拟机执行的字节码文件。GXChain使用WebAssembly虚拟机来支持用多种语言(如C ++和TypeScript)编写的智能合约。
开发智能合约之前,你需要做如下准备:
- 对C++语言开发有一定程度的了解
- 对Linux、Mac系统命令有一定程度的了解
- 在本地编译源码,启动本地私链或连接到测试网络(源码编译教程点这里 (opens new window))
相关术语:
- Action:智能合约提供的外部接口,可以与前端进行交互,用户调用的
Action
记录在区中。 - Table:智能合约提供持久化存储,类似于数据库中的表,支持多个索引,存储在合约帐户对象下。
工具介绍:
- witness_node:节点程序,可以根据不同的配置启动不同的功能。例如,在开发合约过程中,启动RPC端口与cli_wallet交互,用来调用合约查询table
- cli_wallet:命令行钱包程序,主要用于管理钱包并与witness_node程序交互,命令包括:部署合约、调用合约、更新合约、查询table等(使用
help
命令和gethelp
命令查看该工具的使用)。 - gxx:用于将
C++
源码文件编译为abi文件和wasm文件以部署到GXChain。 - gxc-smart-contract-ide: 通过智能合约IDE,可以编写、编译、部署、调用智能合约。 点击下载 (opens new window)
# 1. 启动本地私链
编译完成后,切换到witness_node程序所在目录,使用如下命令启动本地出块节点,data保存配置信息、生成的区块信息等。 启动本地私链教程点击这里
./witness_node -d data
启动后大致如下图所示(请记录下Chain ID,cli_wallet连接时会使用到):
出块节点运行之后,切换到cli_wallet目录,运行如下命令,启动cli_wallet客户端与出块节点交互,包括创建账号、部署合约、调用合约等功能,均可通过cli_wallet客户端进行测试。(chain-id 切换为自己的id)
./cli_wallet -sws://localhost:11011 --chain-id=679beed54a9081edfd3ede349a0aa1962ea2dc9d379808fecce56226cb199c84
启动后大致如下图所示:(初次启动显示为new)
# 2. 创建一个新钱包
首先你需要为你的钱包创建一个新的密码,钱包密码用来解锁你的钱包。在教程中我们使用如下密码:supersecret
你也可以使用字母和数字的组合来创建属于你的密码。请输入如下命令创建:
>>> set_password supersecret
现在你可以解锁你新建的钱包了:
unlock supersecret
# 3. 申领初始余额
资产账户包含在钱包账户中, 要向你的钱包中添加钱包账户, 你需要知道账户名以及账户的私钥。
在例子中,我们将通过import_key
命令向现有钱包中导入my-genesis.json中初始化的nathan
帐户:
import_key nathan 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
提示
nathan
在初始文件中会被用于定义账户名, 如果你修改过my-genesies.json
文件,你可以填入一个不同的名字。并且,请注意5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
是定义在config.ini
内的私钥
现在我们已经将私钥导入进钱包, my-genesis.json中初始化的余额,需要通过import_balance
命令来申领,无需申明费用:
import_balance nathan ["5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"] true
你可以通过以下命令来检视你的账户:
get_account nathan
用以下命令获取账户余额:
list_account_balances nathan
# 4. 创建账户
现在我们将创建一个新的账户alpha
,这样我们可以在 nathan
和alpha
两个账户中来回转账了。
通常我们用一个已有账户来创建新账户,因为登记员需要缴纳注册费用。 并且,登记员的账户需要进入Also, there is the requirement lifetime member (LTM)状态。因此我们必须在创建新账户前,先将账户nathan
升级到LTM状态, 使用upgrade_account
命令来升级账户:
upgrade_account nathan GXC true
返回的信息中,在membership_expiration_date
边上你会发现2106-02-07T06:28:15
。 如果你看到1970-01-01T00:00:00
,说明之前的操作出现了错误,nathan
没能成功升级。
成功升级后,我们可以通过nathan
来注册新账户,但首先我们需要拥有新账户的公钥。通过使用suggest_brain_key
命令来生成公私钥对:
// 生成秘钥对
suggest_brain_key
然后调用register_account / register_account2接口创建新帐户
register_account alpha GXC6vQtDEgHSickqe9itW8fbFyUrKZK5xsg4FRHzQZ7hStaWqEKhZ GXC6vQtDEgHSickqe9itW8fbFyUrKZK5xsg4FRHzQZ7hStaWqEKhZ nathan nathan 10 true
使用transfer3命令转移部分资产到账户
transfer3 nathan alpha 1000 GXC test GXC true
使用如下命令查看资产余额:
list_account_balances alpha
# Hello World合约简介
在阅读本篇教程之前,假定您已经阅读完了入门指导
# 1. 功能简介与部署调用
# 1.0 合约功能
Hello World合约 (opens new window)是一个最精简的GXChain合约,通过分析该合约,我们可以掌握智能合约开发的基本框架。该合约实现了一个hi action
,action是合约给外部调用提供的接口,功能为打印两次hi user(user为调用action的参数)
字符串到控制台,输出结果如下
# 1.1 编译合约
智能合约编写完成后包括xxx.hpp文件和xxx.cpp文件,需要编译为xxx.wast文件和xxx.abi文件,才能部署到区块链上。您可以使用GXChain提供的gxx工具,编译wast和abi文件,该工具可以在目录~/gxb-core/build/tools/gxx
找到。您可以切换到gxx所在目录,然后使用如下命令进行编译:
//编译wast文件,路径需要替换成你自己的
./gxx -o /Users/zhaoxiangfei/code/gxb-core/contracts/examples/helloworld/helloworld.wast /Users/zhaoxiangfei/code/gxb-core/contracts/examples/helloworld/helloworld.cpp
//编译abi文件,路径需要替换成自己的
./gxx -g /Users/zhaoxiangfei/code/gxb-core/contracts/examples/helloworld/helloworld.abi /Users/zhaoxiangfei/code/gxb-core/contracts/examples/helloworld/helloworld.hpp
# 1.2 部署合约
您可以使用如下命令部署Hello World合约,hello为合约用户名(执行部署合约命令则会创建一个合约账户,合约账户的资产只能通过合约来控制),nathan为支付手续费的账户,0 0 表示虚拟机类型和版本,/Users/zhaoxiangfei/code/gxb-core/contracts/examples/helloworld 为合约路径(包括wast文件和abi文件),GXC表示手续费类型,true表示是否广播。
// 部署合约
deploy_contract hello nathan 0 0 /Users/zhaoxiangfei/code/gxb-core/contracts/examples/helloworld GXC true
注意
- 部署合约时,支付手续费的账户必须已经导入过私钥(上面命令支付账户为
nathan
),且账户余额足够支付手续费
# 1.3 调用合约
您可以使用如下命令调用合约接口,GXChain的调用合约接口可以附加资产发送选项。附加资产的调用方式,会将资产发送到合约账户。合约账户的资产,只能通过合约自身代码使用提现APIwithdraw_asset
来控制。
// 不附带资产
call_contract nathan hello null hi "{\"user\":\"gxchain!!!\"}" GXC true
// 附带资产(附带资产的action,需要在合约中添加 // @abi payable )
call_contract nathan hello {"amount":10000000,"asset_id":1.3.1} hi "{\"user\":\"gxchain!!!\"}" GXC true
带转移资产的action定义
// @abi action
// @abi payable
void hi(std::string user)
{
{
for (int i = 0; i < 2; ++i) {
print("hi, ", user, "\n");
}
}
}
# 2. 代码解析
Hello World智能合约只包含一个action,是一个最简单的智能合约。我们可以以此为例,分析智能合约的基本框架。
合约的开发包括定义一个合约类,并提供一个apply接口,apply接口可以使用系统提供的GRAPHENE_ABI
来定义。
以上,一个基础智能合约就完成了。其中头文件所在目录为/Users/zhaoxiangfei/code/gxb-core/contracts/graphenelib
(更改为你的对应目录),合约开发过程中,引入相关头文件之后,便可以使用合约的内置类型和内置api函数了。下一篇教程分析一个较为复杂的智能合约-红包合约。
# 红包合约简介
在阅读本篇教程之前,假定您已经阅读完了入门指导
# 1. 功能简介与部署调用
# 1.0 合约功能
红包合约 (opens new window)是一个相对复杂的合约,通过对红包合约的分析,我们来说明多索引表、内置API调用等功能的使用方式。
红包合约包括三个action:创建红包(issue)、打开红包(open)、关闭红包(close)。
注:调用创建红包(issue)接口可以附带资产;如果红包未抢完,发行者可以关闭红包,余额退还。 发红包需要设置一个口令(公钥),抢红包时,需要使用口令对应的私钥对自己用户id进行签名才可以抢成功。
- 调用发红包接口,调用过程与结果反馈如下
# 发红包 pubkey为随机生成的口令 :下面调用表示发5个红包,总金额为11 GXC(1.3.1表示GXC资产)
unlocked >>> call_contract nathan redpacket {"amount":1100000,"asset_id":1.3.1} issue "{\"pubkey\":\"GXC81z4c6gEHw57TxHfZyzjA52djZzYGX7KN8sJQcDyg6yitwov5b\",\"number\":5}" GXC true
{
"ref_block_num": 15124,
...
...
...
"signatures": [
"1f7fade01ef08d986282164c1428fee37ecc5817c4e6bdc7c160220cf965b881d7417874ab22be48047becf62936e6a060a3e06c65e3548e90a72ddc1720794db3"
]
}
# 查看合约账户资产,当前合约只有一个用户发红包,所以金额为一个用户发送的资产11 GXC
unlocked >>> list_account_balances redpacket
11 GXC
# 查看红包table信息,subpackets是随机分成的5个子红包序列,pub_key用来验证签名
unlocked >>> get_table_rows redpacket packet 0 -1
[{
"issuer": 17,
"pub_key": "GXC81z4c6gEHw57TxHfZyzjA52djZzYGX7KN8sJQcDyg6yitwov5b",
"total_amount": {
"amount": 1100000,
"asset_id": 1
},
"number": 5,
"subpackets": [
350531,
150227,
390591,
66767,
141884
]
}
]
抢红包,需要知道发红包口令对应的私钥。即发红包时创建的口令为:GXC81z4c6gEHw57TxHfZyzjA52djZzYGX7KN8sJQcDyg6yitwov5b,那么需要知道该口令对应的私钥(此口令的私钥为5J9vj4XiwVQ2HNr22uFrxgaaerqrPN7xZQER9z2hwSPeWdbMKBM)。然后使用私钥对用户instanceid进行签名(签名方式使用cli_wallet提供了sign_string方法,instanceid为为账号id的最后一个字段,例如nathan的账号id是1.2.17,那么他的instanceid是17)
- 调用抢红包接口,调用过程与结果反馈如下
#使用私钥对instanceid进行签名
unlocked >>> get_account_id nathan
"1.2.17"
unlocked >>> sign_string 5J9vj4XiwVQ2HNr22uFrxgaaerqrPN7xZQER9z2hwSPeWdbMKBM 17
"1f1d104d5750beba9fd4b0637ce69cf54721a57cce91ca81904653307eb72b0a840bd8a80c58df0a7be206a4c5c5b1fa0d96d497667e54579e717d499d0a3498b2"
#调用接口 抢红包
call_contract nathan redpacket null open "{\"issuer\":\"nathan\",\"sig\":\"1f1d104d5750beba9fd4b0637ce69cf54721a57cce91ca81904653307eb72b0a840bd8a80c58df0a7be206a4c5c5b1fa0d96d497667e54579e717d499d0a3498b2\"}" GXC true
#合约账户所剩余额
list_account_balances redpacket
unlocked >>> list_account_balances redpacket
7.09409 GXC
#合约账户剩余红包分配序列,由5个减少为4个,减少的项为390591,代表3.90591个GXC 被抢走
unlocked >>> get_table_rows redpacket packet 0 -1
[{
"issuer": 17,
"pub_key": "GXC81z4c6gEHw57TxHfZyzjA52djZzYGX7KN8sJQcDyg6yitwov5b",
"total_amount": {
"amount": 1100000,
"asset_id": 1
},
"number": 5,
"subpackets": [
350531,
150227,
66767,
141884
]
}
]
# 抢红包记录
unlocked >>> get_table_rows redpacket record 0 -1
[{
"packet_issuer": 17,
"accounts": [{
"account_id": 17,
"amount": 390591
}
]
}
]
- 调用关闭红包接口,该接口只能由发红包的用户调用,会将未抢完的红包返回给用户,调用过程与结果反馈如下
# 您可以使用如下命令关闭红包
unlocked >>> call_contract nathan redpacket null close "{}" GXC true
# 获取合约账户资产余额
unlocked >>> list_account_balances redpacket
0 GXC
# 1.1 编译合约
您可以使用如下命令编译智能合约的abi文件和wast文件
# 其中的redpacket.cpp所在路径需要替换为你自己的路径
./gxx -g /Users/zhaoxiangfei/code/gxb-core/contracts/examples/redpacket/redpacket.abi /Users/zhaoxiangfei/code/gxb-core/contracts/examples/redpacket/redpacket.cpp
# 其中的redpacket.cpp所在路径需要替换为你自己的路径
./gxx -o /Users/zhaoxiangfei/code/gxb-core/contracts/examples/redpacket/redpacket.wast /Users/zhaoxiangfei/code/gxb-core/contracts/examples/redpacket/redpacket.cpp
# 1.2 部署合约
您可以使用如下命令部署Redpacket红包合约
# 需要将智能合约所在路径替换为你自己的路径
deploy_contract redpacket nathan 0 0 /Users/zhaoxiangfei/code/gxb-core/contracts/examples/redpacket GXC true
# 1.3 调用合约
# 发红包
unlocked >>> call_contract nathan redpacket {"amount":1100000,"asset_id":1.3.1} issue "{\"pubkey\":\"GXC81z4c6gEHw57TxHfZyzjA52djZzYGX7KN8sJQcDyg6yitwov5b\",\"number\":5}" GXC true
# 抢红包
unlocked >>> call_contract nathan redpacket null open "{\"issuer\":\"nathan\",\"sig\":\"1f1d104d5750beba9fd4b0637ce69cf54721a57cce91ca81904653307eb72b0a840bd8a80c58df0a7be206a4c5c5b1fa0d96d497667e54579e717d499d0a3498b2\"}" GXC true
# 您可以使用如下命令关闭红包
unlocked >>> call_contract nathan redpacket null close "{}" GXC true
# 2. 代码解析
红包合约代码包含了内置API的调用,多索引表的使用。通过分析红包合约,我们来简略说明如何使用它们,合约框架可以参考上篇教程Hello World合约简介。
- 注释 // @abi payable表示该action可以附带资产,用来生成正确的abi文件
// @abi action
// @abi payable
void issue(std::string pubkey, uint64_t number){
...
}
- 使用断言graphene_assert,失败时回滚action
graphene_assert(pubkey.size() > prefix_len, "invalid public key");
graphene_assert(pubkey.substr(0, prefix_len) == prefix, "invalid public key");
- 调用内置API
//引入相关头文件,内置API文档,请点击“内置API”
int64_t total_amount = get_action_asset_amount();
- 多索引表简单介绍
//每一个多索引表称为一个table,在合约开发中,定义为一个类。
//注释部分是用来生成abi文件的,详细内容请查看abi文件详解部分
//@abi table packet i64
struct packet {
uint64_t issuer;
std::string pub_key;
contract_asset total_amount;
uint32_t number;
vector<int64_t> subpackets; // 可以定义复杂类型
uint64_t primary_key() const { return issuer; } // 定义一个主键函数,返回的值作为主键索引是唯一的。
GRAPHENE_SERIALIZE(packet, (issuer)(pub_key)(total_amount)(number)(subpackets))
};
//定义多索引表类型
typedef graphene::multi_index<N(packet), packet> packet_index;
//声明多索引表类型的一个实例
packet_index packets;
//增加
packets.emplace(owner, [&](auto &o) {
o.issuer = owner;
...
});
//修改
packets.modify(packet_iter, sender, [&](auto &o) {
o.subpackets.erase(subpacket_it);
});
//删除
packets.erase(packet_iter);
# bank合约简介
在阅读本篇教程之前,假定您已经阅读完了入门指导
# 1. 功能简介与部署调用
# 1.0 合约功能
bank (opens new window)合约提供了代币存取功能。共包含两个action接口,一个为储存接口,用户调用该接口时,附加的代币资产为用户需要储存的资产。第二个为提现接口,可以指定提取一定数量的资产到指定账户。包含一个account table
,存储的内容为用户id以及其所储存的资产列表。
- 调用储存资产接口,调用过程与结果反馈如下
//nathan调用deposit接口储存10GXC到bank合约
unlocked >>> call_contract nathan bank {"amount":1000000,"asset_id":1.3.1} deposit "{}" GXC true
{
{
"ref_block_num": 36472,
"ref_block_prefix": 290922209,
"expiration": "2018-11-20T09:19:40",
"operations": [[
...
...
}
//查看合约内account table,获取账户储存的资产,asset_id表示GXC,owner为instance id,17表示nathan账户
unlocked >>> get_table_rows bank account 0 -1
[{
"owner": 17,
"balances": [{
"amount": 1000000,
"asset_id": 1
}
]
}
]
- 调用提现资产接口,调用过程与结果反馈如下
//nathan调用提现资产接口,提取1GXC到hello账户
unlocked >>> call_contract nathan bank null withdraw "{\"to_account\":\"hello\",\"amount\":{\"asset_id\":1,\"amount\":100000}}" GXC true
{
"ref_block_num": 36733,
"ref_block_prefix": 1321509121,
"expiration": "2018-11-20T09:42:10",
...
...
}
//提现之后,查看hello账户余额
unlocked >>> list_account_balances hello
1 GXC
# 1.1 编译合约
您可以使用如下命令编译智能合约的abi文件和wast文件
# 其中的bank.cpp所在路径需要替换为你自己的路径
./gxx -g /Users/zhaoxiangfei/code/contracts_work/bank/bank.abi /Users/zhaoxiangfei/code/contracts_work/bank/bank.cpp
# 其中的bank.cpp所在路径需要替换为你自己的路径
./gxx -o /Users/zhaoxiangfei/code/contracts_work/bank/bank.wast /Users/zhaoxiangfei/code/contracts_work/bank/bank.cpp
# 1.2 部署合约
您可以使用如下命令部署bank银行存取合约
# 需要将智能合约所在路径替换为你自己的路径
deploy_contract bank nathan 0 0 /Users/zhaoxiangfei/code/contracts_work/bank GXC true
# 1.3 调用合约
# 储存资产接口调用方式
call_contract nathan bank {"amount":1000000,"asset_id":1.3.1} deposit "{}" GXC true
# 提现资产接口调用方式
call_contract nathan bank null withdraw "{\"to_account\":\"hello\",\"amount\":{\"asset_id\":1,\"amount\":100000}}" GXC true
# 2.代码解析
bank合约包括两个action接口和一个table,本篇教程主要分析合约的功能逻辑的实现。
存储用户id以及资产列表的table
//主键为用户instance_id,balances字段为用户储存的资产列表
//@abi table account i64
struct account {
uint64_t owner;
std::vector<contract_asset> balances; //复杂类型vector,元素为contract_asset内置类型
uint64_t primary_key() const { return owner; }
GRAPHENE_SERIALIZE(account, (owner)(balances))
};
储存资产的action,该接口没有参数。当你想调用该接口储存资产时,通过附加资产传送来达到目的。该接口逻辑功能为,获取附加资产信息(资产id,资产数量),遍历持久化table account,将储存的资产信息添加到table中。
提示
修改table中的字段时,需要调用GXChain提供的modify接口修改,不可以直接使用遍历得到的迭代器修改,find方法返回的迭代器类型是const的。
// payable用来表名该action调用时可以附加资产
// @abi action
// @abi payable
void deposit()
{
// 通过get_action_asset_amount与get_action_asset_id获取调用action时附加的资产id和资产数量
int64_t asset_amount = get_action_asset_amount();
uint64_t asset_id = get_action_asset_id();
contract_asset amount{asset_amount, asset_id};
// 获取调用者id
uint64_t owner = get_trx_sender();
auto it = accounts.find(owner);
//如果调用者尚未存储过资产,则为其创建table项,主键为用户instance_id
if (it == accounts.end()) {
accounts.emplace(owner, [&](auto &o) {
o.owner = owner;
o.balances.emplace_back(amount);
});
} else {
uint16_t asset_index = std::distance(it->balances.begin(),
find_if(it->balances.begin(), it->balances.end(), [&](const auto &a) { return a.asset_id == asset_id; }));
if (asset_index < it->balances.size()) {
// contract_asset类重载了+=运算符,当用户往资产上继续存储时,可以直接添加
accounts.modify(it, 0, [&](auto &o) { o.balances[asset_index] += amount; });
} else {
accounts.modify(it, 0, [&](auto &o) { o.balances.emplace_back(amount); });
}
}
}
提现资产的action,该接口有两个参数,第一个参数为账户名(非账户id),第二个资产为提现的资产(资产id和提现的数量)。该函数的功能为校验请求通过之后,调用内置提现api(withdraw_asset),将资产从合约提现到指定账户。详细功能查看如下注释:
// @abi action
void withdraw(std::string to_account, contract_asset amount)
{
//根据用户名 获取用户instance_id,此为需要提现到的账户
int64_t account_id = get_account_id(to_account.c_str(), to_account.size());
graphene_assert(account_id >= 0, "invalid account_name to_account");
graphene_assert(amount.amount > 0, "invalid amount");
// 获取action调用者id
uint64_t owner = get_trx_sender();
auto it = accounts.find(owner);
graphene_assert(it != accounts.end(), "owner has no asset");
int asset_index = 0;
for (auto asset_it = it->balances.begin(); asset_it != it->balances.end(); ++asset_it) {
if ((amount.asset_id) == asset_it->asset_id) {
graphene_assert(asset_it->amount >= amount.amount, "balance not enough");
if (asset_it->amount == amount.amount) {
//当用户某个资产提取完毕时,清空列表中该资产
accounts.modify(it, 0, [&](auto &o) {
o.balances.erase(asset_it);
});
//当用户资产列表为空时,即用户提现完了所有资产,清空用户列表
if (it->balances.size() == 0) {
accounts.erase(it);
}
} else {
accounts.modify(it, 0, [&](auto &o) {
o.balances[asset_index] -= amount;
});
}
break;
}
asset_index++;
}
//内置api,提现资产到指定账户
withdraw_asset(_self, account_id, amount.asset_id, amount.amount);
}
# riddle合约简介
在阅读本篇教程之前,假定您已经阅读完了入门指导
# 1. 功能简介与部署调用
# 1.0 合约功能
riddle合约 (opens new window)是一个谜题合约,包括两个action接口,一个table。用户可以通过issue接口,创建一个谜题以及答案的哈希值保存到区块链上。reveal接口则用来验证谜题的回答是否正确,即验证回答的哈希值是否与谜题答案对应的哈希值一致。
- 创建谜题以及哈希答案
//生成答案对应的sha256哈希值,答案明文为4
zhaoxiangfei@zhaoxiangfeideMacBook-Pro:~$ echo -n "4" | shasum -a 256
4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a
//创建一个内容为`2+2=?`的谜题,答案为4。
unlocked >>> call_contract nathan riddle null issue "{\"question\":\"2 + 2 = ?\", \"hashed_answer\":\"4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a\"}" GXC true
{
"ref_block_num": 39138,
"ref_block_prefix": 3499868408,
"expiration": "2018-11-20T13:37:00",
"operations": [[
75,{
"fee": {
"amount": 100,
"asset_id": "1.3.1"
},
"account": "1.2.17",
"contract_id": "1.2.29",
"method_name": "issue",
"data": "0932202b2032203d203f4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a",
"extensions": []
}
]
],
"extensions": [],
"signatures": [
"1f0982608581765be0119c2af2261dd161b60e9ff5f02d07ff69c486e0ef2e52ef3187544f035d169de640386c87e24f2c61194693ac82d708f6177745d6dfb5a5"
]
}
- 验证提交的回答是否正确,输出成功或失败
验证提交的答案,错误提交,控制台显示如下: 验证提交的答案,正确提交,控制台显示如下:
# 1.1 编译合约
您可以使用如下命令编译智能合约的abi文件和wast文件
# 其中的riddle.cpp所在路径需要替换为你自己的路径
./gxx -g /Users/zhaoxiangfei/code/contracts_work/riddle/riddle.abi /Users/zhaoxiangfei/code/contracts_work/riddle/riddle.cpp
# 其中的riddle.cpp所在路径需要替换为你自己的路径
./gxx -o /Users/zhaoxiangfei/code/contracts_work/riddle/riddle.wast /Users/zhaoxiangfei/code/contracts_work/riddle/riddle.cpp
# 1.2 部署合约
您可以使用如下命令部署riddle谜题合约
# 需要将智能合约所在路径替换为你自己的路径
deploy_contract riddle nathan 0 0 /Users/zhaoxiangfei/code/contracts_work/riddle GXC true
# 1.3 调用合约
生成答案的sha256哈希值
echo -n "4" | shasum -a 256
# 创建谜题和答案的哈希值
call_contract nathan riddle null issue "{\"question\":\"2 + 2 = ?\", \"hashed_answer\":\"4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a\"}" GXC true
# 提交谜题回答的调用方式
# 错误答案
call_contract nathan riddle null reveal "{\"issuer\":\"nathan\", \"answer\":\"3\"}" GXC true
# 正确答案
call_contract nathan riddle null reveal "{\"issuer\":\"nathan\", \"answer\":\"4\"}" GXC true
# 2.代码解析
该合约包括一个table,用来存储谜题以及答案的哈希值,当谜题被解开后,清除table中的破解的谜题项。table主键为issuer(instance_id),由于主键是唯一的,所以每个用户只能同时在链上创建一个谜题。包括两个action,功能为创建谜题和提交回答。谜题答案以哈希值的方式保存在table中。
// @abi table record i64
struct record {
uint64_t issuer; //主键是唯一的,如果一个用户创建多个谜题,不能使用用户id为主键
std::string question;
checksum256 hashed_answer; //checksum256 内置哈希值类型
uint64_t primary_key() const { return issuer; }
GRAPHENE_SERIALIZE(record, (issuer)(question)(hashed_answer))
};
- 创建谜题,提交谜题明文内容以及答案的哈希值,哈希值需要在链下运算生成,采用sha256算法生成哈希
/// @abi action
void issue(const std::string& question, const checksum256& hashed_answer)
{
// 获取调用者的instance_id,作为record table的主键
uint64_t owner = get_trx_sender();
records.emplace(owner, [&](auto &p) {
p.issuer = owner;
p.question = question;
p.hashed_answer = hashed_answer;
});
}
- 提交答案,由合约进行验证,合约内置了sha256方法,可以在链上校验提交的回答是否满足条件
/// @abi action
void reveal(const std::string& issuer, const std::string& answer)
{
int64_t issuer_id = get_account_id(issuer.c_str(), issuer.size());
graphene_assert(issuer_id >= 0, "issuer not exist");
auto iter = records.find(issuer_id);
graphene_assert(iter != records.end(), "no record");
// sha256验证提交的回答是否为答案
checksum256 hashed_answer;
sha256(const_cast<char *>(answer.c_str()), answer.length(), &hashed_answer);
// 谜题被破解后则从table中删除,回收内存
if (iter->hashed_answer == hashed_answer) {
print("reveal success! \n");
records.erase(iter);
} else {
print("answer is wrong! \n");
}
}
# linear_vesting_asset合约简介
在阅读本篇教程之前,假定您已经阅读完了入门指导
# 1. 功能简介与部署调用
# 1.0 合约功能
linear_vesting_asset合约 (opens new window)的功能是向某一个账户按时间线性释放资产。用户可以创建一个资产释放项,包含接收释放资产的账户,冻结时间,释放时间。描述为:账户A通过合约向账户B线性释放一笔资产。可以指定冻结xx时间(xx表示冻结时间)后,开始释放,总共xx时间(xx表示释放时间)释放完,资产释放是线性的。
- 创建线性释放资产项
// 创建一个到hello账户的线性资产释放项,冻结时间为30s,释放时间为120s
call_contract nathan vesting {"amount":1000000,"asset_id":1.3.1} vestingcreate "{\"to\":\"hello\",\"lock_duration\":30,\"release_duration\":120}" GXC true
unlocked >>> get_table_rows vesting vestingrule 0 -1
[{
"account_id": 22,
"vesting_amount": 1000000,
"vested_amount": 0,
"lock_time_point": 1542785150,
"lock_duration": 30,
"release_time_point": 1542785180,
"release_duration": 120
}
]
- 线性解冻合约
// 解冻资产到hello账户
unlocked >>> call_contract nathan vesting null vestingclaim "{\"who\":\"hello\"}" GXC true
unlocked >>> list_account_balances hello
11 GXC
# 1.1 编译合约
您可以使用如下命令编译智能合约的abi文件和wast文件
# 其中的linear_vesting_asset.cpp所在路径需要替换为你自己的路径
./gxx -g /Users/zhaoxiangfei/code/contracts_work/linear_vesting_asset/linear_vesting_asset.abi /Users/zhaoxiangfei/code/contracts_work/linear_vesting_asset/linear_vesting_asset.cpp
# 其中的linear_vesting_asset.cpp所在路径需要替换为你自己的路径
./gxx -o /Users/zhaoxiangfei/code/contracts_work/linear_vesting_asset/linear_vesting_asset.wast /Users/zhaoxiangfei/code/contracts_work/linear_vesting_asset/linear_vesting_asset.cpp
# 1.2 部署合约
您可以使用如下命令部署vesting线性释放资产合约
# 需要将智能合约所在路径替换为你自己的路径
deploy_contract vesting nathan 0 0 /Users/zhaoxiangfei/code/contracts_work/linear_vesting_asset GXC true
# 1.3 调用合约
// 合约名 vesting,附加的资产为10 GXC(资产id为1.3.1),释放的账户为hello账户,冻结30s之后开始释放,经过120s的时间之后,完全完所有的资产。
call_contract nathan vesting {"amount":1000000,"asset_id":1.3.1} vestingcreate "{\"to\":\"hello\",\"lock_duration\":30,\"release_duration\":120}" GXC true
// 认领释放的资产到hello账户(必须30s之后才可以认领,30s为冻结时间)
call_contract nathan vesting null vestingclaim "{\"who\":\"hello\"}" GXC true
# 2.代码解析
- 该合约包含一个table(vestingrule,持久化存储保存了锁定资产数量、锁定时间、释放时间等字段)
//@abi table vestingrule i64
struct vestingrule {
uint64_t account_id; //认领账户的id 主键,不能同时有两项向同一账户释放资产
int64_t vesting_amount; //初始锁定资产
int64_t vested_amount; //已释放资产
int64_t lock_time_point; //锁定开始时间
int64_t lock_duration; //锁定多久开始释放
int64_t release_time_point; //释放开始时间
int64_t release_duration; //释放多久全部释放完毕
uint64_t primary_key() const { return account_id; }
GRAPHENE_SERIALIZE(vestingrule,
(account_id)(vesting_amount)(vested_amount)(lock_time_point)(lock_duration)(release_time_point)(release_duration))
};
- 包含两个action(vestingcreate action用来创建一个线性释放资产项;vestingclaim action用来认领线性释放的资产)
/// @abi action
/// @abi payable
void vestingcreate(std::string to, int64_t lock_duration, int64_t release_duration)
{
// contract_asset_id是一个自定义变量,表示GXC资产,线性释放资产只支持GXC
graphene_assert(contract_asset_id == get_action_asset_id(), "not supported asset");
contract_asset ast{get_action_asset_amount(), contract_asset_id};
int64_t to_account_id = get_account_id(to.c_str(), to.size());
// 验证认领账户是否有效 以及该认领账户是否有已经锁定的资产
graphene_assert(to_account_id >= 0, "invalid account_name to");
auto lr = vestingrules.find(to_account_id);
graphene_assert(lr == vestingrules.end(), "have been locked, can only lock one time");
//创建资产释放项,在vestingrule table中添加一项,使用emplace接口
vestingrules.emplace(0, [&](auto &o) {
o.account_id = to_account_id;
o.vesting_amount = ast.amount;
o.vested_amount = 0;
o.lock_time_point = get_head_block_time();
o.lock_duration = lock_duration;
o.release_time_point = o.lock_time_point + o.lock_duration;
o.release_duration = release_duration;
});
}
/// @abi action
void vestingclaim(std::string who)
{
// 验证认领账户id是否有效
int64_t who_account_id = get_account_id(who.c_str(), who.size());
graphene_assert(who_account_id >= 0, "invalid account_name to");
// 验证该账户是否存在锁定待释放的资产
auto lr = vestingrules.find(who_account_id);
graphene_assert(lr != vestingrules.end(), "current account have no locked asset");
// 验证资产是否经过了冻结时间,并计算可以认领的资产数量
uint64_t now = get_head_block_time();
graphene_assert(now > lr->release_time_point, "within lock duration, can not release");
int percentage = (now - lr->release_time_point) * 100 / lr->release_duration;
if (percentage > 100)
percentage = 100;
int64_t vested_amount = (int64_t)(1.0f * lr->vesting_amount * percentage / 100);
vested_amount = vested_amount - lr->vested_amount;
graphene_assert(vested_amount > 0, "vested amount must > 0");
// 提现资产到认领账户
withdraw_asset(_self, who_account_id, contract_asset_id, vested_amount);
// 提现之后,修改资产认领项的vesting_amount、vested_amount字段
vestingrules.modify(lr, 0, [&](auto &o) {
o.vested_amount += vested_amount;
});
if (lr->vesting_amount == lr->vested_amount) {
vestingrules.erase(lr);
}
}