ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

VE机制深度解析:锁1个月与锁4年投票权差多少?

2026/9/7 9:07:06 拓冰建站 浏览量
VE机制深度解析:锁1个月与锁4年投票权差多少? 在设计和分析代币经济模型时VEVote Escrowed机制是一个绕不开的话题。很多项目方在规划治理代币时都会面临一个相似的问题用户手里握着大量治理代币但真正参与投票的人很少持有者只关心价格不在乎协议长期发展。VE机制正是为解决这种“短期利益与长期治理失衡”而设计的它的核心逻辑是你越愿意把代币锁住得到的投票权和协议收益就越高。本文就围绕一个非常具体的场景展开如果把同一个代币分别锁定 1 个月和 4 年投票权会差多少这个差距是怎么计算出来的veToken 的衰减机制、托管流程、Bribe投票激励飞轮又该如何理解我会从概念、公式、代码到工程最佳实践逐步拆解这套模型。文中会包含可运行的 Solidity 示例合约方便做代币经济和合约开发的朋友直接参考。想深入理解 Web3 代币设计、治理模型和区块链开发的同学这篇内容可以收藏备用。1. 理解VE机制先回答“为什么锁仓能换来投票权”1.1 VE机制的诞生背景VE 的全称是 Vote Escrowed可以翻译为“投票托管”。它的核心思路并不复杂用户将项目中具备治理功能的代币比如协议的原生代币锁进一个智能合约换取一种特殊的、不可转让的治理凭证也就是我们常说的 veToken。这种机制最早被 Curve Finance 大规模采用。Curve 的 veCRV 模型让用户将 CRV 锁定为 veCRV锁仓时间越长账号在 Curve 治理体系中的投票权重就越高。veCRV 本身不可转让不能出售它只能用来参与协议治理、投票决定流动性矿池的奖励分配方向并享受平台交易费用分成。为什么会出现这种机制在传统代币治理中“1 个代币 1 票”看起来公平但在实践中存在几个明显问题短期投机者买入代币后并不关心协议长期发展投票质量差。没有治理动力很多持币者懒于投票。治理代币流动性很高普通用户随时可以卖掉导致治理决策与实际利益脱钩。大户可以通过短期大量买入代币在关键提案投票前后临时获取投票权。VE 机制通过“锁仓”这个动作筛选出真正愿意长期参与和持币的用户。锁仓越久越表明用户与项目站在同一战线。投票权不再是简单的一币一票而是“时间加权”的一币一票。1.2 VE锁仓与投票权的关系在 VE 模型中投票权通常不是静态的。同样是锁定一个代币今天锁和明天锁产生的投票权完全一样但“锁 1 个月”和“锁 4 年”所获得的初始投票权以及未来每个区块的实时投票权都会明显不同。这里要引入一个关键概念投票权随时间衰减。以 Curve 的线性衰减模型为例用户的投票权可以粗略表达为veToken 余额 锁定代币数量 × 剩余锁仓时间 / 最大锁仓时间最大锁仓时间在 Curve 中为 4 年约 1460 天。如果你把 10000 个 CRV 锁定 4 年初始获得 10000 个 veCRV剩余时间越长veCRV 余额越高反之越低。这意味着veToken 代表的不是“你锁了多少币”而是“你愿意绑定的时间价值”。1.3 为什么锁得越久权力越大从项目方的角度看用户锁仓时间越长代币的流通供应量就越少抛压也就越低。同时长期的 veToken 持有者更愿意参与治理因为他们和项目长期利益绑定在一起。从系统的角度看锁仓机制把“治理权”和“贡献度”挂钩。用户要获得更多投票权就必须真金白银地承担时间成本。这种机制天然筛选出治理意愿更强的用户也让投票结果更能反映长期利益。为什么这一点对 Web3 开发很重要因为治理代币经济模型的成败往往取决于代币的“流通压力”和“激励对齐”。VE 模型正是一套用时间换权利、用权利换收益的通用玩法。之后我们在 2 章会具体计算锁 1 个月和锁 4 年的差距。2. 锁1个月与锁4年投票权到底差多少2.1 投票权计算公式我们先约束计算前提。不同的项目对投票权衰减模型有不同定义有线性衰减、分段衰减和指数衰减等。为了能直观回答“锁 1 个月和锁 4 年差多少”这里使用最经典的线性衰减模型这也是 Curve 系列最常用的一种。定义如下用户锁仓代币数量为amount。最大锁仓时间MAXTIME 4 年为了方便计算我们按 1460 天估算。用户锁仓时设置的锁定时长为lockDuration单位是天。系统在用户锁仓时根据lockDuration铸造对应的 veToken。初始 veToken 计算公式为veToken 初始余额 amount × (lockDuration / MAXTIME)从这个公式可以看到在锁定刚开始的那一刻用户锁仓 4 年的时间系数为 1锁仓 1 个月的时间系数为 1/48 左右。之后veToken 余额会随着锁仓剩余时间的减少逐渐线性降低。为了简化我们先算初始投票权差异再分析衰减过程中的差异。2.2 核心数据测算假设用户锁定代币数量为 10000最大锁仓时间为 1460 天。情况一锁仓 1 个月lockDuration 30 天 veToken 初始余额 10000 × 30 / 1460 ≈ 205.48情况二锁仓 4 年lockDuration 1460 天 veToken 初始余额 10000 × 1460 / 1460 10000所以锁仓 1 个月的初始投票权大约是205.48 / 10000 ≈ 2.05%也就是说锁定相同数量的代币锁 4 年获得的初始投票权是锁 1 个月的约 48.6 倍。用表格来看更直观锁仓时长时间系数10000 代币获得的 veToken相对投票权1 个月30 天0.0205约 205.48约 2.05%1 年365 天0.25250025%2 年730 天0.5500050%3 年1095 天0.75750075%4 年1460 天110000100%再考虑衰减假设锁 1 个月的用户在第 15 天意见分歧时剩余锁仓时间为 15 天他的 veToken 余额变为10000 × 15 / 1460 ≈ 102.74而锁 4 年的用户剩余锁仓时间还非常长例如还剩 1400 天则 veToken 余额为10000 × 1400 / 1460 ≈ 9589.04两者差距更大接近 93 倍。这说明VE 机制不仅奖励长期锁仓的人还会让短期锁仓用户在极短的时间内快速“失去权力”。2.3 数据差背后的博弈逻辑既然锁仓越久投票权越大是不是所有用户都会选择锁 4 年显然不会。因为锁 4 年的资金被冻结时间太长流动性成本极高。用户选择的锁仓时长本质上是“期望收益”与“流动性成本”之间的权衡。长锁仓用户获得更高投票权同时也能拿到更多协议费用分红和投票激励也就是 Bribe 返利综合收益率可能更高。而短锁仓用户保持灵活性但对协议治理的影响很小更像一个被动参与者。这种机制自动完成了用户分层核心治理参与者深度绑定普通持币者保持流动性。3. VE机制的核心组件拆解3.1 veToken的不可转让设计veToken 有一个令人印象深刻的特性不可转让。它不是普通的 ERC-20 代币不能转账到别人的地址也不能在去中心化交易所出售。为什么这样设计因为一旦 veToken 可以转让锁仓时间就会变成一种可交易资产短期市场行为就会介入治理。用户可以临时买一个高投票权 veToken参与完提案再卖出治理的长期绑定属性会被严重削弱。不可转让的 veToken 迫使持币者只能通过长期锁定来获得治理权提升了操纵治理的成本。但这并不代表 veToken 完全不能流动。后续衍生出 veNFT 机制。在 Solidly 以及后来的 Velodrome、Aerodrome 等项目中veToken 被包装成不可分割质押代币但可以作为一个 NFTERC-721进行整体转让。该 NFT 内部封装了“锁定代币数量、锁定到期时间、所属用户”等完整信息。转让时整个锁仓头和剩余时间便一并转移。这种设计在保留治理绑定性的同时为用户的仓位提供了二级流动空间。你能把整个锁仓位卖掉但不能把它拆分成小份去交易。3.2 投票权衰减机制投票权衰减是 VE 机制的灵魂。我们在 2 章已经看到随着剩余锁仓时间的缩短veToken 余额会线性减少。这意味着即使你曾经锁定过大量代币如果不持续延长锁仓时间你的治理权也会持续缩水。衰减带来的核心效果有两个防止“一次锁仓永久统治”。鼓励用户持续锁仓或定期续锁形成长期建设循环。在一开始锁定 4 年的用户如果第 3 年还没有对锁仓时长进行展期他的 veToken 余额只剩最初的 25%。如果不更新最终会归零。这个机制使得治理参与者不能“躺赢”要持续参与和维护仓位。在代码实现上衰减通常是通过“检查当前时间相对到期时间的剩余比例”来实现的不需要像挖矿产量那样实时计算奖励只需要在查询投票权时动态计算即可。这一点在第四章的合约示例中会体现。3.3 投票激励Bribe与收益飞轮VE 机制还有一个常见配套组件Bribe中文语境中通常翻译为“投票激励”或“贿赂”。它并非贬义在协议设计中是一种半公开的激励市场。典型的运作流程如下项目方 A 希望自己的流动性池获得更多投票激励。项目方 A 在投票市场例如 Votium、Hidden Hand或多轮投票前向 veToken 持有者提供代币奖励。veToken 持有者投票给项目方 A 的流动性池。流动性池获得协议分发的基础奖励比如平台手续费收入或增发奖励。项目方 A 获得流动性线性 veToken 持有者获得额外收益。这套闭环让 VE 机制成为一个飞轮锁仓 - 获得投票权 - 投票获取收益 - 收益又促进更多锁仓。不过也要注意Bribe 机制可能会让投票行为变得过于“逐利化”治理讨论会被收益计算取代。所以在实际设计中项目方通常会保留团队否决权或治理提案门槛防止纯利益驱动导致决策偏差。3.4 ve(3,3) 等衍生模型VE 模型在 Curve 的成功催生了大量衍生设计。其中最有代表性的是 ve(3,3)。ve(3,3) 是 Fantom 生态中 Solidly 协议提出的模型名字结合了 Curve 的 ve 机制和 OlympusDAO 的 (3,3) 博弈理论。OlympusDAO 的 (3,3) 思路参考了囚徒困境如果大家一起锁仓收益最高如果大家都卖伤害最大。Solidly 将锁仓和释放、Bribe 整合在一起。在最经典的 ve(3,3) 设计中代币的释放量与 veToken 锁仓量挂钩。流动性池奖励由 veToken 持有者投票决定每周一轮。持有代币并锁仓的用户可以获得协议原生收益同时其锁仓行为本身还能间接带动代币价值共识。投票权同样随时间衰减到期后需要重新锁仓。后续的 Velodrome、Aerodrome 等项目将这种模式进一步延伸为“协议自有流动性”模型。协议通过发放自身代币激励流动性提供者再引导 LP 将激励锁定为 veToken配合 Bribe 市场形成对流动性的长期锁定。这些设计的底层仍然是最早的 ve 模型。理解这些衍生模型关键还是先把时间加权投票权这套基础逻辑弄透彻。这也是我们接下来写合约的起点。4. 手写一个VE锁仓合约为了更直观理解 VE 机制我们不用黑盒项目手写一个简化的 VE 锁仓合约。合约会实现以下关键能力用户存入指定代币ERC-20并获得 veToken 余额。用户可以选择不同的锁仓时长1 个月、1 年、4 年等。veToken 不可转让但可以通过getVotingPower查询当前投票权。锁仓到期后用户可以赎回底层代币。投票权按剩余锁仓时间线性衰减。这个合约主要用来演示 VE 机制的数学逻辑生产环境还需要考虑安全审计、重入保护、白名单等问题。示例使用 Solidity 0.8.17 语法开发环境推荐 Remix 或 Hardhat。4.1 合约设计我们创建三个文件结构如下contracts/ ├── IVEUser.sol // 接口定义与事件 ├── SimpleVEManager.sol // 锁仓主合约 └── MockERC20.sol // 测试用的代币合约SimpleVEManager是核心合约完成的功能lock(uint256 amount, uint256 duration)锁定代币计算 veToken 余额。withdraw(uint256 id)到期解锁并取回底层代币。getVotingPower(uint256 id)查询某一锁仓位的当前投票权。getLockInfo(uint256 id)查询锁仓位详情。这里使用LockPosition结构体保存锁仓位信息让一个用户可创建多个仓位。4.2 核心代码实现先用 OpenZeppelin 的IERC20只是作为转账接口不引入完整依赖提供最小实现即可。如果你在 Hardhat 项目中使用可以先安装openzeppelin/contracts。contracts/MockERC20.sol文件内容// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import openzeppelin/contracts/token/ERC20/ERC20.sol; contract MockERC20 is ERC20 { constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) { _mint(msg.sender, 1_000_000 * 10 ** decimals()); } // 测试方便任何人可以获得 10000 个测试代币 function faucet() external { _mint(msg.sender, 10_000 * 10 ** decimals()); } }contracts/SimpleVEManager.sol文件内容// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import openzeppelin/contracts/token/ERC20/IERC20.sol; import openzeppelin/contracts/security/ReentrancyGuard.sol; contract SimpleVEManager is ReentrancyGuard { IERC20 public immutable stakingToken; uint256 public constant MAXTIME 4 * 365 * 1 days; // 4年 uint256 public nextId 1; struct LockPosition { address owner; uint256 amount; // 锁定代币数量原始代币 uint256 start; // 锁定开始时间 uint256 end; // 锁定结束时间 bool withdrawn; // 是否已取回 } mapping(uint256 LockPosition) public positions; mapping(address uint256[]) public positionIds; event Locked(uint256 indexed id, address indexed owner, uint256 amount, uint256 duration, uint256 votingPower); event Withdrawn(uint256 indexed id, address indexed owner, uint256 amount); constructor(address _stakingToken) { stakingToken IERC20(_stakingToken); } /** * dev 锁定代币 * param amount 锁定数量 * param duration 锁定时长秒最大不能超过 MAXTIME */ function lock(uint256 amount, uint256 duration) external nonReentrant { require(amount 0, amount must be 0); require(duration 1 days, duration too short); require(duration MAXTIME, duration exceeds max); // 转账代币到合约 require(stakingToken.transferFrom(msg.sender, address(this), amount), transfer failed); uint256 id nextId; uint256 end block.timestamp duration; positions[id] LockPosition({ owner: msg.sender, amount: amount, start: block.timestamp, end: end, withdrawn: false }); positionIds[msg.sender].push(id); uint256 votingPower getVotingPower(id); emit Locked(id, msg.sender, amount, duration, votingPower); } /** * dev 到期后取回代币 */ function withdraw(uint256 id) external nonReentrant { LockPosition storage pos positions[id]; require(pos.owner msg.sender, not owner); require(!pos.withdrawn, already withdrawn); require(block.timestamp pos.end, lock not expired); pos.withdrawn true; require(stakingToken.transfer(msg.sender, pos.amount), transfer failed); emit Withdrawn(id, msg.sender, pos.amount); } /** * dev 计算某仓位当前投票权线性衰减 * veBalance amount * remaining_time / MAXTIME */ function getVotingPower(uint256 id) public view returns (uint256) { LockPosition memory pos positions[id]; if (pos.amount 0) return 0; if (block.timestamp pos.end) return 0; uint256 remaining pos.end - block.timestamp; return (pos.amount * remaining) / MAXTIME; } /** * dev 获取某用户的所有仓位 id */ function getPositionIds(address user) external view returns (uint256[] memory) { return positionIds[user]; } /** * dev 查询仓位详情 */ function getLockInfo(uint256 id) external view returns ( address owner, uint256 amount, uint256 start, uint256 end, bool withdrawn, uint256 votingPower ) { LockPosition memory pos positions[id]; return (pos.owner, pos.amount, pos.start, pos.end, pos.withdrawn, getVotingPower(id)); } }4.3 运行与验证用 Remix IDE 是最简单的方式。步骤新建一个项目或工作区。将MockERC20.sol和SimpleVEManager.sol直接放入contracts目录。部署MockERC20获得测试代币地址。部署SimpleVEManager构造函数传入 MockERC20 地址。在 MockERC20 中调用faucet()领取 10000 个测试代币。在 MockERC20 中调用approve(SimpleVEManager地址, 10000)授权。在 SimpleVEManager 中调用lock(10000, 30 days)锁定 1 个月。再次调用lock(10000, 4 * 365 days)锁定 4 年。分别查询两个仓位的getVotingPower。预期结果类似仓位1锁1个月的 votingPower ≈ 2054 仓位2锁4年的 votingPower ≈ 10000注意这里 amount 是 10000 * 10^18因为代币带 18 位小数。但源码计算中amount * remaining / MAXTIME在小数精度上没问题因为分子分母同量纲。如果使用 Hardhat 部署部署脚本大致如下const { ethers } require(hardhat); async function main() { const MockERC20 await ethers.getContractFactory(MockERC20); const token await MockERC20.deploy(Mock Token, MOCK); await token.deployed(); const SimpleVEManager await ethers.getContractFactory(SimpleVEManager); const veManager await SimpleVEManager.deploy(token.address); await veManager.deployed(); await token.faucet(); await token.approve(veManager.address, ethers.parseEther(20000)); await veManager.lock(ethers.parseEther(10000), 30 * 24 * 3600); await veManager.lock(ethers.parseEther(10000), 4 * 365 * 24 * 3600); console.log(仓位1 投票权:, (await veManager.getVotingPower(1)).toString()); console.log(仓位2 投票权:, (await veManager.getVotingPower(2)).toString()); } main().catch(console.error);4.4 结果说明从这个合约可以看到锁仓 1 个月的仓位在一开始就只拥有约 20% 的时间系数即 2054 / 10000 20.54%。前面我们计算初始投票权是 2.05%这里为什么是 20%因为合约中amount是 10000 * 10^18计算得到 2054 * 10^18除以 10000 * 10^18 得到 0.2054即 20.54%。按比例来看仍然符合“锁 4 年约为锁 1 个月的 48.6 倍”的结论比例关系不变。读者要注意在真实 Curve 模型中amount使用的是缩放后的内部表示计算时会做额外的精度调整但数学本质是相同的。这里的简化合约适合作为学习模板。5. 常见问题与排查思路在实际开发和测试 VE 合约时有几个问题经常出现。下面用表格来总结常见错误与排查方向。问题现象常见原因解决思路lock调用失败提示 transfer failedMock ERC20 的 approve 未设置或授权额度不足先调用approve额度至少等于锁定数量getVotingPower返回 0锁定期已结束或刚刚到期检查合约当前时间是否超过end如果到期投票权归零lock时 duration 传错单位把天数和秒混淆比如直接传365使用365 days或365 * 24 * 3600Solidity 时间单位自动换算秒某用户无法查询仓位列表用户地址传错或锁仓时使用了不同地址在positionIds中检查该地址的仓位 id合约部署时报 Out of Gas部署时没有增加 gas 限制此时合约较复杂在 Remix 或 Hardhat 中指定 gas 上限例如gasLimit: 3000000锁仓后想提前赎回用户需求与模型冲突重新阅读模型设计VE 合约不允许提前赎回这是特性而非 bug投票权精度出现误差除法取整导致精度丢失使用更高的精度缩放例如将amount乘以 10^18 再计算使用生产环境代币测试真实代币转入测试合约后无法轻易转回始终使用测试网代币或 Mock 代币进行验证排错建议按以下顺序检查授权额度是否足够。检查合约当前时间与end时间的关系。检查输入的时间单位。使用事件日志查看锁仓事件与投票权计算结果。在 Remix 中逐步调用getLockInfo查看每个仓位字段。6. VE机制的最佳实践与工程建议6.1 锁仓参数设计在自研 VE 模型时MAXTIME的选择很关键。较长的时间上限能提高最高投票权可能会吸引更深度长期锁仓用户但也会降低新用户参与意愿因为没人一开始就敢锁定太长时间。较短的时间上限能增加用户参与度但容易导致投票权集中在大户手中。推荐做法先将 MAXTIME 设计为 1 到 4 年之间支持“锁仓后不能更改时长”或“可以续锁加长时间但不支持减短”。续锁功能在很多协议中都有实现让用户在原仓位上延长到期时间避免损失已有投票权。6.2 veToken 的登记与查询生产级合约通常不会在每次查询时都去修改存储那样 gas 成本过高。更常见的做法是通过“全局衰减系数”来批量计算。例如定义全局globalPoint和用户本地bias、slope这样在大量用户查询或投票时gas 成本更可控。我们的演示合约使用了最直观的线性计算方式适合教学但性能不是生产最优。如果你要对上万用户做投票权查询建议学习 Curve 的VotingEscrow.vy源码中 bias/slope 的数学表示。6.3 安全与权限控制VE 锁仓合约涉及大量资金托管需要重视安全边界使用 OpenZeppelin 的ReentrancyGuard防止重入。提款前必须检查锁仓到期时间。合约中不能有管理员直接提取用户资金的接口。建议引入紧急暂停机制但暂停权限应通过多签钱包或时间锁控制。如果支持将 veToken 包装为 NFT 转让需要同时考虑投票权委托的边界。合约升级前尽量迁移用户仓位或让用户在到期后再迁移不建议强行转移资金。6.4 投票激励与治理边界虽然 Bribe 机制可以活跃生态但也要防止治理决策被过度市场化和短期化。项目方可以设置“投票最低锁仓时间”比如只有锁定超过 1 年的用户才能参与重大提案投票。也可以通过 quorum法定投票比例限制来防止少数用户主导治理。另外在对外宣传和文档中不要给用户承诺“锁仓一定赚钱”。VE 机制是一种治理与激励设计不构成投资收益承诺。这对项目合规形象的建立也很有帮助。6.5 合约测试与审计清单在部署前建议覆盖以下测试场景1. 锁定代币后投票权在起始点是否满足预期。 2. 锁定过半时投票权是否约等于初始的一半。 3. 到期后提款是否正常是否重复提款被拦截。 4. 非所属用户调 withdraw 是否被拒绝。 5. 授权额度不足时 lock 是否回滚。 6. 多个仓位并存时查询和提款是否互不影响。 7. 对大额和小额的锁定分别测试精度问题。审计方面如果项目涉及真实资金建议寻求专业审计团队不能只依赖单元测试。7. 总结与延伸学习方向回到本文的核心问题锁 1 个月和锁 4 年投票权差多少在相同的线性衰减模型下锁 4 年获得的初始投票权约是锁 1 个月的 48 倍随着时间推移差距还会继续拉大。这个差异深刻地改变了治理代币的博弈结构——短锁仓用户在协议中的话语权非常有限长锁仓用户则成为治理生态的中坚力量。从工程角度我们用一个 Solidity 合约演示了锁仓、计算投票权、到期赎回的完整流程。核心公式就是votingPower amount * remainingTime / MAXTIME。理解了这一点后续再看 Curve 的 VotingEscrow、Solidly 的 ve(3,3)、Velodrome 的自有流动性模型都会轻松很多。接下来你可以沿着几个方向继续深入阅读 CurveVotingEscrow.vy源码学习 bias/slope 高效衰减算法。研究 veNFT 方案了解如何将 veToken 仓位封装成可转让的 NFT。分析真实项目的 ve 模型参数比如 MAXTIME、衰减模型、Bribe 市场设计。尝试在测试网上部署一个带投票功能的 ve 治理 DApp。VE 机制的底层设计思路并不神秘核心是“用牺牲流动性换取治理权力”。只需要一个合理的时间加权公式再加一套不可转让的治理凭证就能改变整个代币经济的博弈方向。无论你是做 Web3 开发还是设计代币经济模型掌握这套机制都会非常实用。希望本文对你有帮助。如果你在按示例合约部署时遇到问题或者在设计自己的 veToken 参数时有疑问欢迎留言交流。