前文 Rust智能合约养成日记(10-1)Sputnik DAO 概述 已为大家介绍了在区块链智能合约中引入DAO社区治理模式的重要性,并简要描述了SputnikDAO平台的主要功能。
本期摘要:
从本文开始,本系列合约代码解读将自顶向下地为大家介绍NEAR生态基础设施—Sputnik-DAO 平台。首先为大家带来的是 Sputnik_DAOv2::Factory Contract 的合约解读😊。
1. Sputnik-DAO 工厂合约
Sputnik-DAO 采用创建型工厂设计模式(Factory Pattern)实现了该平台下去中心化自治组织(DAO)的统一创建与管理。
本文将详细介绍 Sputnik-DAO 平台工厂模式(sputnikdao-factory)的设计实现。
对应合约的源代码仓库位于:https://github.com/near-daos/sputnik-dao-contract/tree/518ad1d97614fff4b945aba75b6c8bd2483187a2
📄为方便读者理解,以上提供了该合约的架构示意图供参考。
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账户地址。
代码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字符串的形式进行传递。
创建DAO实例所使用的sputnikdao-factory合约方法create()定义如下:
如下是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
以及未来该DAO内部治理策略的基本配置:Policy
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的内部代码实现:
该函数具体的处理逻辑为:
若上述步骤(3-6)中存在错误无法正常执行,此时在回调函数on_create()中通过调用near_sdk::is_promise_success()查询获得的Promise的返回结果将是false。此时将退还最初工厂合约create()方法调用者所attached_deposit的NEAR代币数额。
若上述步骤(3-6)执行准确无误,说明用户请求的新DAO实例合约(Sputnikdaov2)被正常创建。同时本工厂合约将记录追踪该DAO实例合约所部属的子账户地址。
如下是一个在工厂合约中实际成功部署新DAO实例的交易执行结果:
3.2 更新 DAO
在Sputnik-DAO平台中,DAO可通过该工厂合约进行升级(其他方式将在后续文章中进行介绍)。
如下为工厂合约所提供的合约接口update(),它将在底层调用factory_manager所提供的update_contract()接口。
代码位于:sputnikdao-factory2/src/lib.rs # Line136-149
factory_manager.update_contract()处理细节如下:该接口可实现对相应DAO实例合约中update()函数的调用。
值得一提的是:
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主网,因此没有造成损失。
由于项目方响应迅速,目前该漏洞通过增加合理的白名单校验机制已被正确修复😊
详见此 Fixing Commit: 518ad1d97614fff4b945aba75b6c8bd2483187a2🔗
4. Sputnik-DAO Factory合约安全性分析
上述发现并已修复的漏洞之外,Sputnik-DAO Factory合约的安全性主要还从如下几个方面进行保证:
【权限控制】合约开放的view类方法,不应修改合约的状态变量,即方法定义中的第一个参数需设置为self,而非mut self。
以下函数均未修改状态变量:
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🔗