eos合約案例導讀

爲了幫助你們熟悉 EOS 智能合約,EOS 官方提供了一個代幣(資產)智能合約 Demo —— eosio.token。eosio.token 智能合約目前還不是特別完善,個別功能尚未完成。但這個示例合約給出了 EOS 官方智能合約開發的標準結構和開發方法,而且真正的 EOS 代幣也會借鑑這個示例合約的邏輯,是 EOS 智能合約入門的經典案例。

照例,eosio.token 合約由三個文件(cpp,hpp,abi)文件組成,本篇文章將爲你們講解 eosio.token.hpp 文件。原文件地址:https://github.com/EOSIO/eos/tree/master/contracts/eosio.tokengit

預處理指令 & 頭文件
代碼的開頭聲明瞭頭文件,主要是 eos 智能合約的 API 庫。github

//預處理指令,防止文件被重複包含數據庫

pragma once

//eos 資產(asset)頭文件數據結構

include <eosiolib/asset.hpp>

//eos 智能合約 API 庫函數

include <eosiolib/eosio.hpp>

構造函數
智能合約的類名能夠與智能合約名不一樣,智能合約的名字是其帳戶名。構造函數爲空,參數爲智能合約帳戶名。工具

//每一個智能合約類都要繼承 contract 類
class token : public contract {
public:
//類構造函數
token( account_name self ):contract(self){}區塊鏈

建立代幣函數(action)
聲明 create 函數,這個函數用來新建一種代幣,並輸入代幣的各類屬性,同時 create 函數也是一個 action。action 是 eos 智能合約的接口函數,定義外界能夠對智能合約作什麼動做。ui

//參數:發幣者
     void create( account_name issuer,
                  //資產最大數目
                  asset        maximum_supply,
                  //資產是否能夠凍結
                  uint8_t      issuer_can_freeze,
                  //資產是否能夠召回
                  uint8_t      issuer_can_recall,
                  //資產是否能夠設置白名單
                  uint8_t      issuer_can_whitelist );

增發代幣函數(action)
聲明 issue 函數,這個函數用來增發代幣,eosio.token 合約並非新建了代幣就會獲得代幣,新建的代幣只是存儲了資料,發幣者要想獲取代幣,須要調用 issue action 來得到代幣。this

//參數:接收新代幣帳戶,新增多少代幣,memo
     void issue( account_name to, asset quantity, string memo );

1
2
轉帳函數(action)
聲明 transfer 函數,這個函數用來轉帳,是代幣智能合約最經常使用的函數。.net

//發送帳戶
     void transfer( account_name from,
                    //接收帳戶
                    account_name to,
                    //代幣數量
                    asset        quantity,
                    //memo
                    string       memo );

私有數據結構
智能合約須要存儲每種代幣的資料,還要存儲每一個帳戶持有每種代幣的數量。

private:
     //account 結構體,單個記錄帳戶存儲單個代幣的狀況
     struct account {
        //資產餘額
        asset    balance;
        //帳戶是否凍結
        bool     frozen    = false;
        //帳戶是否在白名單
        bool     whitelist = true;
        //設置帳戶主鍵爲代幣名稱
        uint64_t primary_key()const { return balance.symbol.name(); }
     };
     //currency_stats 結構體,記錄當代幣狀態信息
     struct currency_stats {
        //流通量
        asset          supply;
        //最大可流通量
        asset          max_supply;
        //發幣者
        account_name   issuer;
        //是否能夠凍結
        bool           can_freeze         = true;
        //是否能夠召回
        bool           can_recall         = true;
        //是否能夠設置白名單
        bool           can_whitelist      = true;
        //是否已經凍結
        bool           is_frozen          = false;
        //是否已經設置白名單
        bool           enforce_whitelist  = false;
        //設置主鍵爲代幣名稱
        uint64_t primary_key()const { return supply.symbol.name(); }
     };
     //設置一個multi_index類型,存儲 account 結構體
     typedef eosio::multi_index<N(accounts), account> accounts;
     //設置一個multi_index類型,存儲 currency_stats 結構體
     typedef eosio::multi_index<N(stat), currency_stats> stats;

私有函數
合約公有兩個私有函數,分別是給帳戶增長某種資產,和給帳戶減小某種資產。

//增長資產函數:帳戶,增長數量,代幣狀態結構體
     void sub_balance( account_name owner, asset value, const currency_stats& st );
     //減小資產函數:帳戶,減小數量   ,代幣狀態結構體
     void add_balance( account_name owner, asset value, const currency_stats& st,
                       //ram 資源支付者
                       account_name ram_payer );

以前的文章介紹了 eosio.token 智能合約的 hpp 文件,此次向你們介紹 eosio.token.cpp 文件,cpp 文件即 C++ 代碼文件,智能合約全部的業務邏輯內容都是在 cpp 文件中實現的。

eosio.token.cpp 文件地址: https://github.com/EOSIO/eos/blob/master/contracts/eosio.token/eosio.token.cpp

瞭解 C/C++ 開發的同窗確定熟悉,cpp 文件的主要使命是實現 hpp 文件中聲明的函數(方法),包括公有函數(EOS 裏也叫 action)和私有函數。hpp 裏挖的坑,cpp 要一個不留地實現。

私有函數
照慣例,私有函數都是工具函數,供類內部的其餘函數調用。

sub_balance(減資產)函數
做用:從指定帳戶中減去資產
參數:被操做帳戶,資產數,資產狀態

// 參數:被操做帳戶 資產種類與數量 資產狀態結構體
void token::sub_balance( account_name owner, asset value, const currency_stats& st ) {
//創建一個 multi_index,用來操做數據庫
//這裏的參數 _self 表示數據的擁有者爲智能合約自己,參數 owner 表示儲存在名爲被操做帳戶的表中
//這樣並非直接創建了一個新表,而是讓 C++ 程序與數據庫對應的表之間創建了數據傳輸的通道
accounts from_acnts( _self, owner );

//在數據表中查詢要減小的代幣結構體,就是 hpp 文件中定義的 account 結構體
const auto& from = from_acnts.get( value.symbol.name() );
//校驗,要減小的代幣數量應該小於目前擁有的代幣數量,不然會報錯。
eosio_assert( from.balance.amount >= value.amount, "overdrawn balance" );

//判斷是否有被操做帳戶的受權
if( has_auth( owner ) ) {
//校驗,帳戶是否被凍結
eosio_assert( !st.can_freeze || !from.frozen, "account is frozen by issuer" );
//校驗,這種代幣是否被凍結
eosio_assert( !st.can_freeze || !st.is_frozen, "all transfers are frozen by issuer" );
//校驗,帳戶是否在白名單中
eosio_assert( !st.enforce_whitelist || from.whitelist, "account is not white listed" );
//若是沒有被操做帳戶的受權,檢查是否有發幣者的受權
} else if( has_auth( st.issuer ) ) {
//若是有發幣者的受權,那麼確定是在召回代幣,查看代幣是否能夠召回
eosio_assert( st.can_recall, "issuer may not recall token" );
} else {
//若是兩種受權都沒有,則失敗,沒有足夠的權限
eosio_assert( false, "insufficient authority" );
}
//經過 Lambda 表達式(匿名函數)修改將代幣結構體
from_acnts.modify( from, owner, & {
//匿名函數 函數體
a.balance -= value;
});
}

add_balance(增長資產)函數
做用:從指定帳戶中增長資產
參數:被操做帳戶,資產數,資產狀態,存儲資源支付帳戶

// 參數:被操做帳戶 代幣數量 代幣狀態結構體 儲存支付帳戶
void token::add_balance( account_name owner, asset value, const currency_stats& st, account_name ram_payer )
{
//創建一個 multi_index,用來操做數據庫
accounts to_acnts( _self, owner );
//在數據表中查詢要增長的代幣結構體
auto to = to_acnts.find( value.symbol.name() );
//若是 to == to_acnts.end(),說明查找到數據表的末尾都沒有對應的結構體,說明該帳戶沒有該代幣
if( to == to_acnts.end() ) {
//校驗,該代幣是否開啓了白名單功能
eosio_assert( !st.enforce_whitelist, "can only transfer to white listed accounts" );
//使用 emplace 方法,在數據表中增長一項
to_acnts.emplace( ram_payer, &{
//匿名函數體,代幣數量等於每次轉入的數量,由於以前沒有
a.balance = value;
});
//若是數據表中已經存在此項,只需增長代幣數量
} else {
//檢查帳戶是否在白名單中
eosio_assert( !st.enforce_whitelist || to->whitelist, "receiver requires whitelist by issuer" );
//使用 modify 方法,修改項目
to_acnts.modify( to, 0, & {
//直接修改代幣數量
a.balance += value;
});
}
}

公有函數
EOS 合約中的公有函數大可能是供別的帳戶調用的 Action,根據 hpp 文件,咱們須要實現 create、issue、transfer 三個公有函數(action)。

create(新建代幣)函數
create 函數用來建立一種新的代幣,並設置這種新代幣的各類參數。

//參數:發幣帳戶

void token::create( account_name issuer,
//最大發行量
asset maximum_supply,
//發幣者是否能夠凍結代幣
uint8_t issuer_can_freeze,
//發幣者是否能夠召回代幣
uint8_t issuer_can_recall,
//是否能夠設置白名單
uint8_t issuer_can_whitelist )
{
//須要 eosio.token 帳戶自己的受權
require_auth( _self );

auto sym = maximum_supply.symbol;
//校驗,新代幣名稱是否有效
eosio_assert( sym.is_valid(), "invalid symbol name" );
//校驗,最大發行量是否有效
eosio_assert( maximum_supply.is_valid(), "invalid supply");
//校驗,最大發行量是否大於零
eosio_assert( maximum_supply.amount > 0, "max-supply must be positive");

//創建一個 milti_index 數據表,用來與數據庫交互
stats statstable( _self, sym.name() );
//在表中搜索相同名稱的代幣
auto existing = statstable.find( sym.name() );
//校驗,是否已經存在相同名稱的代幣
eosio_assert( existing == statstable.end(), "token with symbol already exists" );

//使用 emplace 方法,在數據表中增長一項
statstable.emplace( _self, [&]( auto& s ) {
   // 使用匿名函數,將傳入的參數賦值給 currency_stats 結構體
   s.supply.symbol = maximum_supply.symbol;
   s.max_supply    = maximum_supply;
   s.issuer        = issuer;
   s.can_freeze    = issuer_can_freeze;
   s.can_recall    = issuer_can_recall;
   s.can_whitelist = issuer_can_whitelist;
});

}

transfer(轉帳)函數
transfer 應該是這個智能合約最經常使用的函數,就是將代幣從一個帳戶轉到另外一個。

//轉出方帳戶名

void token::transfer( account_name from,
//轉入方帳戶名
account_name to,
//代幣種類與數量
asset quantity,
//轉帳備忘(目前還沒實現)
string /memo/ )
{
//打印轉帳提示
print( "transfer" );
//檢查轉出方權限
require_auth( from );
//獲得代幣名稱
auto sym = quantity.symbol.name();

//創建一個 milti_index 數據表,用來與數據庫交互
stats statstable( _self, sym );
//在數據表中尋找代幣的 currency_stats 結構體
const auto& st = statstable.get( sym );

//向轉出方獲取回執
require_recipient( from );
//向轉入方獲取回執
require_recipient( to );

//校驗,轉出的代幣是否有效
eosio_assert( quantity.is_valid(), "invalid quantity" );
//校驗,轉帳數量要大於0
eosio_assert( quantity.amount > 0, "must transfer positive quantity" );

//調用 sub_balance 私有方法
sub_balance( from, quantity, st );
//調用 add_balance 私有方法
add_balance( to, quantity, st, from );

}

issue(發幣)函數
上面的 create 函數建立代幣後只是給定了參數,並無真正的代幣被建立出來,須要 issue 函數進行發幣。

//參數:代幣接收方     代幣數量和種類      備忘

void token::issue( account_name to, asset quantity, string memo )
{
//打印提示
print( "issue" );
//獲取代幣名稱
auto sym = quantity.symbol.name();
//創建一個 milti_index 數據表,用來與數據庫交互
stats statstable( _self, sym );
//在數據表中搜索代幣 currency_stats 結構體
const auto& st = statstable.get( sym );

//檢查發幣者受權
require_auth( st.issuer );
//檢查資產是否有效
eosio_assert( quantity.is_valid(), "invalid quantity" );
//檢查資產是否大於零
eosio_assert( quantity.amount > 0, "must issue positive quantity" );
//檢查創造的總資產是否大於最大代幣數
eosio_assert( quantity <= st.max_supply - st.supply, "quantity exceeds available supply");

//更新資產創造數量記錄
statstable.modify( st, 0, [&]( auto& s ) {
   s.supply += quantity;
});

//給發佈者增長資產
add_balance( st.issuer, quantity, st, st.issuer );

//判斷代幣接受方是不是發幣者
if( to != st.issuer )
{
    //這裏使用了一個特殊處理,先給發幣者增長相應的代幣,再調用 transfer 函數轉帳給代幣接受方。
    //這樣作的目的是讓代幣接受方收到通知
   SEND_INLINE_ACTION( *this, transfer, {st.issuer,N(active)}, {st.issuer, to, quantity, memo} );
}

}

設置 action
你們知道 EOS 系統的智能合約是以 action 爲基本動做單位的,咱們要將須要聲明爲 action 的函數告知 EOS 系統,經過如下宏便可實現。

//將 create issue transfer 三個共有函數聲明爲 action,供其餘帳戶調用。
EOSIO_ABI( eosio::token, (create)(issue)(transfer) )
1
2
End ————————————————————————————————
圓方圓學院聚集大批區塊鏈名師,打造精品的區塊鏈技術課程。
許曉笛老師的CSDN學院視頻專欄 https://edu.csdn.net/lecturer/2008
郭金宏老師的csdn學院視頻專欄:https://edu.csdn.net/lecturer/2214

相關文章
相關標籤/搜索