引言
经过之前的学习,准备进行实战开发一个简单的dao项目,实现一个去中心化自治组织,用于管理共享资金、社区任务、提案和投票等功能,本篇文章分享了提案模块、错误定义。
提案设计
关于提案模式的具体设计:
- dao member又提案的权利
- 为了减少废案,规定提案需要支付一些dao代币(5)
- 提案被成功接受,根据提案等级,提案者会获得不同的dao代币奖励
- 其余dao member可以对提案进行投票,1 dao代币对应1 票,提案时需要将dao代币质押,获取对应凭证,在提案结束后可以销毁凭证取回dao代币
- core member可以管理提案,控制提案关闭时间,变更提案等级等。
const
const MAX_VOTES_ONE_TIME: u64 = 10;
const TOTAL_SUPPLY:u64 = 100_000_000_000_000_000;
const PROPOSAL_FEE:u64 = 5;
const LEVEL0_REWARD:u64 = 0;
const LEVEL1_REWARD:u64 = 10;
const LEVEL2_REWARD:u64 = 15;
const LEVEL3_REWARD:u64 = 30;
首先定义了一些常量,代表提案花费,各个等级的提案奖励。
proposal
struct Proposal has key,store{
id: UID,
title: String, //The title of the proposal
describe: String, //Content of the Proposal
level: u64,
proposer: address, //Initiator of the Proposal
lock_balance: u64, //DAO Tokens Locked by the Proposal
support: u64, //Number of votes in favor of the proposal
against: u64, //Number of votes against the proposal
is_closed: bool,
is_passed: bool,
is_claimed_reward: bool,
}
提案结构体,包括提案题目,描述,等级,提案者,投票总数,赞成反对票数,是否关闭,是否通过,是否已经领取奖励等字段。
投票凭证
struct VoteCap has key{
id: UID,
proposal_id: ID,
voter: address,
is_support: bool,
votes: u64,
}
投票凭证,用户投票后会生成相应的投票凭证,在提案结束后销毁凭证取回质押的dao
发起提案
public fun submit_proposal(member_cap:&MemberCap,title: String, describe:String, level:u64, coin:&mut Coin<DAO>,treasury:&mut Treasury<DAO>,ctx:&mut TxContext){
//1.Verify Membership in the DAO
check_membercap_role(member_cap, ctx);
//2.Pay Proposal Fee
transfer_coin_to_treasury(treasury,coin, PROPOSAL_FEE);
//3.get duration time
//4.Create Proposal
let proposal = Proposal {
id: object::new(ctx),
title: title, //The title of the proposal
describe: describe, //Content of the Proposal
level: level,
proposer: member_cap.role_address, //Initiator of the Proposal
lock_balance: 0, //DAO Tokens Locked by the Proposal
support: 0, //Number of votes in favor of the proposal
against: 0, //Number of votes against the proposal
is_closed: false,
is_passed: false,
is_claimed_reward: false,
};
transfer::share_object(proposal);
}
- 首先验证提案者的身份
- 支付提案费
- 创建提案obj 设置为share对象
投票
public fun vote(member_cap:&MemberCap, proposal:&mut Proposal, votes:u64, is_support:bool, coin:&mut Coin<DAO>, treasury:&mut Treasury<DAO>,ctx:&mut TxContext){
//1. verify
check_membercap_role(member_cap,ctx);
assert!(!proposal.is_closed,EProposalClosed);
assert!(member_cap.role_address != proposal.proposer, EVoteSelf);
assert!(votes >= 1 && votes <= MAX_VOTES_ONE_TIME, EInvailVotes);
check_membercap_role(member_cap, ctx);
//2. transfer dao
transfer_coin_to_treasury(treasury,coin,votes);
//3. change proposal
update_proposal_vote(proposal,votes,is_support);
//4. distrubte voteCap
let vote_cap = VoteCap {
id: object::new(ctx),
proposal_id:object::uid_to_inner(&proposal.id),
voter: member_cap.role_address,
is_support: is_support,
votes: votes,
};
transfer::transfer(vote_cap, member_cap.role_address);
}
- 验证投票者身份
- 对提案的状态进行检查 查看是否关闭 提案者不能为自己投票 投票数不能超贵最大限制
- 质押相应的dao 代币
- 更改提案数据
- 颁发投票凭证
manage
public fun change_proposal_level(core_cap:& CoreCap, proposal:&mut Proposal, new_level:u64 ,ctx:&mut TxContext){
check_corecap_role(core_cap,ctx);
assert!(!proposal.is_closed, EProposalClosed);
proposal.level = new_level;
}
public fun close_proposal(core_cap:& CoreCap,proposal:&mut Proposal,ctx:&mut TxContext){
check_corecap_role(core_cap,ctx);
assert!(!proposal.is_closed, EProposalClosed);
if(proposal.against == 0 || (proposal.support * 2) / 3 >= proposal.against){
proposal.is_passed = true;
};
proposal.is_closed = true;
}
core member可以对提案进行管理 更改等级或关闭提案
取回票据
public fun claim_proposal_vote(member_cap:&MemberCap, proposal:&mut Proposal, vote_cap: VoteCap,treasury:&mut Treasury<DAO>, ctx:&mut TxContext){
// 1.veriry the parameters
check_membercap_role(member_cap,ctx);
assert!(&vote_cap.proposal_id == object::borrow_id(proposal), EProposalCheck);
assert!(proposal.is_closed, EProposalNotClosed);
assert!(member_cap.role_address == vote_cap.voter, ERoleCheck);
// 2.delete voteCap
let VoteCap {id, proposal_id, voter, is_support, votes} = vote_cap;
object::delete(id);
// 3.take coin from treasury
let reward_coin = take_coin_from_treasury(treasury,votes,ctx);
// 4.transfer coin to voter
transfer::public_transfer(reward_coin,voter);
}
在投票结束后,投票者销毁Votecap取回dao代币
public fun claim_proposal_reward(member_cap:&MemberCap, proposal:&mut Proposal, treasury:&mut Treasury<DAO> ,ctx:&mut TxContext){
// 1.veriry the parameters
check_membercap_role(member_cap,ctx);
assert!(proposal.is_passed, EProposalNotPassed);
assert!(!proposal.is_claimed_reward, EAlreadyClaimed);
assert!(proposal.proposer == tx_context::sender(ctx), ERoleCheck);
proposal.is_claimed_reward = true;
//2.reward calculation
let level = proposal.level;
let reward_amount:u64;
if(level == 1){
reward_amount = LEVEL1_REWARD;
}
else if(level == 2){
reward_amount = LEVEL2_REWARD;
}
else{
reward_amount = LEVEL3_REWARD;
};
//3.send reward token;
let reward_coin = take_coin_from_treasury(treasury,reward_amount,ctx);
transfer::public_transfer(reward_coin,proposal.proposer);
}
如果提案被接受,提案者可以获取奖励
内部函数
fun check_init_corecap_role(init_core_cap:& InitCoreCap,ctx: &mut TxContext){
assert!(init_core_cap.role_address== tx_context::sender(ctx), ERoleCheck);
}
fun check_corecap_role(core_cap:& CoreCap,ctx: &mut TxContext){
assert!(core_cap.role_address== tx_context::sender(ctx), ERoleCheck);
}
fun check_membercap_role(member_cap:& MemberCap,ctx: &mut TxContext){
assert!(member_cap.role_address == tx_context::sender(ctx), ERoleCheck);
}
fun transfer_coin_to_treasury(treasury:&mut Treasury<DAO>,coin:&mut Coin<DAO>,amount: u64){
assert!(balance::value<DAO>(&treasury.supply) >= amount, EInsufficientTreasurySupply);
balance::join<DAO>(&mut treasury.supply, balance::split<DAO>(coin::balance_mut(coin), amount));
}
fun take_coin_from_treasury(treasury:&mut Treasury<DAO>,amount: u64,ctx:&mut TxContext): Coin<DAO>{
let supply = &mut treasury.supply;
let reward_coin = coin::take<DAO>(supply, amount, ctx);
reward_coin
}
fun update_proposal_vote(proposal:&mut Proposal,votes:u64,is_support:bool){
let lock_balance = & proposal.lock_balance;
proposal.lock_balance =*lock_balance + votes;
if(is_support){
let support = & proposal.support;
proposal.support =*support + votes;
}
else{
let against = & proposal.against;
proposal.against =*against + votes;
}
}
重构了一些重复的功能,用于代码简化
check_init_corecap_role和check_corecap_role和check_membercap_role用于角色检查
transfer_coin_to_treasury和take_coin_from_treasury用于将代币转到金库或从金库转出
update_proposal_vote用来更新提案选票
视图函数
public fun is_closed(proposal: &Proposal):bool{
proposal.is_closed
}
public fun is_passed(proposal: &Proposal):bool{
proposal.is_passed
}
public fun support(proposal: &Proposal):u64{
proposal.support
}
public fun against(proposal: &Proposal):u64{
proposal.against
}
public fun proposer(proposal: &Proposal):address{
proposal.proposer
}
public fun total_members(dao: &Dao<DAO>):u64{
dao.total_members
}
public fun treasury_supply(treasury: &Treasury<DAO>):u64{
balance::value<DAO>(&treasury.supply)
}
用于获取状态,分为提案状态和dao状态
错误定义
const ETaskDistributeEnded:u64 = 0;
const ENotTaskCapOwner:u64 = 1;
const EProposalClosed:u64 = 2;
const EVoteSelf:u64 = 3;
const EInvailVotes:u64 = 4;
const EProposalCheck:u64 = 5;
const EProposalNotClosed:u64 = 6;
const ERoleCheck:u64 = 7;
const EProposalNotPassed:u64 = 8;
const EAlreadyClaimed:u64 = 9;
const EInsufficientTreasurySupply:u64 = 10;
定义一些错误,在assert时返回,方便交互失败时定位回滚位置
这些错误在之前代码的检查中有用到
Move语言学习交流QQ群: 79489587
Sui官方中文开发者电报群: https://t.me/sui_dev_cn