版本比较

密钥

  • 该行被添加。
  • 该行被删除。
  • 格式已经改变。

...

代码块
breakoutModewide
languagerust
// Dao.move
module Dao {
  use DaoSpace;

  struct DaoSignerDelegate has key {
    cap: SignerCapability, // 托管的签名(这里一旦放进来,就没法再还回去了,且只有一份,没想好是放在space还是放在这里,暂时放这里)
  }
  
  struct Dao {
    id: u64,
    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 ProposalPluginInfo<PluginConfig has store> hash key{
     installer: address,
     //通过 BitSet 的不同位置标识插件的不同权限
     capabilities: BitSet,
     config: Option<PluginConfig,
  }
  
  // DaoWithdrawCapability 是一次性的 capability,不能 store,可以 drop
  struct DaoWithdrawCapability has drop{
  }
  
  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, ...) {
     //基本逻辑同上,省去了创建账号的流程
  }
  
  ///这里好像没办法检测 DaoT 和 dao_address 的关系
  public fun register<DaoT>(creator: &signer, dao_address: address){
      let dao = borrow_global<Dao>(DaoRegsitry::dao_address<DaoT>());
      //check the dao creator and creator args
      DaoRegistry::register<DaoT>(dao_address, dao.id)
  }
  
  fun dao_signer<DaoT>(): signer {
    let signer_cap = borrow_global<DaoSignerDelegate>(DaoRegsitry::dao_address<DaoT>());
    Account::create_signer_with_cap(&siger_cap)
  }
  
  /// _access_key 确保这个调用来自 PluginT 的 Module
  public fun install_proposal_plugin<DaoT, PluginT>(cap: &dao::InstallPluginCapability, sender _access_key: &PluginT, installer:&signer, capabilites: vector<u8>){
      let dao_signer = dao_signer<DaoT>();
      move_to(&dao_signer, ProposalPluginCapability<PluginT>{
        installer: Signer:address_of(senderinstaller),
        capabilites: BiteSet::from_bytes(capabilites),
      })
  }
  
