eosio.forum 智能合約開發教程第三期: 源代碼的深度解析

本文爲 dfuse 與 EOS Studio 合作內容,原文由 EOS Studio 發佈  

 

我們將在近一段時間內陸續推出多期的系列教程,深度詳解一些開源的 EOS 智能合約項目。我們將仔細挑選那些內容優質、設計精心、並可以成功構建的合約示例,其中的一些已經在 EOS 主網上廣泛使用。通過本次系列教程,我們希望能爲 EOSIO 上的 dApp 開發者提供更多的學習資料,並幫助他們瞭解更多智能合約的設計模式和應用場景。

第一期第二期中,我們討論了智能合約 eosio.forum 的設計動機,以及進行提案和投票的基本流程。本篇教程中,我們將會帶大家一起閱讀源碼,解析技術細節,理解這些智能合約的具體運行原理。

eosio.forum 合約可以大致分成四個部分:proposal (提案),vote (投票),status (狀態),post (發佈和回覆)。所有的 actions (調用合約的方法) 和 tables (表) 都放在 forum 這個類裏面。源碼中定義的 action 和 table 我們都提供了指向源代碼的鏈接,方便您在需要時快速查閱和引用。

提案

整個 proposal 部分包含了一個名爲 proposals 的 table 以及一些用來運行這個 proposals 的 actions: 

ACTION propose(eosio::name proposer, eosio::name proposal_name, string title, string proposal_json, eosio::time_point_sec expires_at)
TABLE proposals {
eosio::name proposal_name; // primary key
eosio::name proposer; // secondary key
string title;
string proposal_json;
eosio::time_point_sec created_at;
eosio::time_point_sec expires_at;
}

所有的賬號都可以通過執行 propose() 來創建一個新的提案。通過一些必要的參數檢查後,新的提案將會被存儲在 proposals 的表裏,其中所消耗的 RAM 將從提案者 proposer 的賬戶中扣除。每個 action 中的參數都對應着 table 中的的一列:

  • proposer 指的是創建提案的賬戶;
  • proposal_name 指的是 proposals 這個表的主鍵,這是一個提案的唯一標識;
  • title 指的是類型爲字符串,長度少於 1024 的提案標題;
  • proposal_json 指的是類型爲 JSON 格式的字符串,用以表示提案的具體描述,格式需要遵循 Proposal JSON Structure Guidelines 的規範;
  • expires_at 定義了投票的截止期限,這個時間點需要是 action 執行的時間到未來 6 個月之間

一旦某個提案創建了,只要在 expires_at 的時間內,任何賬戶(包括提案者 proposer 自己)都可以通過 vote() 的這個 action 來投票。

 

ACTION expire(eosio::name proposal_name)

 

這個 action 允許提案者 proposer 提前結束他/她的提案,並且立刻終止投票。這個操作的本質實際上是把 expires_at 字段修改成了當前的時間。 


 

proposal_table.modify(itr, proposer, [&](auto& row) {
row.expires_at = current_time_point_sec();
});

 

只有初始提案者 proposer 纔可以通過調用 expire() 來提前結束提案。如果在一個不存在的,或者已經結束的提案中調用此 action,系統會返回錯誤信息。


 

ACTION clnproposal(eosio::name proposal_name, uint64_t max_count)

 

當一個提案的凍結時間超過 3 天后(通過 FREEZE_PERIOD_IN_SECONDS 來設置這個時間),我們就可以通過這個 action 來移除該提案。


 

bool can_be_cleaned_up() const { return current_time_point_sec() > (expires_at + FREEZE_PERIOD_IN_SECONDS); }

 

操作 clnproposal() 將清除與這個提案相關的所有投票記錄。由於每次刪除的數量由 max_count 來限制,因此這個操作採用不斷迭代的方式,通過多次調用這個 action ,直到所有的投票記錄被刪除。

auto index = vote_table.template get_index<"byproposal"_n>();
auto vote_key_lower_bound = compute_by_proposal_key(proposal_name, name(0x0000000000000000));
auto vote_key_upper_bound = compute_by_proposal_key(proposal_name, name(0xFFFFFFFFFFFFFFFF));
auto lower_itr = index.lower_bound(vote_key_lower_bound);
auto upper_itr = index.upper_bound(vote_key_upper_bound);
uint64_t count = 0;
while (count < max_count && lower_itr != upper_itr) {
lower_itr = index.erase(lower_itr);
count++;
}

 

