代币投票系统
代币投票系统是一种基于代币持有量进行投票权重分配的治理机制,用于实现去中心化的社区决策。本教程将介绍如何实现一个安全可靠的投票系统。
功能特性
- 提案管理
- 投票权重
- 投票委托
- 提案执行
- 时间锁定
合约实现
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/Timers.sol";
/**
* @title TokenVoting
* @dev 代币投票合约实现
*/
contract TokenVoting is Ownable, ReentrancyGuard {
using SafeMath for uint256;
using SafeERC20 for IERC20;
using Timers for Timers.BlockNumber;
// 提案状态
enum ProposalState {
Pending, // 等待中
Active, // 活跃
Canceled, // 已取消
Defeated, // 已失败
Succeeded, // 已成功
Queued, // 已排队
Expired, // 已过期
Executed // 已执行
}
// 提案信息
struct Proposal {
uint256 id; // 提案ID
address proposer; // 提案人
uint256 startBlock; // 开始区块
uint256 endBlock; // 结束区块
uint256 forVotes; // 赞成票
uint256 againstVotes; // 反对票
uint256 abstainVotes; // 弃权票
bool canceled; // 是否取消
bool executed; // 是否执行
mapping(address => Receipt) receipts; // 投票记录
bytes32 descriptionHash; // 描述哈希
bytes[] calldatas; // 调用数据
address[] targets; // 目标合约
uint256[] values; // 调用金额
}
// 投票记录
struct Receipt {
bool hasVoted; // 是否已投票
uint8 support; // 投票支持类型
uint256 votes; // 投票数量
}
// 投票配置
struct VotingConfig {
uint256 votingDelay; // 投票延迟
uint256 votingPeriod; // 投票期限
uint256 proposalThreshold; // 提案门槛
uint256 quorumNumerator; // 法定人数分子
uint256 executionDelay; // 执行延迟
}
// 状态变量
IERC20 public token; // 投票代币
mapping(uint256 => Proposal) public proposals; // 提案映射
mapping(address => uint256) public delegateVotes; // 委托投票权
mapping(address => address) public delegates; // 委托人映射
VotingConfig public config; // 投票配置
uint256 public proposalCount; // 提案计数
bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,uint8 support)");
string public constant name = "Token Voting";
uint256 public constant QUORUM_DENOMINATOR = 100;
// 事件
event ProposalCreated(uint256 indexed proposalId, address indexed proposer);
event ProposalCanceled(uint256 indexed proposalId);
event ProposalExecuted(uint256 indexed proposalId);
event VoteCast(address indexed voter, uint256 indexed proposalId, uint8 support, uint256 weight);
event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);
event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);
/**
* @dev 构造函数
*/
constructor(
address _token,
uint256 _votingDelay,
uint256 _votingPeriod,
uint256 _proposalThreshold,
uint256 _quorumNumerator,
uint256 _executionDelay
) {
require(_quorumNumerator <= QUORUM_DENOMINATOR, "Invalid quorum");
token = IERC20(_token);
config = VotingConfig({
votingDelay: _votingDelay,
votingPeriod: _votingPeriod,
proposalThreshold: _proposalThreshold,
quorumNumerator: _quorumNumerator,
executionDelay: _executionDelay
});
}
/**
* @dev 创建提案
*/
function propose(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
string memory description
) external returns (uint256) {
require(
getVotes(msg.sender) >= config.proposalThreshold,
"Insufficient votes"
);
require(
targets.length == values.length && targets.length == calldatas.length,
"Invalid proposal"
);
uint256 proposalId = hashProposal(targets, values, calldatas, keccak256(bytes(description)));
Proposal storage proposal = proposals[proposalId];
require(proposal.proposer == address(0), "Proposal exists");
uint256 startBlock = block.number.add(config.votingDelay);
uint256 endBlock = startBlock.add(config.votingPeriod);
proposal.id = proposalId;
proposal.proposer = msg.sender;
proposal.startBlock = startBlock;
proposal.endBlock = endBlock;
proposal.targets = targets;
proposal.values = values;
proposal.calldatas = calldatas;
proposal.descriptionHash = keccak256(bytes(description));
proposalCount++;
emit ProposalCreated(proposalId, msg.sender);
return proposalId;
}
/**
* @dev 投票
*/
function castVote(uint256 proposalId, uint8 support) external {
require(support <= 2, "Invalid vote type");
return _castVote(msg.sender, proposalId, support);
}
/**
* @dev 带签名的投票
*/
function castVoteBySig(
uint256 proposalId,
uint8 support,
uint8 v,
bytes32 r,
bytes32 s
) external {
bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainId(), address(this)));
bytes32 structHash = keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support));
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
address signer = ecrecover(digest, v, r, s);
require(signer != address(0), "Invalid signature");
return _castVote(signer, proposalId, support);
}
/**
* @dev 执行投票
*/
function _castVote(
address voter,
uint256 proposalId,
uint8 support
) internal {
require(state(proposalId) == ProposalState.Active, "Voting is closed");
Proposal storage proposal = proposals[proposalId];
Receipt storage receipt = proposal.receipts[voter];
require(!receipt.hasVoted, "Already voted");
uint256 votes = getVotes(voter);
if (support == 0) {
proposal.againstVotes = proposal.againstVotes.add(votes);
} else if (support == 1) {
proposal.forVotes = proposal.forVotes.add(votes);
} else if (support == 2) {
proposal.abstainVotes = proposal.abstainVotes.add(votes);
}
receipt.hasVoted = true;
receipt.support = support;
receipt.votes = votes;
emit VoteCast(voter, proposalId, support, votes);
}
/**
* @dev 执行提案
*/
function execute(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) external payable {
uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash);
require(state(proposalId) == ProposalState.Succeeded, "Proposal not succeeded");
Proposal storage proposal = proposals[proposalId];
proposal.executed = true;
for (uint256 i = 0; i < targets.length; i++) {
(bool success, ) = targets[i].call{value: values[i]}(calldatas[i]);
require(success, "Transaction failed");
}
emit ProposalExecuted(proposalId);
}
/**
* @dev 取消提案
*/
function cancel(uint256 proposalId) external {
require(state(proposalId) != ProposalState.Executed, "Cannot cancel executed proposal");
Proposal storage proposal = proposals[proposalId];
require(msg.sender == proposal.proposer || getVotes(proposal.proposer) < config.proposalThreshold, "Cannot cancel");
proposal.canceled = true;
emit ProposalCanceled(proposalId);
}
/**
* @dev 委托投票权
*/
function delegate(address delegatee) external {
return _delegate(msg.sender, delegatee);
}
/**
* @dev 带签名的委托
*/
function delegateBySig(
address delegatee,
uint256 nonce,
uint256 expiry,
uint8 v,
bytes32 r,
bytes32 s
) external {
bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainId(), address(this)));
bytes32 structHash = keccak256(abi.encode("Delegation(address delegatee,uint256 nonce,uint256 expiry)"));
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
address signer = ecrecover(digest, v, r, s);
require(signer != address(0), "Invalid signature");
require(nonce == nonces[signer]++, "Invalid nonce");
require(block.timestamp <= expiry, "Signature expired");
return _delegate(signer, delegatee);
}
/**
* @dev 内部委托函数
*/
function _delegate(address delegator, address delegatee) internal {
address currentDelegate = delegates[delegator];
uint256 delegatorBalance = token.balanceOf(delegator);
delegates[delegator] = delegatee;
emit DelegateChanged(delegator, currentDelegate, delegatee);
_moveDelegates(currentDelegate, delegatee, delegatorBalance);
}
/**
* @dev 移动委托票数
*/
function _moveDelegates(
address srcRep,
address dstRep,
uint256 amount
) internal {
if (srcRep != dstRep && amount > 0) {
if (srcRep != address(0)) {
uint256 srcRepOld = delegateVotes[srcRep];
uint256 srcRepNew = srcRepOld.sub(amount);
delegateVotes[srcRep] = srcRepNew;
emit DelegateVotesChanged(srcRep, srcRepOld, srcRepNew);
}
if (dstRep != address(0)) {
uint256 dstRepOld = delegateVotes[dstRep];
uint256 dstRepNew = dstRepOld.add(amount);
delegateVotes[dstRep] = dstRepNew;
emit DelegateVotesChanged(dstRep, dstRepOld, dstRepNew);
}
}
}
/**
* @dev 获取提案状态
*/
function state(uint256 proposalId) public view returns (ProposalState) {
Proposal storage proposal = proposals[proposalId];
require(proposal.proposer != address(0), "Unknown proposal");
if (proposal.canceled) {
return ProposalState.Canceled;
}
if (proposal.executed) {
return ProposalState.Executed;
}
uint256 currentBlock = block.number;
if (currentBlock < proposal.startBlock) {
return ProposalState.Pending;
}
if (currentBlock <= proposal.endBlock) {
return ProposalState.Active;
}
if (_quorumReached(proposalId) && _voteSucceeded(proposalId)) {
if (currentBlock <= proposal.endBlock.add(config.executionDelay)) {
return ProposalState.Queued;
} else {
return ProposalState.Expired;
}
}
return ProposalState.Defeated;
}
/**
* @dev 检查是否达到法定人数
*/
function _quorumReached(uint256 proposalId) internal view returns (bool) {
Proposal storage proposal = proposals[proposalId];
uint256 totalSupply = token.totalSupply();
uint256 quorum = totalSupply.mul(config.quorumNumerator).div(QUORUM_DENOMINATOR);
return proposal.forVotes.add(proposal.againstVotes) >= quorum;
}
/**
* @dev 检查投票是否成功
*/
function _voteSucceeded(uint256 proposalId) internal view returns (bool) {
Proposal storage proposal = proposals[proposalId];
return proposal.forVotes > proposal.againstVotes;
}
/**
* @dev 获取投票权重
*/
function getVotes(address account) public view returns (uint256) {
return delegateVotes[account];
}
/**
* @dev 获取提案哈希
*/
function hashProposal(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) public pure returns (uint256) {
return uint256(keccak256(abi.encode(targets, values, calldatas, descriptionHash)));
}
/**
* @dev 获取链ID
*/
function getChainId() internal view returns (uint256) {
uint256 chainId;
assembly {
chainId := chainid()
}
return chainId;
}
/**
* @dev 更新配置
*/
function updateConfig(
uint256 _votingDelay,
uint256 _votingPeriod,
uint256 _proposalThreshold,
uint256 _quorumNumerator,
uint256 _executionDelay
) external onlyOwner {
require(_quorumNumerator <= QUORUM_DENOMINATOR, "Invalid quorum");
config.votingDelay = _votingDelay;
config.votingPeriod = _votingPeriod;
config.proposalThreshold = _proposalThreshold;
config.quorumNumerator = _quorumNumerator;
config.executionDelay = _executionDelay;
}
/**
* @dev 获取提案详情
*/
function getProposal(uint256 proposalId)
external
view
returns (
address proposer,
uint256 startBlock,
uint256 endBlock,
uint256 forVotes,
uint256 againstVotes,
uint256 abstainVotes,
bool canceled,
bool executed,
bytes32 descriptionHash
)
{
Proposal storage proposal = proposals[proposalId];
return (
proposal.proposer,
proposal.startBlock,
proposal.endBlock,
proposal.forVotes,
proposal.againstVotes,
proposal.abstainVotes,
proposal.canceled,
proposal.executed,
proposal.descriptionHash
);
}
/**
* @dev 获取投票记录
*/
function getReceipt(uint256 proposalId, address voter)
external
view
returns (
bool hasVoted,
uint8 support,
uint256 votes
)
{
Receipt storage receipt = proposals[proposalId].receipts[voter];
return (receipt.hasVoted, receipt.support, receipt.votes);
}
// 用于签名投票的nonce
mapping(address => uint256) public nonces;
}
关键概念
提案管理
提案系统支持:
- 提案创建
- 提案取消
- 提案执行
- 状态管理
投票机制
投票功能包括��
- 投票权重
- 投票委托
- 投票记录
- 结果统计
治理流程
治理过程包括:
- 提案期
- 投票期
- 执行期
- 冷却期
安全考虑
投票安全
- 权重验证
- 重复投票检查
- 时间控制
- 签名验证
提案安全
- 门槛限制
- 执行延迟
- 取消机制
- 状态检查
系统安全
- 权限管理
- 重入防护
- 参数验证
- 紧急控制
数据安全
- 状态同步
- 数据验证
- 事件记录
- 错误处理
最佳实践
提案管理
- 合理的门槛
- 充分的讨论期
- 适当的投票期
- 安全的执行期
投票管理
- 透明的规则
- 公平的权重
- 便捷的操作
- 完整的记录
委托管理
- 灵活的机制
- 清晰的关系
- 实时的更新
- 安全的转移
系统维护
- 定期检查
- 参数优化
- 升级预案
- 应急处理
扩展功能
- 多签提案
- 分级投票
- 提案激励
- 自动执行
- 跨链投票
应用场景
协议治理
- 参数调整
- 升级决策
- 资金使用
- 紧急处理
社区管理
- 提案投票
- 资源分配
- 规则制定
- 权益分配
项目决策
- 发展方向
- 合作伙伴
- 激励方案
- 生态建设
总结
代币投票系统是去中心化治理的核心机制。通过本教程,你可以:
- 实现完整的投票系统
- 确保治理安全性
- 优化用户体验
- 促进社区发展
常见问题解答(FAQ)
1. 基本概念
Q: 什么是代币投票? A: 代币投票是一种去中心化治理机制,主要特点包括:
- 基于代币的投票权
- 提案创建和投票
- 自动执行机制
- 委托投票功能
- 时间锁定保护
Q: 投票系统有哪些组成部分? A: 主要组成包括:
- 提案管理
- 投票机制
- 权重计算
- 执行系统
- 委托系统
2. 功能相关
Q: 如何创建提案? A: 创建流程:
solidity
function createProposal(
string memory title,
string memory description,
address target,
bytes memory data
) public returns (uint256) {
// 1. 检查提案资格
require(getVotingPower(msg.sender) >= proposalThreshold);
// 2. 创建提案
uint256 proposalId = proposalCount++;
Proposal storage proposal = proposals[proposalId];
// 3. 设置提案信息
proposal.title = title;
proposal.description = description;
proposal.target = target;
proposal.data = data;
proposal.startTime = block.timestamp + votingDelay;
proposal.endTime = proposal.startTime + votingPeriod;
return proposalId;
}
Q: 如何进行投票? A: 投票机制:
- 检查投票权
- 验证提案状态
- 记录投票选择
- 更新投票统计
- 触发相关事件
3. 安全相关
Q: 投票系统有什么风险? A: 主要风险包括:
- 投票操纵
- 提案攻击
- 执行风险
- 时间攻击
- 委托风险
Q: 如何保护投票安全? A: 安全措施包括:
- 投票门槛
- 时间锁定
- 权重验证
- 多重签名
- 紧急暂停
4. 优化相关
Q: 如何优化投票效率? A: 优化策略:
- 批量投票
- 快照机制
- 存储优化
- Gas优化
- 并行处理
Q: 如何提高参与度? A: 改进方案:
- 投票激励
- 界面优化
- 教育引导
- 社区参与
- 透明度提升
5. 实现细节
Q: 如何实现投票委托? A: 实现机制:
solidity
function delegate(
address delegatee,
uint256 amount
) internal {
// 1. 更新委托记录
delegations[msg.sender] = Delegation({
delegatee: delegatee,
amount: amount,
timestamp: block.timestamp
});
// 2. 更新投票权
votingPower[delegatee] += amount;
votingPower[msg.sender] -= amount;
// 3. 触发事件
emit DelegationUpdated(msg.sender, delegatee, amount);
}
Q: 如何处理提案执行? A: 处理机制:
- 结果验证
- 时间锁定
- 执行准备
- 状态更新
- 失败处理
6. 最佳实践
Q: 投票系统开发建议? A: 开发建议:
- 完整测试
- 安全审计
- 社区反馈
- 渐进升级
- 应急预案
Q: 如何提高系统可靠性? A: 改进方案:
- 故障检测
- 自动恢复
- 状态验证
- 日志记录
- 监控预警
7. 错误处理
Q: 常见错误及解决方案? A: 错误类型:
"Invalid proposal"
: 检查提案"Already voted"
: 验证状态"Not enough votes"
: 检查权重"Proposal expired"
: 检查时间"Execution failed"
: 验证执行
Q: 如何处理异常情况? A: 处理机制:
- 状态回滚
- 错误记录
- 通知机制
- 手动干预
- 补偿机制
8. 升级维护
Q: 如何升级投票系统? A: 升级策略:
- 代理合约
- 数据迁移
- 兼容处理
- 测试验证
- 平滑过渡
Q: 如何监控系统状态? A: 监控方案:
- 提案追踪
- 投票统计
- 参与度分析
- 异常检测
- 性能监控
9. 与其他系统集成
Q: 如何与DeFi协议集成? A: 集成方案:
- 权重计算
- 收益分配
- 风险控制
- 状态同步
- 接口适配
Q: 如何实现跨链投票? A: 实现策略:
- 跨链消息
- 状态同步
- 结果验证
- 安全保护
- 一致性维护