转至元数据结尾
转至元数据起始

You are viewing an old version of this content. View the current version.

与当前比较 View Version History

« 上一页 版本 18 下一步 »

一、改进动机

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

投票系统

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

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

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

Dao 的标志与身份

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

工具与应用的支持

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

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

二、改进目标

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

  1. 基于快照的投票系统

  2. 将投票权重积累到一个具体的数值上,代表成员在这个 Dao 中的 reputation

  3. 每个成员有一个 NFT 用于标识该用户的身份,该 NFT 使不可转让的

  4. Dao 可以适用于不同规模的 Dao,比如 Starcoin Dao,DeFi 项目 Dao,或者一个几人合作的独立工作室作为 Dao合作的独立工作室作为 Dao

三、基本概念

Dao

代表 Dao 实体。 是否需要有一个类型标识来区分不同的 Dao?,比如 Dao<X> 代表 X Dao 。如果考虑到同一个 Dao 账号下只能有一个 Dao resource,不存在路径冲突问题,暂时不用考虑。

Dao 账号

每个 Dao 对应一个链上账号,该账号拥有 Dao 实体,它的 signer 被托管到 Dao 合约中,所有交易都必须通过 Dao 来发起。

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

Dao 成员 NFT

Dao 创建的时候,会同时发行一个 NFT,复用 Dao 的类型标识,DaoMemberNFT<X>(名字待定)。该 NFT 创建之后就立刻存入 IdentiferNFT 容器中,和用户绑定。成员加入 Dao 的过程就是领取 DaoMemberNFT 的过程。

Dao SBT

Dao 创建的时候,会同时发行一个 Token,复用 Dao 的类型标识,Token<X>。这个 Token 并不自由流通,而是锁在 DaoMemberNFT 的 Body 中,代表用户在 Dao 中积累的 reputation。之所以不直接用一个数字来代表 reputation,主要是这样可以复用 Token 的一些基础工具,比如总量追踪,比如安全计算。

Dao Governance

不同的 Dao 有不同的治理策略,如果可能,需要考虑支持插件式治理模式机制

Dao Proposal

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

Dao Proposal Action

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

四、使用场景预演

个人 Dao(Personal Dao)

类比个人独资公司。创始人拥有所有权限,不需要治理,但有和个人独立的账号和品牌。比直接使用个人账户的优势:

  1. 可以升级为其他类型的 Dao,适合初创的过渡阶段。

  2. 个人 Dao 可以转让所有权。

合伙 Dao (Partnerships Dao)

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

公共 Dao (Public Dao)

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

五、整体设计

文档参考: Move版 Snapshot 1.0需求列表

space:空间,负责跟踪提案、投票的全局设置,其包含多个提案,空间中有一些Meta data(头像,描述,主站信息等),DaoSpace用以负责描述空间信息,每个项目方均可通过调用DaoSpace::create 来创建一个space。

// DaoSpace.move
module DaoSpace {

  struct Space {
    voting_delay: u128, // The delay between when a proposal is created, and when the voting starts. A voting_delay of 0 means that as soon as the proposal is created, anyone can vote. A voting_delay of 3600 means that voting will start 3600 seconds after the proposal is created.
    voting_duration: u128, // The duration of the voting period, i.e. how long people will be able to vote for a proposal.
    meta_data: vector<u8>, // 空间元数据,编码后存储在链上(可考虑存在链下)
  }
  
  struct NFTMeta {
    user: address,
  }
  
  struct NFTBody {
     weight: u128
  }
  
  struct SpaceCapability {
  }

  // 创建 space,一个账号只能创建一个(是否只能一个账户创建一个,待讨论)
  public fun create(signer: &signer, voting_delay: u128, voting_duration: u128, meta_data: &vector<u8>): SpaceCapability;
  // 查询 space
  public fun query(broker: address) : (u128, u128, u128);
  // 非托管账号 register
  public fun register(signer: &signer, space_broker: address, cap: &SpaceCapability) : NFT<NFTMeta, NFTBody>;
  // 非托管账号 unregister
  public fun unregister(signer: &signer, nft: NFT<NFTMeta, NFTBody>);
  // NFT 添加权重
  public fun add_weight(signer: &signer, nft: &mut NFT<NFTMeta, NFTBody>, weight: u128);
  // NFT 减少权重
  public fun reduce_weight(signer: &signer, nft: &mut NFT<NFTMeta, NFTBody>, weight: u128);
}

