EOS入門指南PART8——智能合約入門實戰

上一章咱們細緻地學習了node

  • 索引和迭代器的關係;
  • 如何生成和使用索引以及迭代器
  • 介紹了multi_index的相關操做

相信你們對multi_index已經有了比較全面的理論理解以及掌握了一些基礎的操做。這一章將會教你們如何完整地構建一個智能合約,並在合約中直觀地操做multi_index。c++

摘要

這一章主要以實操爲主,會有較大篇幅的代碼,但願你們最好能夠照着文章本身操做一遍。

這一章將會以一個簡單的智能合約例子,簡單瞭解一個完整的EOS智能合約長什麼樣。但願你們經過這一章的學習,不只能夠有能力構建一個簡單的智能合約,而且對multi_index在EOS智能合約中的重要性,會有更加深入的認識。bash

頭文件:*.hpp

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});
}

1. 添加數據

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裏的兩個參數依次就是咱們前面提到過的codescope,分別表示表的擁有帳戶以及代碼層次結構的範圍標識符(已經忘記的小夥伴能夠翻看上一章內容)。

當profiles表實例化完成以後,緊接着就是插入數據。關於插入數據的操做上一章咱們有過詳細的介紹,這裏就再也不贅述了。主要注意防止主鍵重複的操做。

2. 根據主鍵獲取相關信息

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()),若是不爲空的話,就返回主鍵對應的其餘字段的信息。

這些操做都是經過咱們上一章介紹過的迭代器來完成。

3. 根據主鍵更新信息

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;
    });
}

和以前的操做相似,確保主鍵不爲空的狀況下,更新該主鍵對應的其餘字段的信息。

4. 根據主鍵刪除數據

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!");
}

5. 經過自定義的自定義索引實現查詢

前面四個介紹的都是主鍵相關的增刪改查的操做,別忘了咱們在上一章中還曾經定義過自定義索引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.wastapp.abi文件。

部署合約:

cleos set contract eosio ./ ./app.wast app.abi -p eosio@active

(該命令前文也詳細介紹過每一個參數的含義,詳情參考第五篇)

下圖爲成功部署合約的畫面:

eos-app-contract

合約調用

1. 查看錶內容

cleos get table eosio eosio profile

經過上述指令查看錶中的內容,參數eosio eosio profile分別表示前文提到過的code、scope和表名。

結果以下圖:

eos-app-contract

由於在頭文件裏聲明過,能夠看到該表已存在,可是內容爲空。

2. 插入數據

執行以下命令往表中插入數據,而後再次查詢:

// 插入
cleos push action eosio create '["eosio","hammer","25","programmer"]' -p eosio@active
// 再次查詢
cleos get table eosio eosio profile

eos-app-contract

這時就能夠看到表中保存了咱們剛插入的一條數據。

還記得咱們曾經建立過一個叫作testeosio的帳戶麼?咱們再使用那個帳戶往表中插入一條數據(記得先unlock錢包哦):

// 切換帳號插入數據
cleos push action eosio create '["testeosio","maggie","23","waitress"]' -p testeosio@active
// 查詢
cleos get table eosio eosio profile

這時咱們能夠看到:

eos-app-contract

數據確實添加進入表中了。

傳入數據的第一個參數必須是調用該方法的帳戶名,還記得代碼中的require_auth麼?😉

3. 查詢數據

使用get方法,傳入主鍵:

cleos push action eosio get '["testeosio"]' -p testeosio@active
cleos push action eosio get '["testeosio"]' -p eosio@active

這裏咱們分別使用不一樣帳戶進行查詢,由於沒有權限限制,因此任何人均可以查詢任意信息。獲得的結果以下:

eos-app-contract

4. 根據自定義索引age篩選數據

根據主鍵的更新和刪除方法按照上面的調用方法類推便可,這裏就再也不贅述了。讓咱們來試試以前定義的自定義索引的相關方法,byage和以前方法相似,咱們就一步到位,直接調用agerange方法了。

// 傳入參數的第一個爲年齡下限,第二個爲年齡上限
cleos push action eosio agerange '["22","27"]' -p eosio@active

此時獲得:

eos-app-contract

注意到這裏只返回了一個值,這時咱們切換到nodeos的終端界面發現返回了完整的結果:

eos-app-contract

若是你們本身調用byage方法,會發現nodeos終端也只會顯示一個結果(即便兩個都符合條件),由於它只會返回符合條件的第一個結果。

總結

在鋪墊了那麼多理論和碎片的操做知識以後,咱們終於第一次以完整的合約的形式,實現了對multi_index的操做,例如如何以主鍵以及自定義索引實現增刪改查。但願你們能夠感覺到理解了multi_index,才能更加準確地理解智能合約的數據存儲以及運行原理。

下一章咱們將介紹你們最感興趣的token合約的實現以及使用。

相關文章
相關標籤/搜索