EOS Dawn 3.0 智能合约 -- blog_view

1、前提概要

关于如何编译EOS源码、编译docker镜像,搭建节点等等,官方都有相关文档,新的特性也在之前文章中大体介绍了EOS Dawn 3.0整理

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的新合约编写方式见:新格式
该合约要实现以下功能:

  1. 每个用户能够上传自己的blog文章;
  2. 其他用户能够审核该用户的文章是否正确;
  3. 作者可以查询文章状态和数量;

结构定义如下,即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) )

要点简单总结:

  1. 智能合约的开发,主要是要摒弃之前开发习惯,因为要在一定的限制下开发需要的功能;比如,在审查员审查blog的时候,在blog_index结构中,用scope作为主分类,然后使用ID作为primary_key,这样就要求每次要查询primary_key的时候,首先要知道scope,但是,虽然审查员可以每次都传入producer,但是太过麻烦,这种时候,就要在生成一个结构体idlist,用于存储这种关系,因为idlist的scope是合约本身,这就可以避免不知道scope的情况;
  2. 在进行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,方便审核员查找等等。

代码见:我的GitHub

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容