上一章咱們細緻地學習了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合約的實現以及使用。