  //public fun borrow_plugin 参数这里没有用,只是确保这个调用来自 PluginT 的实现模块
  public fun extract_proposal_plugin_capability<DaoT, PluginT>(_withdraw_capability<DaoT, PluginT>(plugin: &PluginT): DaoWithdrawCapability ProposalPluginCapability<PluginT>{
      //  move_from<ProposalPluginCapability<PluginT>>(dao_address<DaoT>())
  }check capability with ProposalPluginCapability
      //DaoWithdrawCapability
这里有个难题是 TokenT 从哪里来。}
  //
一种方法是先生成一个账号,部署合约,然后升级为 dao public fun // 另外一种方法是通过 Dao 的合约升级方式进行部署合约
  fun issue_member_nft<DaoT>(creator: &signer,withdraw<DaoT, TokenT>(cap: DaoWithdrawCapability, amount: u64): Token<TokenT>{
    let dao_signer: &signer){ = dao_signer<DaoT>();
     TokenAccount::register<DaoT>withdraw<TokenT>(dao_signeramount);
  }
  
let basemeta = NFT::new_meta_with_image();
struct PluginStorageItem<PluginT, V>{
    NFT::register_nft_v2<DaoMemberMeta<DaoT>>(dao_signer, basemeta);
item: V,
  }
  
 let creator_addresspublic = Signer::address_of(creator);
     // issue 第一个 NFT 给 creator
 fun move_to<DaoT, PluginT, V>(_access_key: &PluginT, item: V){
    let dao_signer = dao_signer<DaoT>();
   let meta = DaoMemberMeta<DaoT> move_to(&dao_signer, PluginStorageItem<PluginT>{
      item
 user: creator;  });
   };
  
  //如何初始化 creator这里有个难题是 TokenT sbt?从哪里来。
  // 一种方法是先生成一个账号,部署合约,然后升级为 dao
let sbt = Token::zero<DaoT>();
 // 另外一种方法是通过 Dao 的合约升级方式进行部署合约
   let body = DaoMemberBody<DaoT>fun issue_member_nft<DaoT>(creator: &signer, dao_signer: &signer){
      sbt,Token::register<DaoT>(dao_signer);
      }
     let nftbasemeta = NFT::mint(basemeta, meta, body);new_meta_with_image();
     IdentifierNFTNFT::accept<DaoMemberMeta<DaoT>,DaoMemberBody<DaoT>>(creator);register_nft_v2<DaoMemberMeta<DaoT>>(dao_signer, basemeta);
     let creator_address = IdentifierNFTSigner::grant(dao_signer, address_of(creator);
     // issue 第一个 }NFT  creator
     let // 参与投票方/创建投票方注册到space
meta = DaoMemberMeta<DaoT>{
 public fun register_to_space(       signeruser: &signer,creator;
     };
 broker:  address) { //如何初始化 creator  sbt?
  // 创建对应的NFT  let }sbt = Token::zero<DaoT>();
 // 参与投票方/创建投票方注册到space   //let 方便再上一层的DAO调用body = DaoMemberBody<DaoT>{
public fun regiter_to_space_get_nft(    sbt,
  signer: &signer,  }
     broker:let address)nft := OptionNFT::Option<NFT<DaoSpace::NFTMeta, DaoSpace::NFTData>> {mint(basemeta, meta, body);
     IdentifierNFT::accept<DaoMemberMeta<DaoT>,DaoMemberBody<DaoT>>(creator);
     IdentifierNFT::grant(dao_signer, creator);
     
  }
     
  // 根据 space_broker 来创建对应的proposal参与投票方/创建投票方注册到space
   public fun createregister_to_proposal<ExecutionStrategy>space(
      signer: &signer, // 创建者
      space_broker: u64, // space 代理人address) {
        block_num: u64, // 快照高度创建对应的NFT
    }

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

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

代码块
breakoutModewide
languagerust
// DaoProposal.move
module DaoProposal {// 证明...
    // 取本地NFT
   struct ProposalCapability// {用NFT投票
    proposal_id: u64,
  do_vote_with_nft(broker, id, choice, );
  }
   struct
Proposal<Action> { // 用NFT来投票
  ... // 现有的一些结构方便上层DAO来调用
  public fun proposal_id: u64,do_vote_with_nft(
      voting_systembroker: u8address,
// 投票类型有单选投票、二次投票、排名投票、加权投票等投票类型     voting_start_timeid: u64,
    voting_end_timechoice: u64u8,
    voting_block_numnft: u64Option::Option<NFT<DaoSpace::NFTMeta, // 投票快照高度DaoSpace::NFTData>>
  ) {
 voting_block_root: vector<u8>, // 投票快照高度的root
hash  }
  action: Option::Option<Action>, }

DaoTemplate

每个 DAO 初始化的时候都会根据这个模版生成一个命名为 XDao 的 module。需要确认下 XDao 这个 module 是否使用统一的名字更容易使用。

代码块
breakoutModewide
/// 执行策略的结构参数Dao 生成模版,
module {X}Dao{
  
  struct Vote {use StarcoinFramework::Dao;
  
  ///XDao vote的类型标识
for the proposalstruct underX the{}
`proposer`.  
  proposer: address,
 const DAO_ADDRESS = {dao_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,
 public(script) init_dao(sender: signer, capabilities:vector<vector<u8>, plugin_configs: vector<vector<u8>>){
    let plugin_install_cap = Dao::create_dao();
    PluginX::install_plugin(&plugin_install_cap, &sender, capabilities[0],configs[0]);
    PluginY::install_plugin(&plugin_install_cap, &sender, capabilities[1],configs[1]);
  }
  
  ///Dao 的入口方法是不是都需要通过 XDao 模块代理一边?
  
}

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

代码块
breakoutModewide
languagerust
// 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

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

module XProposalPlugin{
  use Dao::{Self,  ProposalPluginCapability}
  
  // XPlugin 的类型标识
  struct XPlugin{
  }
  
  struct XPluginConfig{
    cfg_1: u64,
  }
  
  struct XAction{
  }
  
  struct ProposalPluginCapabilityHolder{
     cap: &ProposalCapabilityProposalPluginCapability<XPlugin>
    );}
  
 
  public fun extractnew_strategy<Action>action(idargs: u64vector<u8>): :XAction{
Action; };  

Proposal action plugin

代码块
breakoutModewide
    //所有的bcs proposaldecode action 插件都需要提供两个公开的args
entry script function,参数保持一致  module XProposalPlugin{  //construct use Dao::{Self,XAction
  ProposalPluginCapability}
  
  // XPlugin 的类型标识,用于保存配置返回 plugin 所需要的 capabilities
  public struct XPlugin{fun get_capabilities():vector<u8>{}
  
  public cfg_a: u64,fun install_plugin<DaoT>(cap: &Dao::PluginInstallCapability, installer: &signer, capabilities: vector<u8>, config:vector<u8>){
  }   //bcs decode config structarg, XAction{construct config.
 }    let config struct= ProposalPluginCapabilityHolderXPluginConfig{}
     cap:let ProposalPluginCapability<XPlugin>plugin =  XPlugin{};
     fun new_action(args:vector<u8>): XAction{
       //bcs decode args
Dao::install_proposal_plugin_capability<DaoT, XPlugin>(cap, &plugin, installer, capabilities);
     //保存配置
      //construct XActionDao::move_to<DaoT, XPlugin>(&plugin, config);
  }
  
  public fun initexecute_plugin<DaoT>proposal<DaoT>(sender: &signer, args:vector<u8>proposal_id: u64){
      //bcs decode argslet dao_address = DaoRegistry::dao_address<DaoT>();
      let plugincap_configholder = XPlugin{}borrow_global<ProposalPluginCapabilityHolder>(dao_address);
      let capactoin = Dao::extract_proposal_plugin_capability<DaoTaction<DaoT, XPlugin>();
     move_to()XAction>(&cap_holder.cap, sender, proposal_id);
  }    //execute the publicaction,such fun execute_proposal<DaoT>(sender: &signer, proposal_id: u64){as trasnfer token
       let dao_addressplugin = DaoRegistry::dao_address<DaoT>()XPlugin{};
      let withdraw_cap_holder = Dao::borrow_withdraw_global<ProposalPluginCapabilityHolder>(dao_addresscapability<DaoT,XPlugin>(&plugin);
      let actointoken = Dao::extract_proposal_action<DaoT, XAction>withdraw<STC>(&capwithdraw_holder.cap, sender, proposal_id1000);
      //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)
  }
}

三、流程

...

创建流程

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

  2. 用户填写配置,选择需要安装的插件,生成 XDao module,以及初始化的参数。

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

...