1、学习目标
通过本课你将掌握:
- 治理代币(Governance Token)的设计目标与现实角色;
- 去中心化治理结构(Governor + Timelock + Proposal);
- 流动性激励与收益分配机制(Liquidity Mining、Staking、Fee Redistribution);
- 如何用 Solidity 实现一个治理代币 + 投票治理系统 + 协议收入分配模型;
- 理解激励滥用、投票集中化、DAO 决策延迟等现实问题。
2、核心概念与现实机制详解
2.1 治理代币(Governance Token)
定义:治理代币是 DeFi 协议中用来代表治理权、收益权或投票权的加密资产。 持有者可以对协议的参数、资金使用、升级提案等进行投票或提案。
常见模型:
协议 | 治理代币 | 特点 |
---|---|---|
Compound | COMP | 每区块发放激励,按借贷量分配;投票决定参数(利率模型、资产列表等) |
Aave | AAVE | 质押(Safety Module)提供保险功能,参与治理,赚取协议费 |
Curve | CRV | 投票锁仓 (veCRV) 模型,决定激励分配、治理提案 |
MakerDAO | MKR | 承担清算风险;治理 DAI 的利率、抵押物比例等 |
关键思路:治理代币 ≠ 股权;它是治理机制的燃料。
经济模型要设计为:
- 激励长期持有者;
- 抑制短期投机;
- 维持系统稳定。
2.2 Tokenomics(代币经济模型)
核心维度:
- 分配机制
- Liquidity Mining(流动性挖矿):向借贷双方或流动性提供者发放代币;
- Treasury / Reserve:保留部分代币作为协议储备、开发基金;
- Staking / 锁仓激励:长期锁定代币获得更高收益或投票权(veToken 模型)。
- 价值捕获机制
- 协议费分配(如:利息的 10% 分配给治理金库);
- 代币回购(Buyback & Burn);
- 保险基金收益(Safety Module)。
- 投票权机制
- 每个治理代币 = 一票;
- 锁仓时间越长,投票权越大(veToken 模型);
- 或采用“代表投票”(Delegation)。
- 治理执行机制
- Proposal → Voting → Timelock → Execution;
- 投票通过的提案在延时后由执行合约(TimelockController)调用协议函数;
- 防止紧急攻击或治理闪电贷。
2.3 DAO 与治理流程(以 Aave/Compound 为例)
- 提案(Proposal):由满足条件(持有一定代币)的用户发起;
- 投票(Voting):代币持有人按持币比例投票;
- 通过阈值(Quorum):达到最低票数或通过率;
- 延时执行(Timelock):通过后等待 N 天执行;
- 执行(Execution):由 Timelock 合约调用协议合约的修改函数。
3、最小化治理系统(Simplified Governance System)
我们来构建一个简单但完整的治理代币与 DAO:
系统组成
GovToken
:ERC20 代币 + 委托投票(类似 COMP token 模式);LendingProtocol
:已有借贷系统的一部分(这里仅示意接口,如设置费率);Governor
:管理提案、投票与执行;Timelock
:治理提案延时执行;RewardDistributor
:根据用户借贷活动发放治理代币奖励。
3.1 GovToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
/**
* @title GovToken
* @author DeFi Protocol Team
* @notice 治理代币合约,继承 ERC20Votes 功能,支持投票治理
* @dev 该合约实现了 ERC20Votes 标准,允许代币持有者参与协议治理
* 代币可以通过奖励分发器进行铸造,用于激励用户参与协议
*/
contract GovToken is ERC20Votes {
/**
* @notice 构造函数,初始化治理代币
* @dev 铸造 1,000,000 个代币给部署者,并设置 EIP712 域名
*/
constructor()
ERC20("Governance Token", "GOV")
EIP712("Governance Token", "1")
{
_mint(msg.sender, 1_000_000e18);
}
/**
* @notice 铸造新代币用于奖励分发
* @dev 该函数允许外部合约铸造代币,主要用于奖励分发器
* @param to 接收代币的地址
* @param amount 铸造的代币数量
* @custom:security 在实际部署中,应该添加访问控制,只允许特定的分发器合约调用
*/
function mint(address to, uint256 amount) external {
// 在实际部署中,应该添加访问控制,只允许特定的分发器合约调用
_mint(to, amount);
}
}
3.2 SimpleGovernor.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/governance/Governor.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
/**
* @title SimpleGovernor
* @author DeFi Protocol Team
* @notice 简化的治理合约,基于 OpenZeppelin Governor 实现
* @dev 该合约实现了基本的治理功能,包括提案创建、投票和简单计数
* 使用 ERC20Votes 代币进行投票,支持提案阈值和法定人数要求
*/
contract SimpleGovernor is Governor, GovernorCountingSimple, GovernorVotes {
/**
* @notice 构造函数,初始化治理合约
* @param _token 用于投票的 ERC20Votes 代币合约
*/
constructor(
IVotes _token
) Governor("SimpleGovernor") GovernorVotes(_token) {}
/**
* @notice 获取投票延迟时间
* @dev 提案创建后需要等待的区块数
* @return 投票延迟时间(区块数)
*/
function votingDelay() public pure override returns (uint256) {
return 1; // 1 block
}
/**
* @notice 获取投票期间长度
* @dev 投票开始后持续的时间(区块数)
* @return 投票期间长度(区块数)
*/
function votingPeriod() public pure override returns (uint256) {
return 45818; // ~1 week
}
/**
* @notice 获取法定人数要求
* @dev 提案通过所需的最小投票权数量
* @param 提案ID(未使用,但需要保持函数签名一致)
* @return 法定人数要求(代币数量)
*/
function quorum(uint256) public pure override returns (uint256) {
return 100e18; // 100 GOV
}
/**
* @notice 获取提案阈值
* @dev 创建提案所需的最小投票权数量
* @return 提案阈值(代币数量)
*/
function proposalThreshold() public pure override returns (uint256) {
return 10e18;
}
}
3.3 RewardDistributor.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./GovToken.sol";
/**
* @title RewardDistributor
* @author DeFi Protocol Team
* @notice 奖励分发器合约,用于记录用户活动并分发治理代币奖励
* @dev 该合约允许协议记录用户活动,累积奖励,并允许用户领取治理代币
* 只有指定的协议地址可以调用 accrue 函数来记录奖励
*/
contract RewardDistributor {
/// @notice 治理代币合约地址
GovToken public gov;
/// @notice 协议地址,只有该地址可以调用 accrue 函数
address public protocol;
/// @notice 用户累积的奖励映射
mapping(address => uint256) public accrued;
/**
* @notice 构造函数,初始化奖励分发器
* @param _gov 治理代币合约地址
* @param _protocol 协议地址,用于权限控制
*/
constructor(GovToken _gov, address _protocol) {
gov = _gov;
protocol = _protocol;
}
/**
* @notice 记录用户活动并累积治理代币奖励
* @dev 只有协议地址可以调用此函数,用于记录用户参与协议活动
* @param user 用户地址
* @param value 奖励的治理代币数量
* @custom:security 只有 protocol 地址可以调用此函数
*/
function accrue(address user, uint256 value) external {
require(msg.sender == protocol, "not protocol");
accrued[user] += value;
}
/**
* @notice 领取累积的治理代币奖励
* @dev 用户调用此函数来领取之前累积的所有奖励
* 领取后,用户的累积奖励会被重置为 0
* @custom:security 用户只能领取自己的奖励
*/
function claim() external {
uint256 amount = accrued[msg.sender];
require(amount > 0, "no rewards");
accrued[msg.sender] = 0;
gov.mint(msg.sender, amount);
}
}
说明: 该 RewardDistributor 可挂接在借贷逻辑里,例如用户借贷时按借款量累积 GOV 奖励,从而实现“流动性挖矿”模式。
4、测试示例:治理与奖励流
用 Foundry 编写测试验证完整流程:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/GovToken.sol";
import "../src/SimpleGovernor.sol";
import "../src/RewardDistributor.sol";
import "@openzeppelin/contracts/governance/TimelockController.sol";
/**
* @title GovernanceTest
* @author DeFi Protocol Team
* @notice 治理系统测试合约,测试奖励分发和投票流程
* @dev 该测试合约验证治理代币的奖励分发机制和治理投票功能
* 包括代币铸造、奖励累积、投票权委托和提案创建等核心功能
*/
contract GovernanceTest is Test {
/// @notice 治理代币合约
GovToken gov;
/// @notice 时间锁控制器(测试中未使用,但保留用于未来扩展)
TimelockController timelock;
/// @notice 简化治理合约
SimpleGovernor governor;
/// @notice 奖励分发器合约
RewardDistributor distributor;
/// @notice 测试用户 Alice
address alice = address(0x1);
/// @notice 测试用户 Bob
address bob = address(0x2);
/**
* @notice 测试设置函数,初始化所有合约和测试环境
* @dev 部署所有必要的合约,设置测试用户,并分配初始代币
*/
function setUp() public {
gov = new GovToken();
address[] memory proposers = new address[](1);
address[] memory executors = new address[](1);
proposers[0] = address(this);
executors[0] = address(this);
timelock = new TimelockController(
1 days,
proposers,
executors,
address(this)
);
governor = new SimpleGovernor(gov);
distributor = new RewardDistributor(gov, address(this));
// give alice some tokens and delegate votes
gov.transfer(alice, 50 ether); // give alice 50 tokens
vm.prank(alice);
gov.delegate(alice);
}
/**
* @notice 测试奖励分发和投票流程的完整功能
* @dev 该测试验证以下流程:
* 1. 协议为用户累积奖励
* 2. 用户领取奖励代币
* 3. 用户创建治理提案
* 4. 验证提案状态
*/
function testRewardAndVoteFlow() public {
// accrue rewards
distributor.accrue(alice, 100 ether);
vm.prank(alice);
distributor.claim();
assertEq(gov.balanceOf(alice), 150 ether); // 50 ether initial + 100 ether reward
// advance block so voting power is available for proposal
vm.roll(block.number + 1);
// alice creates proposal
vm.startPrank(alice);
address[] memory targets = new address[](1);
uint256[] memory values = new uint256[](1);
bytes[] memory calldatas = new bytes[](1);
targets[0] = address(this);
values[0] = 0;
calldatas[0] = abi.encodeWithSignature("mockAction()");
uint256 proposalId = governor.propose(
targets,
values,
calldatas,
"Update parameter"
);
assertEq(uint256(governor.state(proposalId)), 0); // Pending
vm.stopPrank();
}
/**
* @notice 测试奖励分发器的基本功能
* @dev 验证奖励累积和领取机制
*/
function testRewardDistributorBasic() public {
// 测试累积奖励
distributor.accrue(alice, 50 ether);
assertEq(distributor.accrued(alice), 50 ether);
// 测试多次累积
distributor.accrue(alice, 30 ether);
assertEq(distributor.accrued(alice), 80 ether);
// 测试领取奖励
uint256 aliceBalanceBefore = gov.balanceOf(alice);
vm.prank(alice);
distributor.claim();
assertEq(gov.balanceOf(alice), aliceBalanceBefore + 80 ether);
assertEq(distributor.accrued(alice), 0);
}
/**
* @notice 测试奖励分发器的权限控制
* @dev 验证只有协议地址可以调用 accrue 函数
*/
function testRewardDistributorAccessControl() public {
// 非协议地址调用应该失败
vm.prank(bob);
vm.expectRevert("not protocol");
distributor.accrue(alice, 50 ether);
// 协议地址调用应该成功
distributor.accrue(alice, 50 ether);
assertEq(distributor.accrued(alice), 50 ether);
}
/**
* @notice 测试无奖励时的领取行为
* @dev 验证用户在没有累积奖励时无法领取
*/
function testClaimWithNoRewards() public {
vm.prank(alice);
vm.expectRevert("no rewards");
distributor.claim();
}
/**
* @notice 测试治理合约的基本参数
* @dev 验证投票延迟、投票期间、法定人数和提案阈值
*/
function testGovernorParameters() public view {
assertEq(governor.votingDelay(), 1);
assertEq(governor.votingPeriod(), 45818);
assertEq(governor.quorum(0), 100e18);
assertEq(governor.proposalThreshold(), 10e18);
}
/**
* @notice 测试提案创建权限
* @dev 验证只有满足提案阈值的用户才能创建提案
*/
function testProposalCreationPermissions() public {
// 给 Bob 少量代币,不满足提案阈值
gov.transfer(bob, 5 ether); // 5 tokens < 10 ether threshold
vm.prank(bob);
gov.delegate(bob);
vm.roll(block.number + 1);
// Bob 尝试创建提案应该失败
vm.startPrank(bob);
address[] memory targets = new address[](1);
uint256[] memory values = new uint256[](1);
bytes[] memory calldatas = new bytes[](1);
targets[0] = address(this);
values[0] = 0;
calldatas[0] = abi.encodeWithSignature("mockAction()");
vm.expectRevert();
governor.propose(targets, values, calldatas, "Test proposal");
vm.stopPrank();
}
/**
* @notice 测试投票权委托功能
* @dev 验证代币持有者可以委托投票权
*/
function testVoteDelegation() public {
// 给 Bob 一些代币
gov.transfer(bob, 20 ether);
// Bob 委托给自己
vm.prank(bob);
gov.delegate(bob);
// 验证投票权
vm.roll(block.number + 1);
assertEq(gov.getVotes(bob), 20 ether);
// Bob 委托给 Alice
vm.prank(bob);
gov.delegate(alice);
vm.roll(block.number + 1);
assertEq(gov.getVotes(alice), 50 ether + 20 ether); // Alice 原有 + Bob 委托
assertEq(gov.getVotes(bob), 0);
}
/**
* @notice 测试提案状态转换
* @dev 验证提案从 Pending 到 Active 的状态变化
*/
function testProposalStateTransition() public {
// 推进区块确保投票权可用
vm.roll(block.number + 1);
// Alice 创建提案
vm.startPrank(alice);
address[] memory targets = new address[](1);
uint256[] memory values = new uint256[](1);
bytes[] memory calldatas = new bytes[](1);
targets[0] = address(this);
values[0] = 0;
calldatas[0] = abi.encodeWithSignature("mockAction()");
uint256 proposalId = governor.propose(
targets,
values,
calldatas,
"Test proposal"
);
vm.stopPrank();
// 初始状态应该是 Pending
assertEq(uint256(governor.state(proposalId)), 0); // Pending
// 推进投票延迟 + 1 个区块,应该变为 Active
vm.roll(block.number + governor.votingDelay() + 1);
assertEq(uint256(governor.state(proposalId)), 1); // Active
}
/**
* @notice 测试多用户奖励分发
* @dev 验证多个用户可以独立累积和领取奖励
*/
function testMultipleUsersRewards() public {
// 给 Bob 一些代币
gov.transfer(bob, 20 ether);
vm.prank(bob);
gov.delegate(bob);
// 为两个用户累积奖励
distributor.accrue(alice, 100 ether);
distributor.accrue(bob, 50 ether);
// 验证累积奖励
assertEq(distributor.accrued(alice), 100 ether);
assertEq(distributor.accrued(bob), 50 ether);
// Alice 领取奖励
uint256 aliceBalanceBefore = gov.balanceOf(alice);
vm.prank(alice);
distributor.claim();
assertEq(gov.balanceOf(alice), aliceBalanceBefore + 100 ether);
assertEq(distributor.accrued(alice), 0);
// Bob 的奖励应该保持不变
assertEq(distributor.accrued(bob), 50 ether);
// Bob 领取奖励
uint256 bobBalanceBefore = gov.balanceOf(bob);
vm.prank(bob);
distributor.claim();
assertEq(gov.balanceOf(bob), bobBalanceBefore + 50 ether);
assertEq(distributor.accrued(bob), 0);
}
/**
* @notice 测试复杂提案场景
* @dev 验证包含多个目标和操作的提案
*/
function testComplexProposal() public {
vm.roll(block.number + 1);
vm.startPrank(alice);
address[] memory targets = new address[](2);
uint256[] memory values = new uint256[](2);
bytes[] memory calldatas = new bytes[](2);
// 第一个操作:调用 mockAction
targets[0] = address(this);
values[0] = 0;
calldatas[0] = abi.encodeWithSignature("mockAction()");
// 第二个操作:调用另一个函数
targets[1] = address(this);
values[1] = 0;
calldatas[1] = abi.encodeWithSignature("anotherMockAction()");
uint256 proposalId = governor.propose(
targets,
values,
calldatas,
"Complex proposal"
);
vm.stopPrank();
assertEq(uint256(governor.state(proposalId)), 0); // Pending
}
/**
* @notice 测试边界情况:零奖励
* @dev 验证累积零奖励的行为
*/
function testZeroRewardAccrual() public {
distributor.accrue(alice, 0);
assertEq(distributor.accrued(alice), 0);
vm.prank(alice);
vm.expectRevert("no rewards");
distributor.claim();
}
/**
* @notice 测试大额奖励
* @dev 验证大额奖励的累积和领取
*/
function testLargeReward() public {
uint256 largeAmount = 1000000 ether; // 100万代币
distributor.accrue(alice, largeAmount);
uint256 aliceBalanceBefore = gov.balanceOf(alice);
vm.prank(alice);
distributor.claim();
assertEq(gov.balanceOf(alice), aliceBalanceBefore + largeAmount);
assertEq(distributor.accrued(alice), 0);
}
/**
* @notice 模拟提案执行的目标函数
* @dev 这是一个空的测试函数,用于模拟提案要执行的操作
*/
function mockAction() external pure {}
/**
* @notice 另一个模拟提案执行的目标函数
* @dev 用于测试复杂提案场景
*/
function anotherMockAction() external pure {}
}
执行测试:
➜ defi git:(master) ✗ forge test --match-path test/SimpleGovernor.t.sol -vvv
[⠊] Compiling...
[⠘] Compiling 1 files with Solc 0.8.30
[⠃] Solc 0.8.30 finished in 1.73s
Compiler run successful!
Ran 12 tests for test/SimpleGovernor.t.sol:GovernanceTest
[PASS] testClaimWithNoRewards() (gas: 13592)
[PASS] testComplexProposal() (gas: 79907)
[PASS] testGovernorParameters() (gas: 9772)
[PASS] testLargeReward() (gas: 69381)
[PASS] testMultipleUsersRewards() (gas: 211130)
[PASS] testProposalCreationPermissions() (gas: 140005)
[PASS] testProposalStateTransition() (gas: 79027)
[PASS] testRewardAndVoteFlow() (gas: 126945)
[PASS] testRewardDistributorAccessControl() (gas: 44705)
[PASS] testRewardDistributorBasic() (gas: 75545)
[PASS] testVoteDelegation() (gas: 195965)
[PASS] testZeroRewardAccrual() (gas: 19806)
Suite result: ok. 12 passed; 0 failed; 0 skipped; finished in 7.38ms (15.24ms CPU time)
Ran 1 test suite in 348.72ms (7.38ms CPU time): 12 tests passed, 0 failed, 0 skipped (12 total tests)
5、设计延伸与思考
5.1 veToken 模型(Curve 风格)
- 用户可锁仓治理代币(1 周 - 4 年);
- 锁仓时间越长,投票权越大;
- 激励可根据投票权发放(veBoost);
- 缺点:复杂、锁定灵活性差;
- 优点:促进长期治理与稳定性。
5.2 Fee Redistribution
借贷协议的手续费(如借款利息的 10%)可进入 Treasury:
- 50% 用于回购/销毁治理代币;
- 30% 分配给质押者(staking pool);
- 20% 用于开发基金。
5.3 治理风险与攻击
问题 | 风险 | 缓解策略 |
---|---|---|
闪电贷治理攻击 | 攻击者瞬时借入代币投票 | 采用 Snapshot / DelegateLock 机制 |
投票冷漠 | 投票率低,治理失灵 | 代理投票 / 激励投票 |
延时执行过长 | 决策响应慢 | 紧急治理模块 |
协议分叉治理 | 社区意见分裂 | Timelock + off-chain signal |
6、总结
通过本课,你可以理解并掌握:
- 治理代币的意义、作用与设计方法;
- 去中心化治理的完整链路(Proposal → Vote → Timelock → Execute);
- 激励分配机制(流动性挖矿、治理奖励、Fee 分配);
- 如何在 Solidity 中快速实现一个最小可用的治理系统;
- 现实中 DAO 治理与经济模型的主要风险与改进方向。
7、作业(实践挑战)
- 将本治理模块接入你的借贷平台:
- 在每次借款/还款时调用
RewardDistributor.accrue()
; - 允许用户通过
claim()
获取代币激励。
- 在每次借款/还款时调用
- 在 Governor 合约中加入“修改利率模型参数”的提案执行逻辑,模拟社区决策。
- 实现 veToken 模型:用户锁仓代币获得投票权和收益,锁期越长权重越高。

声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
腾讯云开发者社区:孟斯特
—