請注意,二級索引 byproposal 是用來通過 proposal_name(請查看 vote 表) 來查詢和迭代所有給定投票記錄的,一旦所有相關的投票記錄都被移除,提案本身也將會被刪除。

 

if (lower_itr == upper_itr && itr != proposal_table.end()) {
proposal_table.erase(itr);
}

 

這個操作可以有效清理提案以及它的投票記錄所佔用的所有 RAM 資源。任何人都可以調用  clnproposal() 這個 action,因爲這個操作僅接受對已經完成且凍結期結束的提案執行。我們鼓勵所有投票者、提案者以及社區的成員調用 clnproposal() 來清理過期提案,減少 RAM 資源的佔用。

 

投票

投票部分包含了一個名爲 vote 的 table 以及 vote() 和 unvote() 兩個 action。 

ACTION vote(eosio::namevoter, eosio::nameproposal_name, uint8_t vote, string vote_json)
ACTION unvote(eosio::namevoter, eosio::nameproposal_name)
TABLE vote {
uint64_t id; // primary key
eosio::name proposal_name; // secondary key
eosio::name voter; // secondary key
uint8_t vote;
string vote_json;
eosio::time_point_sec updated_at;
}

 

對於尚未截止的提案,任何賬戶都可以使用 vote() 來進行投票,這將會消耗投票者的少量 RAM 資源 (430 字節) ,用以把投票信息保存在 vote 表中。

 

vote 表中具體含義由 vote 的字段來表示:

  • 0 代表拒絕票
  • 1 代表同意票
  • 255 代表棄權票
  • 其他值可以用來表示其他含義

 

在 vote 表中,主鍵 id 是自動生成的。二級索引 proposal_name 和 voter 是爲了使用 proposal 或者 voter 字段進行搜索,而 vote_json 字段則是用來記錄一個投票記錄的額外信息,例如可以記錄投票人投票時的一些看法等。

 

投票人可以通過再次調用 vote() 來修改他/她的投票,或者調用 unvote() 來把他/她的投票記錄從 vote 表中刪除。移除有效的投票可以拿回存儲這個投票記錄所佔用的 RAM 資源,相應的,這個投票記錄將不再被這個提案所記錄。

 

vote() 和 unvote() 這兩個 action 會首先檢查其提案是否處於投票階段,如果一個提案已經超過了投票期限,和投票相關的操作將會被系統拒絕。


 

bool is_expired() const { return current_time_point_sec() >= expires_at; }

 

因此,當一個提案的投票階段結束後,系統可以保證所有投票記錄不能被修改,大家便可以着手清點和計算投票結果了。

 

 

狀態 

ACTION status(eosio::name account, string content)
TABLE status {
// scope is self
eosio::name account; // primary key
string content;
eosio::time_point_sec updated_at;
}

 

status() 會記錄與之關聯的 account 賬號的狀態,如果參數 content 爲空,這個 action 會移除之前的狀態;如果不爲空,將會在 status 表中新增或者修改這個 account 賬號所對應的狀態記錄。

 

發佈和回覆 

ACTION post(eosio::name poster, string post_uuid, string content, eosio::name reply_to_poster, string reply_to_post_uuid, bool certify, string json_metadata)
ACTION unpost(eosio::name poster, string post_uuid)

 

我們也可以通過 post() 和 unpost() 來發布帖子和回覆,不過這兩個 action 僅驗證參數,並不會將數據存在數據庫中。因此所有的帖子和回覆內容都不會保存在 RAM 中,他們只能通過鏈上的交易記錄來查看。因此,需要一些鏈下工具來爲 post() 和 unpost() 這兩個 action 的數據提供排序、展示、計數和統計報告等服務。例如,Novusphere 按照他們的數據格式,爲用戶提供了一個有用戶界面的應用來展示和分類帖子。他們使用 eosio.forum 合約作爲後端服務,並提供了一個基於 EOSIO 的類似 Reddit 的網頁應用。

 

 

下一步是什麼?

如果您覺得本教程有幫助,請別忘了點贊或關注我們的微信公衆號黑曜石實驗室 (Obsidianlabs),幣乎號 EOSStudio,我們會持續更新更多的產品信息、技術文章和精彩內容。

 

深度解析 EOS 合約:eosio.forum

- 第一部分: EOSIO 公投系統

- 第二部分: 投票的流程解析

- 第三部分: 源代碼的深度解讀

 

非常感謝 dfuse 團隊爲本期教程的編寫提供的諸多幫助!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章