...
代码块 | ||||
---|---|---|---|---|
| ||||
// 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,该合约只处理提案的相关处理过程,不做其他无关的事情。
代码块 | ||||
---|---|---|---|---|
| ||||
// 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 是否使用统一的名字更容易使用。
代码块 | ||
---|---|---|
| ||
/// 执行策略的结构参数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,该合约只处理提案的相关处理过程,不做其他无关的事情。
代码块 | ||||
---|---|---|---|---|
| ||||
// 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{ } 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
代码块 | ||
---|---|---|
| ||
//所有的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) } } |
三、流程
...
创建流程
用户 A 发起交易,先创建合约账号 X, A 拥有 X 的控制权。
用户填写配置,选择需要安装的插件,生成 XDao module,以及初始化的参数。
编译 module,并且通过两阶段升级的方式,由 A 来发期交易,将 XDao module 部署到 X 账号下,并通 init_script 初始化 XDao 以及插件。
Dao 创建完成。
四、问题
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类型如何转换;
投票权重的扩展性;
投票策略类的扩展性;
...