📊 第22课:代币经济学

治理与未来 阶段五 ✅ 验证通过

🎯 学习目标:理解代币经济学(Tokenomics)设计原则,掌握供应量模型、分配机制、释放计划、价值捕获机制,学会用Solidity实现Vesting和Staking合约。

📖 一、代币经济学核心要素

代币经济学设计框架 ┌─────────────────────────────────────────────┐ │ 代币经济学 │ │ │ │ ┌─────────┐ ┌─────────┐ ┌──────────┐ │ │ │ 供应模型 │ │ 分配机制 │ │ 价值捕获 │ │ │ │ │ │ │ │ │ │ │ │•总量 │ │•团队比例 │ │•手续费 │ │ │ │•通胀/通缩│ │•社区比例 │ │•销毁 │ │ │ │•铸造权限 │ │•投资人 │ │•质押收益 │ │ │ │•销毁机制 │ │•国库 │ │•治理权 │ │ │ └─────────┘ └─────────┘ └──────────┘ │ │ │ │ ┌─────────┐ ┌─────────┐ ┌──────────┐ │ │ │ 释放计划 │ │ 效用设计 │ │ 激励对齐 │ │ │ │ │ │ │ │ │ │ │ │•悬崖期 │ │•支付 │ │•长期持有 │ │ │ │•线性释放 │ │•访问 │ │•参与治理 │ │ │ │•里程碑 │ │•质押 │ │•贡献奖励 │ │ │ └─────────┘ └─────────┘ └──────────┘ │ └─────────────────────────────────────────────┘

1.1 供应量模型

模型特点代表
固定供应总量不变,通缩预期BTC(2100万)
通胀供应持续增发,按规则铸造ETH(增发+销毁)
双代币治理代币+稳定币分离MakerDAO(MKR+DAI)
通缩代币每笔交易销毁一部分SAFE代币
弹性供应自动增发/缩减维持价格Ampleforth(已衰落)

1.2 代币分配案例

// 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年线性释放防止团队砸盘

📖 二、Vesting(代币释放)合约

// 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));
    }
}

📖 三、Staking(质押)合约

// 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];
    }
}

📖 四、代币经济学设计原则

🔑 代币经济学设计10大原则:

1. 效用优先 — 代币必须有实际用途,不只是投机工具
2. 长期对齐 — 释放计划应激励长期持有和参与
3. 社区优先 — 社区分配应占最大比例
4. 透明公平 — 分配规则公开透明
5. 防鲸鱼 — 避免少数地址持有过多代币
6. 价值回流 — 协议收入应回流代币持有者
7. 合理通胀 — 增发率应匹配生态增长
8. 安全缓冲 — 国库保留足够运营资金
9. 治理参与 — 代币与治理权绑定
10. 可升级性 — 留出参数调整空间

🧪 练习

1. 悬崖期(Cliff)在Vesting中的作用是什么? 2. Staking合约中rewardPerToken的计算为什么要除以totalSupply? 3. 为什么代币分配要设置4年线性释放?
📊

🏆 成就解锁:代币经济学家

你已掌握代币经济学设计!从供应模型到Vesting释放,从Staking质押到价值捕获,你懂得了设计可持续代币经济体系的方法论。

关键收获:

✅ 代币供应与分配模型
✅ Vesting合约(悬崖期+线性释放)
✅ Staking合约(rewardPerToken算法)
✅ 代币经济学设计10大原则
✅ 激励对齐与长期主义