Starcoin DAOSpace:基于 SBT 的 DAO 框架

一、改进动机

Starcoin 在主网启动之时就在引入 DAO 以及链上治理,这一年里进行了十多次系统合约升级以及配置变更的投票治理,链上参与者也越来越多,运行稳定,无安全事故,但也发现了一些当前 DAO 系统的不足之处。

投票系统

当前的投票系统基于 Token 抵押模式,带来几个问题:

  1. 投票成本比较高。投票期间 Token 被锁定,尤其随着 DeFi 生态的发展,用户的投票机会成本会越来越高。这样的投票方式也很难并行发起几个投票提案。

  2. 没有 Token 的应用无法进行治理。如果尚未发布 Token 的应用或者社群,无法进行投票。

DAO 的标志与身份

当前的 DAO 系统假定一个 Token 代表一个 DAO,所有的 Token 持有者都是 DAO 的成员。DAO 对外没有一个账号地址标志,成员没有独立的身份标志,不利于社群类型的 DAO 发展,也很难适配有准入机制的 DAO。

工具与应用的支持

由于以上不足,当前的 DAO 只提供了一个投票界面,用于发起投票。没能提供一个用户友好的,创建和管理 DAO 的工具。

 

所以我们计划对当前的 DAO 进行改进升级,以解决以上不足。

二、改进目标

基于以上分析,我们设定以下改进目标:

  1. 将投票权重积累到一个具体的数值上,这个数值用一种不可转让的 Token 来表达,可以称为灵魂绑定的 Token(Soulbound Token),简写为 SBT。

  2. 设计基于快照的投票系统,用户使用快照时的 SBT 的数值来投票,可以同时给多个提案投票。

  3. 每个成员有一个成员 NFT 用于标识该用户的身份,该 NFT 是不可转让的,通过 StarcoinFramework 中的 IdentifierNFT 来实现,上面的 SBT 嵌入到 NFT 中,同时实现不可转让。

  4. DAO 可以适用于不同规模的 DAO,比如 Starcoin DAO,DeFi 项目 DAO,或者一个几人合作的独立工作室 DAO。不同规模的 DAO 之间可以无缝切换。

三、基本概念

DAOSpace

StarcoinFramework 中的 DAO 相关的系统合约。它是一个用来创建 DAO 的框架,提供有扩展能力的插件机制,可以让开发者组合出适合自己项目场景的 DAO。同时它也通过 DApp 方式提供一种简易的组合方式,让普通用户可以通过无代码的方式,利用系统合约内置以及第三方提供的插件来组合出符合自己需求的 DAO。

DAO 账户(DAO Account)

每个 DAO 对应一个链上账户,该账户拥有代表 DAO 的 Resource。它的 signer 被托管到 DAO 合约中,所有交易都必须通过 DAO 来发起。

创建 DAO 的流程实际上是 DAO 发起者,先创建 DAO 账号,然后部署 DAO 合约,再创建 DAO 实体,并将 DAO 实体写入 DAO 账号,同时将 DAO 账号的 signer 托管给 DAO 的过程。

DAO

每个 DAO 有一个代表该 DAO 的 Resource 保存在 DAO 账号下。 每个 DAO 需要有一个类型标识,相当于该 DAO 的全局 ID,当 DAO 创建并注册的时候,将该类型和 DAO 进行关联。

比如有个叫 XDAO 的类型标识类似 0x_dao_address::XDAO::XDAO

每个 DAO 代表一个合约控制的账户,以及在合约中定义的一组“能力(Capability)”,DAO 的成员可以通过发起提案以及 DAO 插件使用 DAO 的能力。

当前 DAO 定义了以下能力:

  • Install plugin capability:给 DAO 安装插件的能力

  • Upgrade module capability:升级 DAO 账户下合约的能力

  • Modify dao config capability:修改 DAO 配置的能力

  • Withdraw Token capability:从 DAO 账户下提取 Token 的能力

  • Withdraw NFT capability:从 DAO 账户下提取 NFT 的能力

  • Storage capability:给 DAO 账户下保存数据的能力

  • Member capability:给 DAO 添加成员,以及修改 DAO 成员 SBT 的能力

  • Proposal capability:给 DAO 发起提案的能力

DAO 注册表(DAO Registry)

每个 DAO 创建的时候,会将自己的类型标识和 DAO 进行关联,记录在 DAO 注册表中,合约中可以通过 DAO 的类型标识来反向查询 DAO 的账户地址。

DAO 成员 NFT(DAO Member NFT)

DAO 创建的时候,会同时发行一个 NFT,复用 DAO 的类型标识,DAOMemberNFT<X>。该 NFT 创建之后就立刻存入 IdentiferNFT 容器中,和用户绑定。成员加入 DAO 的过程就是领取 DAOMemberNFT 的过程,退出 DAO 的过程就是解除绑定并销毁 NFT 的过程。

