1. 程式人生 > >EOS 智能合約案例解析(2)

EOS 智能合約案例解析(2)

EOS 智能合約

詳解 EOS 智能合約的 cpp 文件

之前的文章介紹了 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, [&]( auto& a ) {      //匿名函數 函數體   
       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, [&]( auto& a ){        //匿名函數體,代幣數量等於每次轉入的數量,因為之前沒有
        a.balance = value;
      });      //如果數據表中已經存在此項,只需增加代幣數量
   } else {      //檢查賬戶是否在白名單中
      eosio_assert( !st.enforce_whitelist || to->whitelist, "receiver requires whitelist by issuer" );      //使用 modify 方法,修改項目
      to_acnts.modify( to, 0, [&]( auto& a ) {        //直接修改代幣數量
        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) )


EOS 智能合約案例解析(2)