DaoRegistry

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

module DaoRegistry{
  
  ///
  struct DaoRegistryEntry<phantom DaoT>{
    dao_id: u64,
    dao_address: address,
  }

  //这个方法必须由 Dao module 调用
  public(friend) fun register<DaoT>(dao_id: u64,dao_address: address){
    let genesis_account = //get 0x1 signer
    move_to(&genesis_account, DaoRegistryEntry{
        dao_id,
        dao_address,
      }
    )
  }
  
  public fun dao_address<DaoT>():address{
    *&borrow_global<DaoRegistryEntry<DaoT>>.dao_address
  }
}

Dao:代表治理的相关流程,该合约中处理底层Space与Proposal的拼装,将一些权限托管到该合约中,考虑还可以往上拆分,即将执行策略部分拆分到上层合约中。

// Dao.move
module Dao {
  use DaoSpace;

  struct DaoSignerDelegate has key {
    cap: SignerCapability, // 托管的签名(这里一旦放进来,就没法再还回去了,且只有一份,没想好是放在space还是放在这里,暂时放这里)
  }
  
  struct Dao {
    voting_delay: u128, // The delay between when a proposal is created, and when the voting starts. A voting_delay of 0 means that as soon as the proposal is created, anyone can vote. A voting_delay of 3600 means that voting will start 3600 seconds after the proposal is created.
    voting_duration: u128, // The duration of the voting period, i.e. how long people will be able to vote for a proposal.
    name: vector<u8>, //人类可识别的名称标识,是否需要?
    creator: address,
    meta_data: vector<u8>, // 空间元数据,编码后存储在链上(可考虑存在链下)
    cap: SpaceCapability
  }
  
  /// Proposal data struct.
  /// 是否需要通过 DaoT 来区分 Proposal,如果不需要,则需要把 DAO id 记录在 proposal 中
  /// 但 Action 的泛型无法消除
  struct Proposal<phantom DaoT: store, Action: store> has key {
        /// id of the proposal
        id: u64,
        /// creator of the proposal
        proposer: address,
        /// when voting begins.
        start_time: u64,
        /// when voting ends.
        end_time: u64,
        /// count of voters who agree with the proposal
        for_votes: u128,
        /// count of voters who're against the proposal
        against_votes: u128,
        /// executable after this time.
        eta: u64,
        /// after how long, the agreed proposal can be executed.
        action_delay: u64,
        /// how many votes to reach to make the proposal pass.
        quorum_votes: u128,
        /// proposal action.
        action: Option::Option<Action>,
        //在原来的 Proposal 字段上新增两个字段
        block_num: u64, // 快照高度
        root_hash: vector<u8> // 快照根hash
  }
  
  //Set 上也需要用泛型来区分   
  struct DaoProposalSet {
    proposal_set: HashSet<u64, DaoProposal>
  }
  
  //初始化过程中分配给 creator 的 cap
  struct RootCapability has key{}
  
  struct InstallPluginCapability has key{
  }
  
  struct ProposalPluginCapability<phontem ActionT> hash key{
     installer: address,
     //通过 BitSet 的不同位置标识插件的不同权限
     capabilities: BitSet,
  }
  
  struct DaoMemberMeta<phontem DaoT> has copy{
    user: address,
  }
  
  //这里是否需要 DaoT 来区分不同的 Dao?
  //如果不区分的话,同一个用户加入多个 Dao 的情况,当前的 IdentifierNFT 无法表达
  struct DaoMemberBody<DaoT>{
    sbt: Token<DaoT>,
  }
  
  // 执行策略:转账
  struct ExecutionTransferStrategy<phontem TokenT> {
    amount: u128,
    to: address,
  }
  
