治理与未来 阶段五 ✅ 验证通过
| 模型 | 特点 | 代表 |
|---|---|---|
| 固定供应 | 总量不变,通缩预期 | BTC(2100万) |
| 通胀供应 | 持续增发,按规则铸造 | ETH(增发+销毁) |
| 双代币 | 治理代币+稳定币分离 | MakerDAO(MKR+DAI) |
| 通缩代币 | 每笔交易销毁一部分 | SAFE代币 |
| 弹性供应 | 自动增发/缩减维持价格 | Ampleforth(已衰落) |
// Uniswap (UNI) 代币分配
总供应量: 1,000,000,000 UNI
┌─────────────────────────────────┐
│ 社区: 60% (600M) │
│ ├── 国库: 43% (430M) │
│ ├── 流动性挖矿: 15% (150M) │
│ └── 空投: 15% (150M) │ ← 4年线性释放
│ │
│ 团队: 21.6% (216M) │ ← 4年线性释放
│ 投资人: 17.8% (178M) │ ← 4年线性释放
│ 顾问: 0.6% (6M) │ ← 4年线性释放
└─────────────────────────────────┘
// 关键:4年线性释放防止团队砸盘
// contracts/TokenVesting.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title TokenVesting
* @dev 代币释放合约——支持悬崖期+线性释放
*
* 释放时间线:
* |----悬崖期----|--------线性释放期--------|
* 0 cliff duration
* │不可提取 │ 每秒线性增加可提取额度 │全部可提取
*/
contract TokenVesting is Ownable {
using SafeERC20 for IERC20;
struct VestingSchedule {
bool initialized;
address beneficiary; // 受益人
uint256 cliff; // 悬崖期(秒)
uint256 duration; // 总释放期(秒)
uint256 start; // 开始时间
uint256 totalAmount; // 总释放金额
uint256 released; // 已释放金额
bool revocable; // 是否可撤销
}
IERC20 public immutable token;
mapping(bytes32 => VestingSchedule) private vestingSchedules;
bytes32[] private vestingScheduleIds;
uint256 public vestingSchedulesTotalAmount;
event VestingScheduleCreated(address indexed beneficiary, uint256 amount);
event Released(address indexed beneficiary, uint256 amount);
event Revoked(address indexed beneficiary, uint256 unreleased);
constructor(address _token) Ownable(msg.sender) {
token = IERC20(_token);
}
/// @notice 创建释放计划
function createVestingSchedule(
address _beneficiary,
uint256 _start,
uint256 _cliff,
uint256 _duration,
uint256 _amount,
bool _revocable
) external onlyOwner {
require(_beneficiary != address(0), "Zero beneficiary");
require(_duration > 0, "Zero duration");
require(_cliff <= _duration, "Cliff exceeds duration");
require(_amount > 0, "Zero amount");
bytes32 id = _computeScheduleId(_beneficiary, vestingScheduleIds.length);
vestingSchedules[id] = VestingSchedule({
initialized: true,
beneficiary: _beneficiary,
start: _start,
cliff: _start + _cliff,
duration: _duration,
totalAmount: _amount,
released: 0,
revocable: _revocable
});
vestingScheduleIds.push(id);
vestingSchedulesTotalAmount += _amount;
emit VestingScheduleCreated(_beneficiary, _amount);
}
/// @notice 释放可提取的代币
function release(bytes32 scheduleId) external {
VestingSchedule storage schedule = vestingSchedules[scheduleId];
require(schedule.initialized, "Schedule not found");
uint256 releasable = _computeReleasableAmount(schedule);
require(releasable > 0, "Nothing to release");
schedule.released += releasable;
vestingSchedulesTotalAmount -= releasable;
token.safeTransfer(schedule.beneficiary, releasable);
emit Released(schedule.beneficiary, releasable);
}
/// @notice 计算可释放金额
function _computeReleasableAmount(VestingSchedule memory schedule)
internal view returns (uint256)
{
if (block.timestamp < schedule.cliff) {
return 0; // 悬崖期内不可提取
}
if (block.timestamp >= schedule.start + schedule.duration) {
return schedule.totalAmount - schedule.released; // 已到期,全部可提取
}
// 线性释放: (已过时间 / 总时长) * 总金额 - 已释放
uint256 elapsed = block.timestamp - schedule.start;
uint256 vested = (schedule.totalAmount * elapsed) / schedule.duration;
return vested - schedule.released;
}
function _computeScheduleId(address beneficiary, uint256 index) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(beneficiary, index));
}
}
// contracts/SimpleStaking.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title SimpleStaking
* @dev 质押合约——年化奖励分配
*/
contract SimpleStaking is Ownable {
using SafeERC20 for IERC20;
IERC20 public immutable stakingToken;
IERC20 public immutable rewardToken;
uint256 public rewardRate; // 每秒奖励量
uint256 public rewardsDuration; // 奖励周期(秒)
uint256 public periodFinish; // 当前奖励期结束时间
uint256 public rewardPerTokenStored;
uint256 public lastUpdateTime;
uint256 public totalSupply; // 总质押量
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;
mapping(address => uint256) private _balances;
event Staked(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event RewardPaid(address indexed user, uint256 reward);
constructor(address _stakingToken, address _rewardToken) Ownable(msg.sender) {
stakingToken = IERC20(_stakingToken);
rewardToken = IERC20(_rewardToken);
rewardsDuration = 30 days;
}
// ═══════ 修饰符:更新奖励 ═══════
modifier updateReward(address account) {
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
if (account != address(0)) {
rewards[account] = earned(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
}
_;
}
// ═══════ 质押 ═══════
function stake(uint256 amount) external updateReward(msg.sender) {
require(amount > 0, "Cannot stake 0");
totalSupply += amount;
_balances[msg.sender] += amount;
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
emit Staked(msg.sender, amount);
}
// ═══════ 取回 ═══════
function withdraw(uint256 amount) external updateReward(msg.sender) {
require(amount > 0, "Cannot withdraw 0");
require(_balances[msg.sender] >= amount, "Insufficient balance");
totalSupply -= amount;
_balances[msg.sender] -= amount;
stakingToken.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
// ═══════ 领取奖励 ═══════
function getReward() external updateReward(msg.sender) {
uint256 reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
rewardToken.safeTransfer(msg.sender, reward);
emit RewardPaid(msg.sender, reward);
}
}
// ═══════ 计算函数 ═══════
function rewardPerToken() public view returns (uint256) {
if (totalSupply == 0) return rewardPerTokenStored;
return rewardPerTokenStored + (
(lastTimeRewardApplicable() - lastUpdateTime) * rewardRate * 1e18 / totalSupply
);
}
function earned(address account) public view returns (uint256) {
return (
_balances[account] * (rewardPerToken() - userRewardPerTokenPaid[account]) / 1e18
) + rewards[account];
}
function lastTimeRewardApplicable() public view returns (uint256) {
return block.timestamp < periodFinish ? block.timestamp : periodFinish;
}
// ═══════ 管理函数 ═══════
function notifyRewardAmount(uint256 reward) external onlyOwner updateReward(address(0)) {
if (block.timestamp >= periodFinish) {
rewardRate = reward / rewardsDuration;
} else {
uint256 remaining = periodFinish - block.timestamp;
uint256 leftover = remaining * rewardRate;
rewardRate = (reward + leftover) / rewardsDuration;
}
lastUpdateTime = block.timestamp;
periodFinish = block.timestamp + rewardsDuration;
}
function balanceOf(address account) external view returns (uint256) {
return _balances[account];
}
}
1. 悬崖期(Cliff)在Vesting中的作用是什么?
2. Staking合约中rewardPerToken的计算为什么要除以totalSupply?
3. 为什么代币分配要设置4年线性释放?
你已掌握代币经济学设计!从供应模型到Vesting释放,从Staking质押到价值捕获,你懂得了设计可持续代币经济体系的方法论。
关键收获:
✅ 代币供应与分配模型
✅ Vesting合约(悬崖期+线性释放)
✅ Staking合约(rewardPerToken算法)
✅ 代币经济学设计10大原则
✅ 激励对齐与长期主义