詳解 EOS 智慧合約的 cpp 檔案
之前的文章介紹了 eosio.token 智慧合約的 hpp 檔案,這次向大家介紹 eosio.token.cpp 檔案,cpp 檔案即 C++ 程式碼檔案,智慧合約所有的業務邏輯內容都是在 cpp 檔案中實現的。
eosio.token.cpp 檔案地址: github.com/EOSIO/eos/b…
瞭解 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) )
複製程式碼
相關文章和視訊推薦
圓方圓學院彙集大批區塊鏈名師,打造精品的區塊鏈技術課程。 在各大平臺都長期有優質免費公開課,歡迎報名收看。
公開課地址:ke.qq.com/course/3451…