关于 IdentiferNFT 的介绍参看 SIP22

DAO SBT

DAO 创建的时候,会同时发行一个 Token,复用 DAO 的类型标识,Token<X>。这个 Token 并不自由流通,而是锁在 DAOMemberNFT 的 Body 中,代表用户在 DAO 中治理投票的权重。不同的 DAO 可以根据自己的场景来映射 SBT,用来代表不同的含义。

DAO 提案(DAO Proposal)

DAO 中发起的治理提案,通过治理机制决定提案是否执行,Proposal 是抽象的,具体的 Proposal 如何执行取决于 DAO 的 Action。

DAO Proposal Action

DAO Proposal Action 决定了具体的 Proposal 的行为,可以由第三方作为插件提供,每个 DAO 可以安装不同的插件。

DAO 插件(DAO Plugin)

DAO 插件是给 DAO 提供扩展的主要方式,通过不同插件的组合,可以让 DAO 适用于不同的场景。每个插件安装时需要表明自己需要使用的“能力(Capability)”,插件可以在必要时从 GenesisDAO 申请能力的使用权限进行调用。

四、使用场景预演

个人 DAO(Personal DAO)

类比个人独资公司。发起人拥有对 DAO 的直接控制力。

  1. 可以逐渐无缝升级为其他类型的 DAO,适合初创的过渡阶段。

  2. 个人 DAO 可以转让所有权,比使用个人账户建立品牌有优势。

使用场景:个人自媒体,尚在寻求合伙人的初创项目。

合伙 DAO (Partnerships DAO)

类比有限责任合伙公司。几个合伙人根据出资比例或者其他条件分配治理权重,决策提案需要通过一定的投票阈值通过(比如 50%)才能执行,包括引入新的成员。

  1. 可以通过开放准入条件,无缝升级为 Public DAO。

  2. 内部共识即可引入新成员或者重新分配权重,无额外成本。

  3. 内部决策通过合约自动执行,保证执行力,相对传统公司可支持更分散的治理权结构。

使用场景:多人联合自媒体,合伙人初创项目

公共 DAO (Public DAO)

类比上市的股份有限责任公司。参与 DAO 的成员无限制,只要符合一定的公开条件,比如持有或者抵押某种 Token 等,即可加入 DAO,成为 DAO 的成员,并且治理权重根据公开条件计算。

  1. DAO 数据链上公开透明,支持第三方随时审计。

  2. 治理权重根据合约计算得出,并不和持有的 Token 强绑定。

使用场景:公开发行 Token 的项目

比如,创始人 Alice 开始有了一个想法,于是创建了一个 ADAO,这时候成员只有创始人一人,所以他的 SBT 权重为 100%,所有的提案他一个人就可以通过。后来他招募了两个合伙人,Bob 和 Tom,每人占 1/3 的 SBT 权重,所有的提案至少需要 2 个人同意(DAO 投票阈值为 50%的情况),ADAO 变成了合伙 DAO。再后来,ADAO 发行了一个 Token,安装了 Stake 插件,抵押该 Token 的自动成为 DAO 成员,成员 SBT 根据抵押时长计算,该 DAO 成为一个公共 DAO。

五、整体设计

 

Entity Relationship

DAORegistry

DAO 注册中心,保存在 0x1 账号下的全局注册表,提供 DAOT → DAO address 的反向索引

 

