1. 程式人生 > 實用技巧 >EOS基礎全家桶(十四)智慧合約進階

EOS基礎全家桶(十四)智慧合約進階

簡介

通過上一期的學習,大家應該能寫一些簡單的功能了,但是在實際生產中的功能需求往往要複雜很多,今天我就繼續和大家分享下智慧合約中的一些高階用法和功能。

使用docker編譯

如果你需要使用不同版本的CDT來編譯不同的合約,那麼這個方法是目前最有效的,使用虛擬機器器也是類似的原理,但是卻沒有docker更便利。如果你要通過安裝解除安裝在不同的CDT版本間切換,你這是最低效且難以維護的方式了。

之前我就介紹過了使用使用EOS Studio上傳的docker映象,我們開啟docker官方的Hub頁面,然後搜尋eosio.cdt,找到eostudio下的。這裡可以參考文章EOS基礎全家桶(十二)智慧合約IDE-VSCode

中的使用eosio.cdt的docker映象部分。

我們拉取需要的版本映象,比如我們現在獲取v1.4.1、v1.5.0和v1.6.3這三個版本的映象。

docker pull eostudio/eosio.cdt:v1.4.1
docker pull eostudio/eosio.cdt:v1.5.0
docker pull eostudio/eosio.cdt:v1.6.3

  

我們啟動時可以通過掛載相應的專案目錄到容器上,以便在容器內訪問到合約原始碼,然後使用容器內的CDT程式編譯合約。

比如,我的hello合約在~/Projects/hello目錄下,那麼我可以將~/Projects/hello/掛載到容器的/root/Projects/

。當然,你也可以直接掛在專案目錄。

docker run --rm -v ~/Projects/hello:/root/Projects/ -w /root/Projects/ eostudio/eosio.cdt:v1.6.3 sh -c "eosio-cpp -abigen hello.cpp -o hello.wasm"

  

表索引

因為合約表是multiindex型別的,支援多索引,我們在進行表查詢的時候,除了可以使用主鍵,還可以指定其他的索引進行查詢。

索引的定義

我們先來把前面的hello合約的my_friend表改改。增加一個第二索引,通過訪問時間排序和查詢。

private:
struct [[eosio::table]] my_friend
{
name friend_name;
uint64_t visit_time; uint64_t primary_key() const { return friend_name.value; }
double by_secondary() const { return -visit_time; }
}; typedef eosio::multi_index<"friends"_n, my_friend, indexed_by<"time"_n, const_mem_fun<my_friend, double, &my_friend::by_secondary>>> friends;

  

  1. 索引型別支援uint64_t,uint128_t,double,float128,ripemd160,sha256。但是常用的就uint64_tdouble,如果需要使用sha256的型別,可以使用fixed_bytes<32>

我們增加了一個第二索引的方法,返回的是double。double by_secondary() const { return -visit_time; }

  1. 索引預設使用正序排序,在合約中查詢表時要注意。如需倒序,可以利用負數,即返回double型別,在數值前取負數,則預設排序相當於變成了倒序。

這裡我們希望使用訪問時間倒序,所以在by_secondary方法中,我們返回了-visit_time,這樣索引取到的就是訪問時間的倒序了。

  1. 使用multi_index定義表型別的時候,增加indexed_by來定義索引。

我們是這樣定義索引的indexed_by<"time"_n, const_mem_fun<my_friend, double, &my_friend::by_secondary>>,time是定義了索引的名稱,查詢時需要使用;指定my_friend型別的by_secondary方法為索引獲取的方法,型別是double。

索引的使用

我們在hello合約中增加一個action,我們通過time索引查詢最近一個訪問的朋友是誰,然後再次拜訪他。

[[eosio::action]] void meetagain()
{
uint32_t now = current_time_point().sec_since_epoch(); auto time_idx = friend_table.get_index<"time"_n>();
auto last_meet_itr = time_idx.begin();
check(last_meet_itr != time_idx.end(), "I don't have a friend."); time_idx.modify(last_meet_itr, get_self(), [&](auto &f) {
f.visit_time = now;
});
}

  