  // 直接创建 Dao
  public fun new_dao(
     creator: &signer, 
     voting_delay: u64, 
     voting_period: u64, 
     min_action_delay: u64) {
     //这里 Account 需要提供一个新的方法,允许用一个账号去创建另外一个 Delegate 账号。
     let signer_cap = Account::create_delegate_account(&signer);
     let dao_signer = Account::create_signer_with_cap(&siger_cap);
     //下面面的操作切换到 dao_signer 身份进行操作
     let dao = Dao{..};
     move_to(&dao_signer, dao);
     // 托管 Dao 账号的 SignerCapability 到该合约
     move_to(&dao_signer, DaoSignerDelegate{cap: signer_cap});
     
     issue_member_nft(creator,&dao_signer);
     
     //初始化 dao 的时候无法一次性完成,需要先把 cap 都存到 creator 账号下
     //然后按照 plugin 的方式逐步初始化
  }
  
  // 将一个账号直接升级为 Dao, sender 会变为 Dao 账号
  public fun upgrade_to_dao(sender:signer, ...) {
     //基本逻辑同上,省去了创建账号的流程
  }
  
  public fun dao_address<DaoT>(){
    
  }
  
  fun dao_signer<DaoT>(): signer {
    let signer_cap = borrow_global<DaoSignerDelegate>(dao_address<DaoT>());
    Account::create_signer_with_cap(&siger_cap)
  }
  
  public fun install_proposal_plugin<PluginT>(cap: &dao::InstallPluginCapability, sender:&signer, capabilites: vector<u8>){
      move_to
  }
  
  // 这里有个难题是 TokenT 从哪里来。
  // 一种方法是先生成一个账号,部署合约,然后升级为 dao
  // 另外一种方法是通过 Dao 的合约升级方式进行部署合约
  fun issue_member_nft<DaoT>(creator: &signer, dao_signer: &signer){
     Token::register<DaoT>(dao_signer);
     let basemeta = NFT::new_meta_with_image();
     NFT::register_nft_v2<DaoMemberMeta<DaoT>>(dao_signer, basemeta);
     let creator_address = Signer::address_of(creator);
     // issue 第一个 NFT 给 creator
     let meta = DaoMemberMeta<DaoT>{
        user: creator;
     };
     //如何初始化 creator 的 sbt?
     let sbt = Token::zero<DaoT>();
     let body = DaoMemberBody<DaoT>{
      sbt,
     }
     let nft = NFT::mint(basemeta, meta, body);
     IdentifierNFT::accept<DaoMemberMeta<DaoT>,DaoMemberBody<DaoT>>(creator);
     IdentifierNFT::grant(dao_signer, creator);
     
  }
     
  // 参与投票方/创建投票方注册到space
  public fun register_to_space(
      signer: &signer, 
      broker: address) {
      // 创建对应的NFT
  }

  // 参与投票方/创建投票方注册到space
  // 方便再上一层的DAO调用
  public fun regiter_to_space_get_nft(
      signer: &signer, 
      broker: address) : Option::Option<NFT<DaoSpace::NFTMeta, DaoSpace::NFTData>> {
  }
  
  // 根据 space_broker 来创建对应的proposal
  public fun create_proposal<ExecutionStrategy>(
      signer: &signer, // 创建者
      space_broker: u64, // space 代理人
      block_num: u64, // 快照高度
      root_hash: vector<u8> // 快照根hash
  );
  
  // 投票
  public fun do_vote(
      signer: &signer, 
      broker: address,
      id: u64, 
      amount: u128,
      choice: u8,
      proof: &vector<u8>,
      side_nodes: &vector<u8>) {
    // 证明...
    // 取本地NFT
    // 用NFT投票
    do_vote_with_nft(broker, id, choice, );
  }
  
  // 用NFT来投票
  // 方便上层DAO来调用
  public fun do_vote_with_nft(
    broker: address,
    id: u64,
    choice: u8,
    nft: Option::Option<NFT<DaoSpace::NFTMeta, DaoSpace::NFTData>>
  ) {
    
  }
}

