Rust 智能合约养成日记(10-2)Sputnik DAO::Factory合约解读
往期回顾:
前文 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)正在公开投票中或状态已结。
通过在NEAR Explorer中探索,我们不难发现,该平台各DAO实例合约由NEAR账户sputnik-dao.near
(sputnikdao-factory合约)统一部署。
即所有基于 Sputnik DAO 平台创建的DAO实例合约分别被部署在该NEAR账户的子账户 下,例如:
有关 NEAR Protocol 中的子账户定义,可以在 https://docs.near.org/docs/concepts/account#subaccounts 🔗 获得参考。
如下图所示,去中心化组织可在NEAR主网中公开发起交易,通过调用sputnikdao-factory
合约所提供的create()
方法,创建新的DAO实例。
为帮助大家更好地了解Rust工厂模式合约的编写方法,本文将深入解读sputnikdao-factory
的合约代码。
创建DAO实例所使用的sputnikdao-factory
合约方法create()
定义如下:
create()
方法时函数参数所指定的用户名name
补全,以获得未来部署DAO合约的NEAR子账户地址。此处env::current_account_id()
指代了sputnikdao-factory
合约的地址,即sputnik-dao.near
。create()
方法在调用factory_manager.create_contract
后回调函数on_create
的函数参数。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
的具体实现:
该函数的参数具体说明如下:
multicall.sputnik-dao.near
,该参数的内容已在create_contract()
的上层函数create()
中构造。new()
。new()
时所需的配置信息,同时包括如下两个方面:
由去中心化自治组织所提供的DAO基本信息:Config
以及未来该DAO内部治理策略的基本配置:Policy
create_contract()
方法执行完毕后的回调函数,用于维护处理新建DAO实例合约在本工厂合约中的信息。该函数的执行主要分为如下几个步骤:
code_hash
找到并载入工厂合约所提供的DAO实例合约模板代码(wasm格式)到编号为0的寄存器中。Promise
用于跟踪如下所有步骤(3-6)的处理结果。create()
方法调用者所attached_deposit
的数额。new()
。最终DAO实例合约部署完毕后,将在factory_manager.create_contract()
执行的末尾代码32-53行回调 on_create()
函数。
如下是回调函数on_create
的内部代码实现:
该函数具体的处理逻辑为:
on_create()
中通过调用near_sdk::is_promise_success()
查询获得的Promise
的返回结果将是false
。此时将退还最初工厂合约create()
方法调用者所attached_deposit
的NEAR代币数额。如下是一个在工厂合约中实际成功部署新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合约的安全性主要还从如下几个方面进行保证:
&self
,而非&mut self
。
以下函数均未修改状态变量:如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!