EOS 的智能合约 -- blog_view
1、概要
关于如何编译EOS源码、编译docker镜像,搭建节点等等,官方都有相关文档,
2、本次合约介绍
之前在eos dawn 2.0版本,有个示例合约 simpledb,公司需求的智能合约就是实现了类似合约,所以,之前我实现的版本就是根据这个修改的。而且,该合约主要使用的是2.0版中的 db.h头文件中的函数 store_str,以字符串为索引,保存结构体的功能;
但是,在3.0版本中,该功能暂时删除,只能暂时使用其他功能代替,目前,决定使用multi_index容器;
3、multi_index
eos dawn 3.0中的multi_index,使用方法和boost中的multi_index,非常类似,就是多重索引容器,假如清楚其使用方法的话,应该对这个比较熟悉;
声明
typedef eosio::multi_index< tablename, typename> table( code, scope);
其中,需要的几个参数,如其命名含义:
tablename:该table的名称;
typename: 该容器存储的结构体;
code:本合约的名称,例如 N(tests);
scope:数据存储的账户名;
多级索引
声明时候,可以使用以下方式声明二级索引,或多级索引:
typedef eosio::multi_index< tablename, typename,
index_by< scope, const_mem_fun<typename, index_type, typename::method> >
table( code, scope);
但是,目前二级索引,只支持uint64_t、uint128_t、 key256(eosio的内建类型) 具体如使用,会在后续程序里面讲解;
方法
声明完成后,可以使用以下功能:
table.emplace(scope, [&]( auto& g ) { ... }) 添加数据;
table.find(primary_key) 用关键字查找;
table.modify(itr, scope, [&]( auto& g ) { ... }) 修改数据;
table.erase(itr) 删除
table.begin() 数据起始
table.end() 数据末尾
...
4、更符合面向对象的新合约
Dawn3.0的新合约编写方式见:新格式
该合约要实现以下功能:
每个用户能够上传自己的blog文章;
其他用户能够审核该用户的文章是否正确;
作者可以查询文章状态和数量;
结构定义如下,即abi文件:
{
"____comment": "This file was generated by eosio-abigen. DO NOT EDIT - 2018-04-16T07:24:15",
"types": [],
"structs": [{
"name": "account",
"base": "",
"fields": [{
"name": "owner",
"type": "account_name"
},{
"name": "blognum",
"type": "uint32"
}
]
},{
"name": "blog",
"base": "",
"fields": [{
"name": "ID",
"type": "uint64"
},{
"name": "status",
"type": "uint8"
},{
"name": "approve_status",
"type": "string"
},{
"name": "producer",
"type": "account_name"
},{
"name": "reviewer",
"type": "account_name"
},{
"name": "content",
"type": "string"
}
]
},{
"name": "upload",
"base": "",
"fields": [{
"name": "producer",
"type": "account_name"
},{
"name": "content",
"type": "string"
}
]
},{
"name": "reviewing",
"base": "",
"fields": [{
"name": "ID",
"type": "uint64"
},{
"name": "reviewer",
"type": "account_name"
}
]
},{
"name": "approved",
"base": "",
"fields": [{
"name": "ID",
"type": "uint64"
}
]
},{
"name": "disapprove",
"base": "",
"fields": [{
"name": "ID",
"type": "uint64"
},{
"name": "reason",
"type": "string"
}
]
},{
"name": "remove",
"base": "",
"fields": [{
"name": "ID",
"type": "uint64"
}
]
}
],
"actions": [{
"name": "upload",
"type": "upload",
"ricardian_contract": ""
},{
"name": "reviewing",
"type": "reviewing",
"ricardian_contract": ""
},{
"name": "approved",
"type": "approved",
"ricardian_contract": ""
},{
"name": "disapprove",
"type": "disapprove",
"ricardian_contract": ""
},{
"name": "remove",
"type": "remove",
"ricardian_contract": ""
}
],
"tables": [{
"name": "account",
"index_type": "i64",
"key_names": [
"owner"
],
"key_types": [
"account_name"
],
"type": "account"
},{
"name": "blog",
"index_type": "i64",
"key_names": [
"ID"
],
"key_types": [
"uint64"
],
"type": "blog"
}
],
"clauses": []
}
总结如下:
table: account(存储用户blog数量)、blog(保存用户blog);
action:
upload 上传blog信息;
reviewing 审核员开始审核;
approved 通过审核;
disapprove 未通过审核;
remove 用户删除自己blog;
5、代码
/**
- @file
- @copyright defined in eos/LICENSE.txt
- @auther: redbutterfly
- @createtime: 2018-04-16
*/
#include <eosiolib/eosio.hpp>
#include <eosiolib/multi_index.hpp>
#include <eosiolib/contract.hpp>
using eosio::indexed_by;
using eosio::const_mem_fun;
using std::string;
class blog_view : public eosio::contract {
public:
using contract::contract;
blog_view(account_name self)
:eosio::contract(self),
accounts(_self, _self),
idlists(_self, _self),
init_status(std::string(64,'0'))
{}
/// @abi action
void upload(const account_name producer, const std::string content) {
require_auth(producer);
blog_index upload_blogs(_self, producer);
//获取ID
uint32_t nowID = get_ID();
//TODO: add the dedup
upload_blogs.emplace(producer, [&]( auto& g ) {
g.ID = nowID;
g.status = Status::s_uploaded;
g.producer = producer;
g.content = content;
g.approve_status = init_status;
});
//在idlist添加,ID--producer关系,用于之后通过ID查询producer
idlists.emplace(_self, [&]( auto& g ) {
g.ID = nowID;
g.producer = producer;
});
//blog数量+1
blognum_op(producer, '+');
}
/// @abi action
void reviewing(const uint64_t ID, const account_name reviewer) {
require_auth(reviewer);
//先通过idlist查询ID,获取用户名,然后才能用mutil_index查询具体用户的blog,下同
auto itrid = idlists.find(ID);
eosio_assert(itrid != idlists.end(), "this blog doesn't exists!\n");
blog_index review_blogs(_self, itrid->producer);
auto itr = review_blogs.find( ID );
eosio_assert(itr != review_blogs.end(), "this blog doesn't exists!\n");
eosio_assert(itr->producer != reviewer, "you can't review youself!\n");
eosio_assert(itr->status == Status::s_uploaded, "this blog is reviewing or reviewed!\n");
review_blogs.modify(itr, itrid->producer, [&](auto& g){
g.status = Status::s_reviewing;
g.reviewer = reviewer;
});
}
/// @abi action
void approved(const uint64_t ID) {
auto itrid = idlists.find(ID);
eosio_assert(itrid != idlists.end(), "this blog doesn't exists!\n");
blog_index approve_blogs(_self, itrid->producer);
auto itr = approve_blogs.find( ID );
eosio_assert(itr != approve_blogs.end(), "this blog doesn't exists!\n");
eosio_assert(itr->status == Status::s_reviewing, "this blog is reviewed!\n");
require_auth(itr->reviewer);
approve_blogs.modify(itr, itrid->producer, [&](auto& g){
g.status = Status::s_approved;
g.approve_status = std::string("approved");
});
}
/// @abi action
void disapprove(const uint64_t ID, std::string reason) {
auto itrid = idlists.find(ID);
eosio_assert(itrid != idlists.end(), "this blog doesn't exists!\n");
blog_index disappr_policys(_self, itrid->producer);
auto itr = disappr_policys.find( ID );
eosio_assert(itr != disappr_policys.end(), "this blog doesn't exists!\n");
eosio_assert(itr->status == Status::s_reviewing, "this blog is reviewed!\n");
require_auth(itr->reviewer);
disappr_policys.modify(itr, itrid->producer, [&](auto& g){
g.status = Status::s_disapprove;
g.approve_status = reason;
});
}
/// @abi action
void remove(const uint64_t ID) {
auto itrid = idlists.find(ID);
eosio_assert(itrid != idlists.end(), "this blog doesn't exists!\n");
blog_index remove_policys(_self, itrid->producer);
auto itr = remove_policys.find( ID );
eosio_assert(itr != remove_policys.end(), "this blog doesn't exists!\n");
require_auth(itr->producer);
//使用erase删除
remove_policys.erase(itr);
blognum_op(itr->producer, '-');
}
private:
enum Status {s_uploaded,s_reviewing,s_approved,s_disapprove};
/**
* 用于保存用户信息,保存用户文章数量
* @abi table account i64
*/
struct account {
account( account_name o = account_name() ):owner(o){}
account_name owner;
uint32_t blognum = 0;
bool is_empty()const { return !blognum; }
uint64_t primary_key()const { return owner; }
EOSLIB_SERIALIZE( account, (owner)(blognum) )
};
typedef eosio::multi_index< N(account), account> account_index;
/**
* 保存文章ID和用户关系,假如不保存,则审核员每次都要提交文章作者;
*/
struct idlist {
uint64_t ID;
account_name producer;
uint64_t primary_key()const { return ID; }
EOSLIB_SERIALIZE( idlist, (ID)(producer) )
};
typedef eosio::multi_index< N(idlist), idlist> idlist_index;
//@abi table blog i64
struct blog {
uint64_t ID;
uint8_t status;
std::string approve_status;
account_name producer;
account_name reviewer;
std::string content;
auto primary_key() const { return ID; }
EOSLIB_SERIALIZE( blog, (ID)(status)(approve_status)(producer)(reviewer)(content) )
};
typedef eosio::multi_index< N(blog), blog> blog_index;
account_index accounts;
idlist_index idlists;
std::string init_status;
// get the code's policynum
uint32_t get_ID() {
auto itr = accounts.find( _self );
if ( itr == accounts.end() ) {
return 0;
} else {
return itr->blognum;
}
}
/**
* to operate the account's policynum
* op : '+','-'
*/
void blognum_op(account_name name, char op) {
auto itr = accounts.find( name );
if ( itr == accounts.end() ) {
accounts.emplace(name, [&]( auto& g ) {
g.owner = name;
g.blognum = 1;
});
} else {
if( op == '+' ) {
accounts.modify(itr, itr->owner, []( auto& g ) {g.blognum += 1;});
} else if( op == '-' ) {
accounts.modify(itr, itr->owner, []( auto& g ) {g.blognum -= 1;});
}
}
itr = accounts.find( _self );
if ( itr == accounts.end() ) {
accounts.emplace(_self, [&]( auto& g ) {
g.owner = _self;
g.blognum = 1;
});
} else {
if( op == '+' ) {
accounts.modify(itr, itr->owner, []( auto& g ) {g.blognum += 1;});
}
}
}
};
EOSIO_ABI( blog_view, (upload)(reviewing)(approved)(disapprove)(remove) )
要点简单总结:
智能合约的开发,主要是要摒弃之前开发习惯,因为要在一定的限制下开发需要的功能;比如,在审查员审查blog的时候,在blog_index结构中,用scope作为主分类,然后使用ID作为primary_key,这样就要求每次要查询primary_key的时候,首先要知道scope,但是,虽然审查员可以每次都传入producer,但是太过麻烦,这种时候,就要在生成一个结构体idlist,用于存储这种关系,因为idlist的scope是合约本身,这就可以避免不知道scope的情况;
在进行upload时候,blog的approve_status属性是string,我将其初始化为一个64字符的字符串。此处是因为,假如我先设置空值,或短字符串的时候,当审核员要修改此string,并超过原先值的时候,就需要producer的权限,正常来说,审核员要提交了,还需要上传者的权限,这就不对了。所以,此处设置一个长字符串,并要求审核员设置不要超过64;
其他,就没有什么难点或者问题了。
6、执行
使用eosiocpp编译完后,执行上传:
image.png
使用tester作为上传者,先上传两个blog:
image.png
使用get table查看上传结果:
image.png
审核员yanyan先开始审核第二个,发送开始审核action:
image.png
审核通过后,发送通过的action:
image.png
审核第一个(略过reviewing,同上),但是,发现问题,执行审核不通过:
image.png
用户看到审核不通,则删除不通过的合约:
image.png
以上,就是该智能合约的执行过程。
7、其他
当然,该合约还有很多可以改进的地方,比如blog增加第二个key,更加方便的排重,或者添加统计未审核blog的table,方便审核员查找等等。