proposal:提案,即一个提案代表一次投票过程,该部分跟原有的流程类似,但是去掉了TokenType,该合约只处理提案的相关处理过程,不做其他无关的事情。

// DaoProposal.move
module DaoProposal {

  struct ProposalCapability {
    proposal_id: u64,
  }

  struct Proposal<Action> {
    ... // 现有的一些结构
    proposal_id: u64,
    voting_system: u8, // 投票类型有单选投票、二次投票、排名投票、加权投票等投票类型
    voting_start_time: u64,
    voting_end_time: u64,
    voting_block_num: u64, // 投票快照高度
    voting_block_root: vector<u8>, // 投票快照高度的root hash
    action: Option::Option<Action>, // 执行策略的结构参数
  }
  
  struct Vote {
    /// vote for the proposal under the `proposer`.
    proposer: address,
    /// proposal id.
    id: u64,
    choie: u8, // 同意,反对,拒绝
    weight: u128, // 投票权重
  }
  
  public fun create_proposal<Action>(...,
      space_broker: address, 
      space_id: u64,
      root_hash: &vector<u8>): ProposalCapability;
  
  public fun cast_vote(
      signer: &signer,
      proposer_broker: address,
      id: u64, 
      amount: u128,
      choice: u8,
      cap: &ProposalCapability
    );
    
  public fun extract_strategy<Action>(id: u64) : Action;
};

Proposal action plugin

//所有的 proposal action 插件都需要提供两个公开的 entry script function,参数保持一致

module XProposalPlugin{
  use Dao::{Self,  ProposalPluginCapability}
  
  // XPlugin 的类型标识,用于保存配置
  struct XPlugin{
     cfg_a: u64,
  }
  
  struct XAction{
  }
  
  struct ProposalPluginCapabilityHolder{
     cap: ProposalPluginCapability<XAction>
  }
  
  fun new_action(args:vector<u8>): XAction{
       //bcs decode args
       //construct XAction
  }
  
  public fun plugin(sender: &signer){
    
  }
  
  public fun execute_proposal<DaoT>(sender: &signer, proposal_id: u64){
      let dao_address = Dao::get_dao_address<DaoT>();
      let cap_holder = borrow_global<ProposalPluginCapabilityHolder>(dao_address);
      let actoin = Dao::extract_proposal_action<DaoT, XAction>(&cap_holder.cap, sender, proposal_id);
      //execute the action
      //update the executor
  }
  
  //the entry script function
  public(script) fun execute_proposal_entry<DaoT>(sender: signer, proposal_id: u64){
        execute_proposal<DaoT>(&sender, proposal_id)
  }
  
  public fun propose<DaoT>(sender:&signer, args:vector<u8>, exec_delay: u64){
      let action = new_action(args);
      
      let dao_address = Dao::get_dao_address<DaoT>();
      let cap_holder = borrow_global<ProposalPluginCapabilityHolder>(dao_address);
      
      Dao::propose<DaoT, XAction>(&cap_holder.cap, sender, action, exec_delay, exec_delay);
  }
  
  public(script) fun propose_entry<DaoT>(sender:signer, args:vector<u8>, exec_delay: u64){
      propose(&sender, args, exec_delay)
  }
}

三、流程

TODO:

四、问题

  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. 投票策略类的扩展性;

五、参考

Snapshot 说明文档

Snapshot X 说明文档

https://github.com/snapshot-labs/sx-core/blob/develop/docs/milestones/1.md snapshot x 合约架构图

https://goerli.snapshotx.xyz/#/ 演示界面

https://mp.weixin.qq.com/s/7stRRj6zB2NarDk7VSitaQ

https://www.theblockbeats.info/news/30539

https://www.theblockbeats.info/news/30567

https://www.theblockbeats.info/news/30569

DAO 畅想曲 — jolestar (mirror.xyz)

CodeforDAO (github.com)

CodeforDAO 的诞生与自组织的互联网未来

  • 无标签