...
代码块 | ||
---|---|---|
| ||
// Dao.move module Dao { use DaoSpace; struct DaoSignerDelegate { 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 } struct/// DaoProposalProposal {data struct. /// cap:是否需要通过 ProposalCapabilityDaoT 来区分 Proposal,如果不需要,则需要把 }DAO id 记录在 proposal 中 /// struct但 DaoProposalSetAction {的泛型无法消除 struct Proposal<phantom proposal_setDaoT: HashSet<u64store, DaoProposal>Action: store> has }key { //初始化过程中分配给 creator 的 cap /// id structof RootCapability has key{} the proposal struct DaoMemberMeta<phontem DaoT> has copy{ user id: addressu64, } //这里是否需要 DaoT 来区分不同的 Dao? //如果不区分的话,同一个用户加入多个 Daocreator 的情况,当前的of IdentifierNFTthe 无法表达proposal struct DaoMemberBody<DaoT>{ sbtproposer: Token<DaoT>address, } /// 执行策略:转账when voting begins. struct ExecutionTransferStrategy<phontem TokenT> { amountstart_time: u128u64, to: address, }/// when voting ends. // 直接创建 Dao public fun new_dao(end_time: u64, creator: &signer, /// count of voters who voting_delay: u64, agree with the proposal voting_period: u64, for_votes: u128, min_action_delay: u64) { /// count of //这里 Account 需要提供一个新的方法,允许用一个账号去创建另外一个 Delegate 账号。voters who're against the proposal let signer_cap = Account::create_delegate_account(&signer);against_votes: u128, let dao_signer = Account::create_signer_with_cap(&siger_cap); //下面面的操作切换到 dao_signer 身份进行操作 /// executable after this time. let dao = Dao{..}; eta: u64, move_to(&dao_signer, dao); /// 托管after Daohow 账号的long, SignerCapabilitythe 到该合约agreed proposal can be executed. move_to(&dao_signer, DaoSignerDelegate{cap: signer_cap}); action_delay: u64, issue_member_nft(creator,&dao_signer); /// how many votes to reach to make the //初始化 dao 的时候无法一次性完成,需要先把 cap 都存到 creator 账号下proposal pass. quorum_votes: u128, //然后按照 plugin 的方式逐步初始化 } /// proposal 将一个账号直接升级为action. Dao, sender 会变为 Dao 账号 public fun upgrade_to_dao(sender:signer, ...) {action: Option::Option<Action>, //基本逻辑同上,省去了创建账号的流程在原来的 Proposal 字段上新增两个字段 } block_num: u64, // 这里有个难题是快照高度 TokenT 从哪里来。 // 一种方法是先生成一个账号,部署合约,然后升级为 dao root_hash: vector<u8> // 快照根hash 另外一种方法是通过 Dao 的合约升级方式进行部署合约} fun issue_member_nft<DaoT>(creator: &signer, dao_signer: &signer){ //Set 上也需要用泛型来区分 Token::register<DaoT>(dao_signer); struct DaoProposalSet { let basemeta = NFT::new_meta_with_image();proposal_set: HashSet<u64, DaoProposal> } NFT::register_nft_v2<DaoMemberMeta<DaoT>>(dao_signer, basemeta); //初始化过程中分配给 creator 的 cap let creator_address = Signer::address_of(creator); struct RootCapability has key{} // issuestruct 第一个DaoMemberMeta<phontem NFTDaoT> 给has creatorcopy{ user: letaddress, meta = DaoMemberMeta<DaoT>{} //这里是否需要 DaoT 来区分不同的 user:Dao? creator; //如果不区分的话,同一个用户加入多个 Dao 的情况,当前的 IdentifierNFT };无法表达 struct DaoMemberBody<DaoT>{ //如何初始化 creator 的 sbt?sbt: Token<DaoT>, } let sbt = Token::zero<DaoT>(); // 执行策略:转账 struct letExecutionTransferStrategy<phontem body =TokenT> DaoMemberBody<DaoT>{ amount: sbtu128, to: }address, } let nft = NFT::mint(basemeta, meta, body); // 直接创建 Dao public IdentifierNFT::accept<DaoMemberMeta<DaoT>,DaoMemberBody<DaoT>>(creator);fun new_dao( IdentifierNFT::grant(dao_creator: &signer, creator); voting_delay: u64, } voting_period: u64, // 参与投票方/创建投票方注册到space public fun register_to_space( min_action_delay: u64) { signer: &signer, //这里 Account 需要提供一个新的方法,允许用一个账号去创建另外一个 Delegate 账号。 broker: address) {let signer_cap = Account::create_delegate_account(&signer); let dao_signer // 创建对应的NFT= Account::create_signer_with_cap(&siger_cap); } // 参与投票方/创建投票方注册到space 下面面的操作切换到 dao_signer 身份进行操作 // 方便再上一层的DAO调用 publiclet fun regiter_to_space_get_nft( dao = Dao{..}; signer: &move_to(&dao_signer, dao); broker: address) : Option::Option<NFT<DaoSpace::NFTMeta, DaoSpace::NFTData>> { }// 托管 Dao 账号的 SignerCapability 到该合约 move_to(&dao_signer, DaoSignerDelegate{cap: signer_cap}); // 根据 space_broker 来创建对应的proposal public fun create_proposal<ExecutionStrategy>(issue_member_nft(creator,&dao_signer); signer: &signer, //初始化 创建者dao 的时候无法一次性完成,需要先把 cap 都存到 creator 账号下 space_broker: u64, //然后按照 spaceplugin 代理人的方式逐步初始化 } block_num: u64, // 快照高度将一个账号直接升级为 Dao, sender 会变为 Dao 账号 root_hash: vector<u8>public // 快照根hash );fun upgrade_to_dao(sender:signer, ...) { //基本逻辑同上,省去了创建账号的流程 投票 } public fun do_vote( // 这里有个难题是 TokenT 从哪里来。 signer: &signer, // 一种方法是先生成一个账号,部署合约,然后升级为 dao // 另外一种方法是通过 broker:Dao address,的合约升级方式进行部署合约 id: u64, fun issue_member_nft<DaoT>(creator: &signer, dao_signer: &signer){ amount: u128,Token::register<DaoT>(dao_signer); let choice:basemeta u8, = NFT::new_meta_with_image(); proof: &vector<u8>,NFT::register_nft_v2<DaoMemberMeta<DaoT>>(dao_signer, basemeta); let sidecreator_nodes: &vector<u8>) { address = Signer::address_of(creator); // 证明... issue 第一个 NFT 给 creator // 取本地NFT let meta = DaoMemberMeta<DaoT>{ // 用NFT投票 do_vote_with_nft(broker, id, choice,user: )creator; }; //如何初始化 用NFT来投票creator 的 sbt? // 方便上层DAO来调用 public fun do_vote_with_nft( broker: address,let sbt = Token::zero<DaoT>(); id:let u64,body = DaoMemberBody<DaoT>{ choice: u8, nft: Option::Option<NFT<DaoSpace::NFTMetasbt, DaoSpace::NFTData>> ) { } let }nft } |
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, // 投票权重 } = 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 createdo_proposal<Action>(...,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::{ProposalPluginCapability} struct XAction{ } struct ProposalPluginCapabilityHolder{ cap: ProposalPluginCapability<XAction> } fun new_action(args:vector<u8>): XAction{ //bcs decode args //construct XAction } public fun plugin(sender: &signer){ //这里的 sender 必须是 dao 账号,但这里如何获得 dao 账号,得想一个方式 } 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); space_broker: address,//execute the action space_id: u64,//update the executor } root_hash: &vector<u8>): ProposalCapability; //the entry script function public fun cast_vote((script) fun execute_proposal_entry<DaoT>(sender: signer, proposal_id: u64){ signer: execute_proposal<DaoT>(&signersender, proposal_id) } proposer_broker: address, public fun propose<DaoT>(sender:&signer, idargs:vector<u8>, exec_delay: u64,){ let amount:action u128, = new_action(args); choice: u8, cap: &ProposalCapabilitylet dao_address = Dao::get_dao_address<DaoT>(); let publiccap_holder fun= extractborrow_strategy<Action>(id: u64) : Action; }; |
action
代码块 |
---|
module XProposalAction{global<ProposalPluginCapabilityHolder>(dao_address); struct XAction{ } public fun new_action(args...): XAction{ //construct action } //理想的情况是 action 的方法是标准化的,但如果牵扯到带有范型的 action,可能不好统一。 public fun execute_action(proposer_address: 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, proposal_id: u64){exec_delay) } } |
三、流程
TODO:
四、问题
Move实现层面,区分不同的项目方来创建DAO,是通过Template Type 来对其进行异化还是通过 address+id的方式,若按照前者,如何解决合约动态部署的问题?
这个当前有两个方案,这个 issue 中提了两个方案 [Feature Request] Support Issue a Token on webpage · Issue #4 · starcoinorg/dapps (http://github.com ) ,未来可以支持合约中部署合约。Snapshot中,存在不同的准入策略,即参与/创建投票的人在DAO中创建/参与投票的时候会有一个准入的门槛,这个准入的门槛是一系列判断条件,这个条件有预置的也有用户。在solidity中可以动态调用,故可以通过一个ABI的字符数组来进行表示,实际执行的时候也是动态去调用这个ABI,且这些策略是可以拼接的。而Move中如果也按照这个思路显然无法实现
感觉可以简化一下,小额抵押某种 Token 即可,如果是发垃圾投票就通过投票没收。类似的,Snapshot也存在不同的Execution Action,在Move中也可以通过预置Execution的模式去实现,是否还有其他的实现思路?
当前 DAO 的 Action 是支持第三方扩展的,并不是只能用预置的 Action。
执行在前端触发。治理权重的计算(基于长线hodl:锁仓时长、锁仓量),基于余额的权重计算有难度。锁仓有激励。
不同类型项目采用不同机制。Swap自实现weight维护。
扩展点的设计,包括Action等动态调用的扩展,通过interface形式,前端来触发。DAO的平滑升级如何扩展?
Action的扩展性;
DAO类型的扩展性,不同DAO类型如何转换;
投票权重的扩展性;
投票策略类的扩展性;
五、参考
...