module DAORegistry{ /// Global DAO registry info struct DAORegistry has key{ next_dao_id: u64, } /// Registry Entry for record the mapping between `DAOT` and `dao_address` struct DAORegistryEntry<phantom DAOT> has key{ dao_id: u64, dao_address: address, } // This function should call from GenesisDAO module public(friend) fun register<DAOT>(dao_address: address): u64; public fun dao_address<DAOT>():address; }

 

DAOAccount

提供通用的 DAO 账户创建和管理能力。DAO 账户是对 SignerDelegated 账户的一种再封装,DAO 账户创建后,升级策略自动更新为两阶段升级策略。拥有 DAOAccountCap 即可拥有对该 DAOAccount 的控制权。

module DAOAccount{ /// DAOAccount struct DAOAccount has key{ dao_address: address, signer_cap: SignerCapability, upgrade_plan_cap: UpgradePlanCapability, } /// This capability can control the DAO account struct DAOAccountCap has store, key{ dao_address: address, } /// Create a new DAO Account and return DAOAccountCap /// DAO Account is a delegate account, the `creator` has the `DAOAccountCap` public fun create_account(creator: &signer): DAOAccountCap; /// Provide a function to create signer with `DAOAccountCap` public fun dao_signer(cap: &DAOAccountCap): signer; public fun submit_upgrade_plan(cap: &DAOAccountCap, package_hash: vector<u8>, version:u64, enforced: bool); }

 

GenesisDAO

定义 DAO,DAO 的 Capability,DAO 的提案处理流程。

 

module GenesisDAO { struct DAO has key { id: u64, name: vector<u8>, dao_address: address, next_member_id: u64, } /// Configuration of the DAO. struct DAOConfig has copy, drop, store { /// after proposal created, how long use should wait before he can vote (in milliseconds) voting_delay: u64, /// how long the voting window is (in milliseconds). voting_period: u64, /// the quorum rate to agree on the proposal. /// if 50% votes needed, then the voting_quorum_rate should be 50. /// it should between (0, 100]. voting_quorum_rate: u8, /// how long the proposal should wait before it can be executed (in milliseconds). min_action_delay: u64, /// how many STC should be deposited to create a proposal. min_proposal_deposit: u128, } public fun withdraw_token<DAOT: store, PluginT, TokenT: store>(_cap: &DAOWithdrawTokenCap<DAOT, PluginT>, amount: u128): Token<TokenT>; public fun join_member<DAOT: store, PluginT>(_cap: &DAOMemberCap<DAOT, PluginT>, to_address: address, init_sbt: u128); }

DAOSBT

DAOMember NFT 的定义以及成员加入时分配 NFT 以及 SBT

DAOTemplate

每个 DAO 初始化的时候都会根据这个模版生成一个命名为 XDAO 的 module,并且初始化插件。

 

Proposal

提案代表一次治理过程,DAO 的成员根据 SBT 的快照来进行投票。

Proposal action plugin

以成员加入的 Proposal Plugin 为例,create_proposal 方法会在 DAOT 代表的 DAO 中创建一个 Proposal。该 DAO 的成员进行投票,按照治理流程对 Proposal 进行决策,如果到达可执行状态,则任何人可以调用 execute_proposal 方法执行该提案,执行后,MemberJoinAction 中的 member 成为 DAO 的成员,并且得到了 init_sbt 个 SBT。

 

TODO 详细描述提案处理流程

六、流程

创建流程

  1. 用户 A 发起交易,先创建合约账号 X, A 拥有 X 的控制权。

  2. 用户通过 DApp 界面填写配置,选择需要安装的插件,DApp 根据 DAO Template 生成 XDAO module,以及初始化的参数。

  3. 浏览器编译 XDAO module,并且通过两阶段升级的方式,由 A 来发起交易,将 XDAO module 部署到 X 账号下,并通 init_script 初始化 XDAO 以及插件。

  4. DAO 创建完成,进入治理阶段。

七、问题

  1. Move实现层面,区分不同的项目方来创建DAO,是通过Template Type 来对其进行异化还是通过 address+id的方式,若按照前者,如何解决合约动态部署的问题?

    这个当前有两个方案,这个 issue 中提了两个方案 [Feature Request] Support Issue a Token on webpage · Issue #4 · starcoinorg/dapps (http://github.com ) ,未来可以支持合约中部署合约。

  2. Snapshot中,存在不同的准入策略,即参与/创建投票的人在DAO中创建/参与投票的时候会有一个准入的门槛,这个准入的门槛是一系列判断条件,这个条件有预置的也有用户。在solidity中可以动态调用,故可以通过一个ABI的字符数组来进行表示,实际执行的时候也是动态去调用这个ABI,且这些策略是可以拼接的。而Move中如果也按照这个思路显然无法实现

    感觉可以简化一下,小额抵押某种 Token 即可,如果是发垃圾投票就通过投票没收。

  3. 类似的,Snapshot也存在不同的Execution Action,在Move中也可以通过预置Execution的模式去实现,是否还有其他的实现思路?

    当前 DAO 的 Action 是支持第三方扩展的,并不是只能用预置的 Action。
    执行在前端触发。

  4. 治理权重的计算(基于长线hodl:锁仓时长、锁仓量),基于余额的权重计算有难度。锁仓有激励。

    1. 不同类型项目采用不同机制。Swap自实现weight维护。

  5. 扩展点的设计,包括Action等动态调用的扩展,通过interface形式,前端来触发。DAO的平滑升级如何扩展?

    1. Action的扩展性;

    2. DAO类型的扩展性,不同DAO类型如何转换;

    3. 投票权重的扩展性;

    4. 投票策略类的扩展性;

八、实现

合约:[genesis_dao] Implement GenesisDAO by jolestar · Pull Request #32 · starcoinorg/starcoin-framework (github.com)

参考

DAO Reference Link