上一章咱们细致地学习了node
- 索引和迭代器的关系;
- 如何生成和使用索引以及迭代器
- 介绍了multi_index的相关操做
相信你们对multi_index已经有了比较全面的理论理解以及掌握了一些基础的操做。这一章将会教你们如何完整地构建一个智能合约,并在合约中直观地操做multi_index。c++
这一章主要以实操为主,会有较大篇幅的代码,但愿你们最好能够照着文章本身操做一遍。
这一章将会以一个简单的智能合约例子,简单了解一个完整的EOS智能合约长什么样。但愿你们经过这一章的学习,不只能够有能力构建一个简单的智能合约,而且对multi_index在EOS智能合约中的重要性,会有更加深入的认识。bash
C++的源代码文件分为两类:头文件(Header file)和源文件(Source code file)。app
- 头文件用于存放对类型定义、函数声明、全局变量声明等实体的声明,做为对外接口;
- 源程序文件存放类型的实现、函数体、全局变量定义;
咱们先来看头文件里的代码:函数
#include <eosiolib/eosio.hpp> #include <eosiolib/print.hpp> #include <string> using namespace eosio; using std::string;
最前面按惯例都是import,接着往下看:学习
class app : public contract { public: using contract::contract; app(account_name self) : contract(self) {} // @abi action void hello(const account_name account); // @abi action void create(const account_name account, const string& username, uint32_t age, const string& bio); // @abi action void get(const account_name account); // @abi action void update(const account_name account, const string& username, uint32_t age, const string& bio); // @abi action void remove(const account_name account); // @abi action void byage(uint32_t age); // @abi action void agerange(uint32_t young, uint32_t old);
这里定义了源文件里的方法接口,接下来就到了最核心的multi_index的相关定义:ui
private: // @abi table profile i64 struct profile { account_name account; string username; uint32_t age; string bio; account_name primary_key() const { return account; } uint64_t by_age() const { return age; } EOSLIB_SERIALIZE(profile, (account)(username)(age)(bio)) }; typedef eosio::multi_index< N(profile), profile, // N(name of interface) indexed_by< N(age), const_mem_fun<profile, uint64_t, &profile::by_age> > > profile_table; };
这里定义了multi_index表的结构 (struct profile),主键以及按年龄的索引定义。(上一章详细讲过)spa
最后再加上EOSIO_ABI的声明:3d
EOSIO_ABI(app, (hello)(create)(get)(update)(remove)(byage)(agerange))
这里只须要简单地把全部方法串联在一块儿就能够了。code
上述能够看到hpp头文件里的内容很简单,只包含了最简单的变量和接口的声明。而与之配套的*.cpp文件就要复杂一些,里面对这些接口都作了具体的实现。
首先确定是引用头文件:
#include <app.hpp> void app::hello(account_name account) { print("Hello ", name{account}); }
void app::create(const account_name account, const string& username, uint32_t age, const string& bio) { require_auth(account); profile_table profiles(_self, _self); auto itr = profiles.find(account); eosio_assert(itr == profiles.end(), "Account already exists"); profiles.emplace(account, [&](auto& p) { p.account = account; p.username = username; p.age = age; p.bio = bio; }); }
require_auth
语句和以太坊中的require(msg.sender == xxx)相似,都对调用者的身份作了限制。
profile_table
是一种类型,能够理解成表示multi_index表
,后面的profiles(_self, _self)
才是真正构建一个multi_index表的实例。profiles里的两个参数依次就是咱们前面提到过的code
和scope
,分别表示表的拥有帐户以及代码层次结构的范围标识符(已经忘记的小伙伴能够翻看上一章内容)。
当profiles表实例化完成以后,紧接着就是插入数据。关于插入数据的操做上一章咱们有过详细的介绍,这里就再也不赘述了。主要注意防止主键重复的操做。
void app::get(const account_name account) { profile_table profiles(_self, _self); auto itr = profiles.find(account); eosio_assert(itr != profiles.end(), "Account does not exist"); print("Account: ", name{itr->account}, " , "); print("Username: ", itr->username.c_str(), " , "); print("Age: ", itr->age , " , "); print("Bio: ", itr->bio.c_str()); }
这里也很简单,先把multi_index表实例化,以后要求查询的结果不能为空 (即itr != profiles.end()
),若是不为空的话,就返回主键对应的其余字段的信息。
这些操做都是经过咱们上一章介绍过的迭代器来完成。
void app::update(const account_name account, const string& username, uint32_t age, const string& bio) { require_auth(account); profile_table profiles(_self, _self); auto itr = profiles.find(account); eosio_assert(itr != profiles.end(), "Account does not exist"); profiles.modify(itr, account, [&](auto& p) { p.username = username; p.age = age; p.bio = bio; }); }
和以前的操做相似,确保主键不为空的状况下,更新该主键对应的其余字段的信息。
void app::remove(const account_name account) { require_auth(account); profile_table profiles(_self, _self); auto itr = profiles.find(account); eosio_assert(itr != profiles.end(), "Account does not exist"); profiles.erase(itr); print(name{account} , " deleted!"); }
前面四个介绍的都是主键相关的增删改查的操做,别忘了咱们在上一章中还曾经定义过自定义索引by_age()
,即以年龄为条件进行筛选。具体实现以下:
void app::byage(uint32_t age) { print("Checking age: ", age, "\n"); profile_table profiles(_self, _self); // get an interface to the 'profiles' containter // that looks up a profile by its age auto age_index = profiles.get_index<N(age)>(); auto itr = age_index.lower_bound(age); for(; itr != age_index.end() && itr->age == age; ++itr) { print(itr->username.c_str(), " is ", itr->age, " years old\n"); } }
这里咱们使用了在头文件里定义过的名为age
的index,从新获得了一张以age排序的multi_index。
这里的lower_bound
是EOS封装的API,返回age_index中,当前索引≥age
的迭代器;以后遍历该迭代器,就能够得到全部age≥
某个特定值的全部数据。
和lower_bound
相对应的,就是upper_bound
方法,用法和lower_bound
相似。以下就实现了同时指定age
上下限的查询:
void app::agerange(uint32_t young, uint32_t old) { profile_table profiles(_self, _self); auto age_index = profiles.get_index<N(age)>(); auto begin = age_index.lower_bound(young); auto end = age_index.upper_bound(old); for_each(begin, end, [&](auto& p) { print(p.username.c_str(), " is ", p.age, " years old\n"); }); }
把前文中全部的hpp和cpp的代码片断拼接成完整的hpp和cpp文件进行编译:
#使用 -o 生成wast文件和wasm文件 eosiocpp -o ./app.wast ./app.cpp #使用 -g 生成abi文件 eosiocpp -g ./app.abi ./app.cpp
生成wast和abi文件的详细内容咱们以前章节介绍过了,这里也不赘述了。这时咱们的当前文件夹下会出现app.wast
和app.abi
文件。
部署合约:
cleos set contract eosio ./ ./app.wast app.abi -p eosio@active
(该命令前文也详细介绍过每一个参数的含义,详情参考第五篇)
下图为成功部署合约的画面:
cleos get table eosio eosio profile
经过上述指令查看表中的内容,参数eosio eosio profile
分别表示前文提到过的code、scope和表名。
结果以下图:
由于在头文件里声明过,能够看到该表已存在,可是内容为空。
执行以下命令往表中插入数据,而后再次查询:
// 插入 cleos push action eosio create '["eosio","hammer","25","programmer"]' -p eosio@active // 再次查询 cleos get table eosio eosio profile
这时就能够看到表中保存了咱们刚插入的一条数据。
还记得咱们曾经建立过一个叫作testeosio
的帐户么?咱们再使用那个帐户往表中插入一条数据(记得先unlock钱包哦):
// 切换帐号插入数据 cleos push action eosio create '["testeosio","maggie","23","waitress"]' -p testeosio@active // 查询 cleos get table eosio eosio profile
这时咱们能够看到:
数据确实添加进入表中了。
传入数据的第一个参数必须是调用该方法的帐户名,还记得代码中的require_auth
么?😉
使用get方法,传入主键:
cleos push action eosio get '["testeosio"]' -p testeosio@active cleos push action eosio get '["testeosio"]' -p eosio@active
这里咱们分别使用不一样帐户进行查询,由于没有权限限制,因此任何人均可以查询任意信息。获得的结果以下:
根据主键的更新和删除方法按照上面的调用方法类推便可,这里就再也不赘述了。让咱们来试试以前定义的自定义索引的相关方法,byage
和以前方法相似,咱们就一步到位,直接调用agerange
方法了。
// 传入参数的第一个为年龄下限,第二个为年龄上限 cleos push action eosio agerange '["22","27"]' -p eosio@active
此时获得:
注意到这里只返回了一个值,这时咱们切换到nodeos的终端界面发现返回了完整的结果:
若是你们本身调用byage
方法,会发现nodeos终端也只会显示一个结果(即便两个都符合条件),由于它只会返回符合条件的第一个结果。
在铺垫了那么多理论和碎片的操做知识以后,咱们终于第一次以完整的合约的形式,实现了对multi_index的操做,例如如何以主键以及自定义索引实现增删改查。但愿你们能够感觉到理解了multi_index,才能更加准确地理解智能合约的数据存储以及运行原理。
下一章咱们将介绍你们最感兴趣的token合约的实现以及使用。