這個方法中核心有三個,首先是通過表的get_index<>方法來獲取索引的物件。這裡泛型型別指定的就是name型別的索引名friend_table.get_index<"time"_n>()

然後是使用該索引物件來獲取所以資料,可以使用find方法來查詢,也可直接獲取begin和end位置的資料。因為我們是要獲取最近訪問的一條資料,該索引又是時間倒序的,所以我們取begin就是了。

最後要注意,對錶進行增刪改的時候,使用的也是索引物件的emplace、modify和erase方法,而非表物件的。

許可權驗證

這裡要說說許可權驗證了,這個可以說是非常重要的了。我們寫的合約,不一定是任何人都可以呼叫的,比如你轉賬,肯定只有使用者自己才能操作自己的資產吧,所以在合約中驗證呼叫者的許可權就很重要了。

但是,許可權驗證是很大的一塊功能,細說的話,我留到後面講解許可權管理的時候再說,今天只說說最簡單的驗證。

在合約中使用內建方法require_auth(user),這是一個強制驗證方法,如果不通過,就會直接報錯返回。引數user是name型別的賬號使用者名稱。假設我們要驗證是否是合約本身賬號呼叫的,只需require_auth(get_self())即可。

還有一種方式是驗證方法,返回true或者false的,has_auth(user),使用方式是一樣的,只是返回了驗證結果,而不會直接報錯。

內聯Action

我們通常呼叫程式內的方法都是直接引用,然後呼叫即可,但是在區塊鏈上,有時我們需要把這種呼叫關係向外暴露,那就需要使用到內聯交易了,這相當於在Action中又發起了對另一個Action的公開呼叫,在鏈上會有這兩次呼叫的記錄,並在同一個Transaction的記錄中,這就是內聯呼叫。

在鏈上我們看到過A將EOS轉給了B,B在收到EOS後立即轉給了C,而這部分EOS從A到B到C的操作都是一個trxid,這就是一個內聯Action呼叫的典型應用。

我們以官方的eosio.token合約的方法為例。我們看官方合約可以看到,一般都定義了標頭檔案,而Action和Table的定義都在標頭檔案中,標頭檔案裡還會有使用action_wrapper定義的各個Action的別名,比如:

using create_action = eosio::action_wrapper<"create"_n, &token::create>;
using issue_action = eosio::action_wrapper<"issue"_n, &token::issue>;
using retire_action = eosio::action_wrapper<"retire"_n, &token::retire>;
using transfer_action = eosio::action_wrapper<"transfer"_n, &token::transfer>;
using open_action = eosio::action_wrapper<"open"_n, &token::open>;
using close_action = eosio::action_wrapper<"close"_n, &token::close>;

  

這樣寫的好處就是為了方便合約內進行action呼叫。

同合約內,只需例項化一個對應action_wrapper型別的action,然後呼叫send方法將action引數傳入即可。

跨合約的呼叫,也只需引入包含該action_wrapper定義的標頭檔案即可。

以下是在eosio.system合約中進行記憶體購買時呼叫eosio.token的合約將手續費轉到eosio.ramfee賬號的操作。

token::transfer_action transfer_act{ token_account, { {payer, active_permission} } };
transfer_act.send( payer, ramfee_account, fee, "ram fee" );

  

token_account是token合約所在的賬號,就是eosio.token。
{ {payer, active_permission} }指明瞭該action呼叫需要使用的許可權是payer賬號的active許可權。(這裡能使用payer的許可權是因為該合約賬號是特權賬號)

send傳遞的引數是action的引數。我們知道transfer方法的引數有from、to、quantity和memo。
payer就是購買記憶體的使用者,是from。
ramfee_account是eosio.ramfee賬號,是to。
fee就是手續費,是quantity。
ram fee就是memo。

總結

今天是我們分享的第一篇合約進階,後續還會有很多分享,合約中還有很多功能和技巧,將更好的幫助你解決實際生產中的問題。