1. Sputnik-DAO 工廠合約Sputnik-DAO 採用創建型工廠設計模式(Factory Pattern)實現了該平台下去中心化自治組織(DAO)的統一創建與管理。
本文將詳細介紹Sputnik-DAO 平台工廠模式(sputnikdao-factory)的設計實現。
一級標題

一級標題
2. DAPP 模塊功能介紹
打開Sputnik DAO 平台的DAPP頁面,可見已經有不少去中心化自治組織在該平台中創建並定制了屬於自己的DAO實例對象(Sputnikdaov2合約)。
截止2022年03月,該平台下所創建最活躍的DAO為news.sputnik-dao.near,其中已有3051個提案(proposals)正在公開投票中或狀態已結。

📄為方便讀者理解,以上提供了該合約的架構示意圖供參考。

即所有基於Sputnik DAO 平台創建的DAO實例合約分別被部署在該NEAR賬戶的子賬戶下,例如:
pcp.sputnik-dao.near
test-dao-bro.sputnik-dao.near
blaqkstereo.sputnik-dao.near
octopode-dao.sputnik-dao.near
有關NEAR Protocol 中的子賬戶定義,可以在https://docs.near.org/docs/concepts/account#subaccounts 🔗 獲得參考。
如下圖所示,去中心化組織可在NEAR主網中公開發起交易,通過調用sputnikdao-factory合約所提供的create()方法,創建新的DAO實例。

3. sputnikdao-factory 合約代碼解讀
為幫助大家更好地了解Rust工廠模式合約的編寫方法,本文將深入解讀sputnikdao-factory的合約代碼。
3.1 創建DAO
sputnikdao-factory合約狀態主要由如下兩個部分組成:

factory_manager:合約主要的內部功能邏輯實現,提供了一系列創建/刪除/更新DAO實例的方法。
daos:採用集合數據結構,記錄了該平台歷史上所有已創建DAO實例的NEAR賬戶地址。
創建DAO實例所使用的sputnikdao-factory合約方法create()定義如下:

代碼3-5行的作用是將調用create()方法時函數參數所指定的用戶名name補全,以獲得未來部署DAO合約的NEAR子賬戶地址。此處env::current_account_id()指代了sputnikdao-factory合約的地址,即sputnik-dao.near。
代碼6-11行構造了create()方法在調用factory_manager.create_contract後回調函數on_create的函數參數。
代碼12-19行調用了工廠合約中factory_manager所提供的create_contract接口為create()方法調用者新建並部署新的DAO實例合約。同時,對於新部署的DAO實例合約,合約的基本配置信息可通過create_contract參數args以Base64字符串的形式進行傳遞。
如下是NEAR主網中某一去中心化組織在Sputnik-DAO平台中創建DAO實例合約所用的一筆交易:
FyECaggFxATGaUMrRKkbotRWAPkhjw5SBnZfRHpzSiQ8🔗
該筆交易調用了sputnikdao-factory合約代碼中的create()方法,實現了multicall.sputnik-dao.near子賬戶的創建,並成功部署了相應DAO實例的合約代碼(具體實現細節將在後文詳細展開說明)。


其中args參數Base64解碼後具體的內容為:
該內容正是部署multicall.sputnik-dao.near合約時,執行合約初始化方法new()時所需的合約配置信息。
下面本文將詳細剖析factory_manager.create_contract的具體實現:

該函數的參數具體說明如下:
code_hash:由Sputnik-DAO 平台所提供標準DAO實例合約模板代碼的哈希值。
account_id:未來新創建DAO實例合約的部署賬戶,例如multicall.sputnik-dao.near,該參數的內容已在create_contract()的上層函數create()中構造。
new_method:指定了新創建DAO實例合約中的合約初始化函數,一般為new()。
args:執行DAO實例合約初始化函數new()時所需的配置信息,同時包括如下兩個方面:
由去中心化自治組織所提供的DAO基本信息:Config

5. callback_method:指定了create_contract()方法執行完畢後的回調函數,用於維護處理新建DAO實例合約在本工廠合約中的信息。
6. callback_args:回調函數的函數參數。
該函數的執行主要分為如下幾個步驟:
代碼15-22行根據code_hash找到並載入工廠合約所提供的DAO實例合約模板代碼(wasm格式)到編號為0的寄存器中。
代碼23-25行構造一個Promise用於跟踪如下所有步驟(3-6)的處理結果。
代碼26-27行創建部署DAO實例合約的賬戶。
代碼28-29行為新創建的賬戶轉送NEAR代幣,這筆代幣源於最初工廠合約create()方法調用者所attached_deposit的數額。
代碼30-31行從0號寄存器讀取wasm代碼,並部署合約。
代碼32-41行調用DAO實例合約代碼的初始化函數new()。
最終DAO實例合約部署完畢後,將在factory_manager.create_contract()執行的末尾代碼32-53行回調on_create()函數。
如下是回調函數on_create的內部代碼實現:
該函數具體的處理邏輯為:
正文
正文
正文
代碼位於:sputnikdao-factory2/src/lib.rs # Line136-149
值得一提的是:
值得一提的是:
BlockSec在對Sputnik-DAO 代碼進行解析的過程中發現其Factory 合約中存在著一個嚴重的安全問題,會影響所有使用了Sputnik-DAO的合約。經與項目方聯繫後,最終該Issue被確認並及時修復。
💡該安全漏洞具體描述為:
在先前版本的代碼中,sputinikdao工廠合約所提供的public update()方法缺少瞭如下一個關鍵的斷言檢查。這導致了該方法可以被任何人調用。
而巧合的是,DAO實例合約(Sputnikdaov2合約)默認允許了可由Sputnik-DAO Factory通過跨合約調用實現本合約的升級。
DAO實例合約中實現的update()方法如下,代碼位於sputnikdao2/src/upgrade.rs # Line 62
上述代碼的第9行中,factory_info.auto_update該值在DAO實例合約部署調用new()方法進行初始化時被默認設置為True。
DAO實例合約new()方法實現如下:代碼位於sputnikdao2/src/lib.rs # Line 83-104
綜上,一位普通用戶(非Factory合約以及DAO合約本身)即可通過Factory合約所提供的pub fn update()方法實現對任意DAO合約的代碼升級(篡改),這會給Sputnik-DAO 平台以及所有依賴於Sputnik-DAO 平台的合約項目帶來極大的安全隱患。
🪴 好在,發現此問題時該版本代碼暫未上線NEAR主網,因此沒有造成損失。
正文
一級標題
正文
上述發現並已修復的漏洞之外,Sputnik-DAO Factory合約的安全性主要還從如下幾個方面進行保證:
以下函數均未修改狀態變量:
get_owner(&self)
get_number_daos(&self)
get_default_version(&self)
get_default_code_hash(&self)
get_daos(&self, from_index: u64, limit: u64)
get_dao_list(&self)
get_contracts_metadata(&self)
get_code(&self, code_hash: Base58CryptoHash)
【權限控制】合約開放的特權函數,這些函數只能由合約owner(或DAO合約賬戶)執行,並在方法中存在相應的assertion,例如:

由於項目方響應迅速,目前該漏洞通過增加合理的白名單校驗機制已被正確修復😊
詳見此Fixing Commit: 518ad1d97614fff4b945aba75b6c8bd2483187a2🔗