npx skills add https://github.com/cyotee/aerodrome-skill --skill 'Aerodrome Emissions'Minter 合约控制 AERO 代币的排放,并将其分配给 Voter(用于流动性提供者)和 RewardsDistributor(用于 veNFT 持有者的复利再分配)。
┌──────────────────────────────────────────────────────────────┐
│ 排放系统 │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ Minter │◄───────── EpochGovernor (尾部排放率) │
│ └──────┬──────┘ │
│ │ updatePeriod() │
│ │ │
│ ├──────────────────────────────────────────────┐ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐│
│ │ Voter │ │ Rewards ││
│ │ (排放) │ │ Distributor ││
│ └──────┬──────┘ │ (复利) ││
│ │ └──────┬──────┘│
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐│
│ │ Gauges │ │ veNFT ││
│ │ (LP 奖励) │ │ 持有者 ││
│ └─────────────┘ └─────────────┘│
│ │
└──────────────────────────────────────────────────────────────┘
contract Minter is IMinter {
IAero public immutable aero;
IVoter public immutable voter;
IVotingEscrow public immutable ve;
IRewardsDistributor public immutable rewardsDistributor;
uint256 public constant WEEK = 1 weeks;
uint256 public constant WEEKLY_DECAY = 9_900; // 99% (1% 衰减)
uint256 public constant WEEKLY_GROWTH = 10_300; // 103% (3% 增长)
uint256 public constant MAX_BPS = 10_000;
// 尾部排放参数
uint256 public constant MAXIMUM_TAIL_RATE = 100; // 1%
uint256 public constant MINIMUM_TAIL_RATE = 1; // 0.01%
uint256 public constant NUDGE = 1; // 每次调整 0.01%
uint256 public constant TAIL_START = 8_969_150 * 1e18; // 约 8.97M 阈值
uint256 public tailEmissionRate = 67; // 0.67% (67 个基点)
uint256 public teamRate = 500; // 5%
uint256 public weekly = 10_000_000 * 1e18; // 起始 10M/周
uint256 public activePeriod;
uint256 public epochCount;
address public team;
}
┌──────────────────────────────────────────────────────────────┐
│ 排放阶段 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 阶段 1: 增长期 (Epochs 1-14) │
│ ──────────────────────────── │
│ • 每周排放量每 epoch 增加 3% │
│ • 10M → 10.3M → 10.61M → ... → ~14.6M │
│ │
│ 阶段 2: 衰减期 (Epochs 15+) │
│ ──────────────────────────── │
│ • 每周排放量每 epoch 减少 1% │
│ • 持续直到达到 TAIL_START 阈值 │
│ │
│ 阶段 3: 尾部排放 │
│ ──────────────────────────── │
│ • 当 weekly < 8.97M AERO 时 │
│ • 排放量 = 总供应量 × tailEmissionRate / 10000 │
│ • 排放率可通过 EpochGovernor 调整 (0.01% - 1%) │
│ │
└──────────────────────────────────────────────────────────────┘
function updatePeriod() external returns (uint256 _period) {
_period = activePeriod;
if (block.timestamp >= _period + WEEK) {
epochCount++;
_period = (block.timestamp / WEEK) * WEEK;
activePeriod = _period;
uint256 _weekly = weekly;
uint256 _emission;
uint256 _totalSupply = aero.totalSupply();
bool _tail = _weekly < TAIL_START;
if (_tail) {
// 尾部排放:占总供应量的百分比
_emission = (_totalSupply * tailEmissionRate) / MAX_BPS;
} else {
// 正常排放
_emission = _weekly;
if (epochCount < 15) {
// 增长期:每 epoch +3%
_weekly = (_weekly * WEEKLY_GROWTH) / MAX_BPS;
} else {
// 衰减期:每 epoch -1%
_weekly = (_weekly * WEEKLY_DECAY) / MAX_BPS;
}
weekly = _weekly;
}
// 计算复利 (veNFT 持有者的抗稀释)
uint256 _growth = calculateGrowth(_emission);
// 团队分配
uint256 _rate = teamRate;
uint256 _teamEmissions = (_rate * (_growth + _weekly)) / (MAX_BPS - _rate);
// 铸造所需代币
uint256 _required = _growth + _emission + _teamEmissions;
uint256 _balanceOf = aero.balanceOf(address(this));
if (_balanceOf < _required) {
aero.mint(address(this), _required - _balanceOf);
}
// 分发
aero.safeTransfer(address(team), _teamEmissions);
aero.safeTransfer(address(rewardsDistributor), _growth);
rewardsDistributor.checkpointToken();
aero.safeApprove(address(voter), _emission);
voter.notifyRewardAmount(_emission);
emit Mint(msg.sender, _emission, aero.totalSupply(), _tail);
}
}
复利用于补偿 veNFT 持有者因稀释造成的损失:
/// @notice 计算复利数量 (锁仓者的抗稀释)
function calculateGrowth(uint256 _minted) public view returns (uint256 _growth) {
uint256 _veTotal = ve.totalSupplyAt(activePeriod - 1);
uint256 _aeroTotal = aero.totalSupply();
// 公式: (minted * (total - locked) / total)^2 / total / 2
// 当锁仓量较少时,给予更多复利
return (((_minted * (_aeroTotal - _veTotal)) / _aeroTotal) *
((_aeroTotal - _veTotal)) / _aeroTotal) / 2;
}
/// @notice 根据治理投票调整尾部排放率
function nudge() external {
address _epochGovernor = voter.epochGovernor();
if (msg.sender != _epochGovernor) revert NotEpochGovernor();
IEpochGovernor.ProposalState _state = IEpochGovernor(_epochGovernor).result();
if (weekly >= TAIL_START) revert TailEmissionsInactive();
uint256 _period = activePeriod;
if (proposals[_period]) revert AlreadyNudged();
uint256 _newRate = tailEmissionRate;
uint256 _oldRate = _newRate;
if (_state != IEpochGovernor.ProposalState.Expired) {
if (_state == IEpochGovernor.ProposalState.Succeeded) {
// 提高排放率 (更多排放)
_newRate = _oldRate + NUDGE > MAXIMUM_TAIL_RATE
? MAXIMUM_TAIL_RATE
: _oldRate + NUDGE;
} else {
// 降低排放率 (更少排放)
_newRate = _oldRate - NUDGE < MINIMUM_TAIL_RATE
? MINIMUM_TAIL_RATE
: _oldRate - NUDGE;
}
tailEmissionRate = _newRate;
}
proposals[_period] = true;
emit Nudge(_period, _oldRate, _newRate);
}
向 veNFT 持有者分发复利:
contract RewardsDistributor is IRewardsDistributor {
uint256 public constant WEEK = 7 days;
address public immutable ve;
address public immutable token; // AERO
uint256 public startTime;
uint256 public timeCursor;
mapping(uint256 => uint256) public timeCursorOf; // tokenId => cursor
mapping(uint256 => uint256) public userEpochOf; // tokenId => epoch
uint256 public lastTokenTime;
uint256[10000000000000] public tokensPerWeek;
uint256 public tokenLastBalance;
/// @notice 检查点代币余额
function checkpointToken() external {
assert(msg.sender == depositor);
_checkpointToken();
}
function _checkpointToken() internal {
uint256 tokenBalance = IERC20(token).balanceOf(address(this));
uint256 toDistribute = tokenBalance - tokenLastBalance;
tokenLastBalance = tokenBalance;
uint256 t = lastTokenTime;
uint256 sinceLast = block.timestamp - t;
lastTokenTime = block.timestamp;
uint256 thisWeek = (t / WEEK) * WEEK;
uint256 nextWeek = 0;
for (uint256 i = 0; i < 20; i++) {
nextWeek = thisWeek + WEEK;
if (block.timestamp < nextWeek) {
tokensPerWeek[thisWeek] += toDistribute;
break;
} else {
tokensPerWeek[thisWeek] += toDistribute;
toDistribute = 0;
}
thisWeek = nextWeek;
}
}
/// @notice 为 veNFT 申领复利
function claim(uint256 _tokenId) external returns (uint256) {
uint256 _lastTokenTime = lastTokenTime;
if (block.timestamp > _lastTokenTime) {
_checkpointTotalSupply();
}
uint256 amount = _claim(_tokenId, ve, _lastTokenTime);
if (amount != 0) {
IVotingEscrow(ve).depositFor(_tokenId, amount);
tokenLastBalance -= amount;
}
return amount;
}
/// @notice 为多个 tokenId 申领
function claimMany(uint256[] memory _tokenIds) external returns (bool) {
uint256 _lastTokenTime = lastTokenTime;
if (block.timestamp > _lastTokenTime) {
_checkpointTotalSupply();
}
for (uint256 i = 0; i < _tokenIds.length; i++) {
uint256 amount = _claim(_tokenIds[i], ve, _lastTokenTime);
if (amount != 0) {
IVotingEscrow(ve).depositFor(_tokenIds[i], amount);
tokenLastBalance -= amount;
}
}
return true;
}
}
┌──────────────────────────────────────────────────────────────┐
│ 每周分配 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 总铸造量 = 排放量 + 复利 + 团队排放量 │
│ │
│ 其中: │
│ • 排放量 = weekly (或 tailRate × totalSupply) │
│ • 复利 = veNFT 持有者的复利再分配 │
│ • 团队排放量 = teamRate × (复利 + 周排放量) / (1 - 费率) │
│ │
│ 分配: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 团队 ◄──────────── 团队排放量 │ │
│ │ │ │
│ │ RewardsDistributor ◄──── 复利 │ │
│ │ │ │
│ │ Voter ◄──────────── 排放量 (LP 奖励) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
/// @notice 设置团队地址
function setTeam(address _team) external {
if (msg.sender != team) revert NotTeam();
if (_team == address(0)) revert ZeroAddress();
pendingTeam = _team;
}
function acceptTeam() external {
if (msg.sender != pendingTeam) revert NotPendingTeam();
team = pendingTeam;
delete pendingTeam;
emit AcceptTeam(team);
}
/// @notice 设置团队排放率 (最高 5%)
function setTeamRate(uint256 _rate) external {
if (msg.sender != team) revert NotTeam();
if (_rate > MAXIMUM_TEAM_RATE) revert RateTooHigh();
teamRate = _rate;
}
// Minter 事件
event Mint(address indexed sender, uint256 weekly, uint256 circulating, bool isTail);
event Nudge(uint256 indexed period, uint256 oldRate, uint256 newRate);
event AcceptTeam(address indexed team);
event DistributeLiquid(address indexed to, uint256 amount);
event DistributeLocked(address indexed to, uint256 amount, uint256 tokenId);
// RewardsDistributor 事件
event CheckpointToken(uint256 time, uint256 tokens);
event Claimed(uint256 indexed tokenId, uint256 amount, uint256 claimEpoch, uint256 maxEpoch);
contracts/Minter.sol - 排放控制器contracts/RewardsDistributor.sol - 复利分配器contracts/EpochGovernor.sol - 尾部排放率治理contracts/interfaces/IMinter.sol - Minter 接口每周安装量
–
代码仓库
首次出现
–
安全审计
The Minter contract controls AERO token emissions and distributes them to the Voter (for LPs) and RewardsDistributor (for veNFT holders as rebases).
┌──────────────────────────────────────────────────────────────┐
│ EMISSION SYSTEM │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ Minter │◄───────── EpochGovernor (tail rate) │
│ └──────┬──────┘ │
│ │ updatePeriod() │
│ │ │
│ ├──────────────────────────────────────────────┐ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐│
│ │ Voter │ │ Rewards ││
│ │ (Emissions) │ │ Distributor ││
│ └──────┬──────┘ │ (Rebases) ││
│ │ └──────┬──────┘│
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐│
│ │ Gauges │ │ veNFT ││
│ │ (LP Rewards)│ │ Holders ││
│ └─────────────┘ └─────────────┘│
│ │
└──────────────────────────────────────────────────────────────┘
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
contract Minter is IMinter {
IAero public immutable aero;
IVoter public immutable voter;
IVotingEscrow public immutable ve;
IRewardsDistributor public immutable rewardsDistributor;
uint256 public constant WEEK = 1 weeks;
uint256 public constant WEEKLY_DECAY = 9_900; // 99% (1% decay)
uint256 public constant WEEKLY_GROWTH = 10_300; // 103% (3% growth)
uint256 public constant MAX_BPS = 10_000;
// Tail emission parameters
uint256 public constant MAXIMUM_TAIL_RATE = 100; // 1%
uint256 public constant MINIMUM_TAIL_RATE = 1; // 0.01%
uint256 public constant NUDGE = 1; // 0.01% per nudge
uint256 public constant TAIL_START = 8_969_150 * 1e18; // ~8.97M threshold
uint256 public tailEmissionRate = 67; // 0.67% (67 basis points)
uint256 public teamRate = 500; // 5%
uint256 public weekly = 10_000_000 * 1e18; // Starting 10M/week
uint256 public activePeriod;
uint256 public epochCount;
address public team;
}
┌──────────────────────────────────────────────────────────────┐
│ EMISSION PHASES │
├──────────────────────────────────────────────────────────────┤
│ │
│ Phase 1: Growth (Epochs 1-14) │
│ ──────────────────────────── │
│ • Weekly emissions INCREASE by 3% per epoch │
│ • 10M → 10.3M → 10.61M → ... → ~14.6M │
│ │
│ Phase 2: Decay (Epochs 15+) │
│ ──────────────────────────── │
│ • Weekly emissions DECREASE by 1% per epoch │
│ • Continues until reaching TAIL_START threshold │
│ │
│ Phase 3: Tail Emissions │
│ ──────────────────────────── │
│ • When weekly < 8.97M AERO │
│ • Emission = totalSupply × tailEmissionRate / 10000 │
│ • Rate adjustable via EpochGovernor (0.01% - 1%) │
│ │
└──────────────────────────────────────────────────────────────┘
function updatePeriod() external returns (uint256 _period) {
_period = activePeriod;
if (block.timestamp >= _period + WEEK) {
epochCount++;
_period = (block.timestamp / WEEK) * WEEK;
activePeriod = _period;
uint256 _weekly = weekly;
uint256 _emission;
uint256 _totalSupply = aero.totalSupply();
bool _tail = _weekly < TAIL_START;
if (_tail) {
// Tail emissions: percentage of total supply
_emission = (_totalSupply * tailEmissionRate) / MAX_BPS;
} else {
// Normal emissions
_emission = _weekly;
if (epochCount < 15) {
// Growth phase: +3% per epoch
_weekly = (_weekly * WEEKLY_GROWTH) / MAX_BPS;
} else {
// Decay phase: -1% per epoch
_weekly = (_weekly * WEEKLY_DECAY) / MAX_BPS;
}
weekly = _weekly;
}
// Calculate rebase (anti-dilution for veNFT holders)
uint256 _growth = calculateGrowth(_emission);
// Team allocation
uint256 _rate = teamRate;
uint256 _teamEmissions = (_rate * (_growth + _weekly)) / (MAX_BPS - _rate);
// Mint required tokens
uint256 _required = _growth + _emission + _teamEmissions;
uint256 _balanceOf = aero.balanceOf(address(this));
if (_balanceOf < _required) {
aero.mint(address(this), _required - _balanceOf);
}
// Distribute
aero.safeTransfer(address(team), _teamEmissions);
aero.safeTransfer(address(rewardsDistributor), _growth);
rewardsDistributor.checkpointToken();
aero.safeApprove(address(voter), _emission);
voter.notifyRewardAmount(_emission);
emit Mint(msg.sender, _emission, aero.totalSupply(), _tail);
}
}
Rebases compensate veNFT holders for dilution:
/// @notice Calculate rebase amount (anti-dilution for lockers)
function calculateGrowth(uint256 _minted) public view returns (uint256 _growth) {
uint256 _veTotal = ve.totalSupplyAt(activePeriod - 1);
uint256 _aeroTotal = aero.totalSupply();
// Formula: (minted * (total - locked) / total)^2 / total / 2
// This gives more rebase when less is locked
return (((_minted * (_aeroTotal - _veTotal)) / _aeroTotal) *
((_aeroTotal - _veTotal)) / _aeroTotal) / 2;
}
/// @notice Adjust tail emission rate based on governance vote
function nudge() external {
address _epochGovernor = voter.epochGovernor();
if (msg.sender != _epochGovernor) revert NotEpochGovernor();
IEpochGovernor.ProposalState _state = IEpochGovernor(_epochGovernor).result();
if (weekly >= TAIL_START) revert TailEmissionsInactive();
uint256 _period = activePeriod;
if (proposals[_period]) revert AlreadyNudged();
uint256 _newRate = tailEmissionRate;
uint256 _oldRate = _newRate;
if (_state != IEpochGovernor.ProposalState.Expired) {
if (_state == IEpochGovernor.ProposalState.Succeeded) {
// Increase rate (more emissions)
_newRate = _oldRate + NUDGE > MAXIMUM_TAIL_RATE
? MAXIMUM_TAIL_RATE
: _oldRate + NUDGE;
} else {
// Decrease rate (less emissions)
_newRate = _oldRate - NUDGE < MINIMUM_TAIL_RATE
? MINIMUM_TAIL_RATE
: _oldRate - NUDGE;
}
tailEmissionRate = _newRate;
}
proposals[_period] = true;
emit Nudge(_period, _oldRate, _newRate);
}
Distributes rebases to veNFT holders:
contract RewardsDistributor is IRewardsDistributor {
uint256 public constant WEEK = 7 days;
address public immutable ve;
address public immutable token; // AERO
uint256 public startTime;
uint256 public timeCursor;
mapping(uint256 => uint256) public timeCursorOf; // tokenId => cursor
mapping(uint256 => uint256) public userEpochOf; // tokenId => epoch
uint256 public lastTokenTime;
uint256[10000000000000] public tokensPerWeek;
uint256 public tokenLastBalance;
/// @notice Checkpoint token balance
function checkpointToken() external {
assert(msg.sender == depositor);
_checkpointToken();
}
function _checkpointToken() internal {
uint256 tokenBalance = IERC20(token).balanceOf(address(this));
uint256 toDistribute = tokenBalance - tokenLastBalance;
tokenLastBalance = tokenBalance;
uint256 t = lastTokenTime;
uint256 sinceLast = block.timestamp - t;
lastTokenTime = block.timestamp;
uint256 thisWeek = (t / WEEK) * WEEK;
uint256 nextWeek = 0;
for (uint256 i = 0; i < 20; i++) {
nextWeek = thisWeek + WEEK;
if (block.timestamp < nextWeek) {
tokensPerWeek[thisWeek] += toDistribute;
break;
} else {
tokensPerWeek[thisWeek] += toDistribute;
toDistribute = 0;
}
thisWeek = nextWeek;
}
}
/// @notice Claim rebase for a veNFT
function claim(uint256 _tokenId) external returns (uint256) {
uint256 _lastTokenTime = lastTokenTime;
if (block.timestamp > _lastTokenTime) {
_checkpointTotalSupply();
}
uint256 amount = _claim(_tokenId, ve, _lastTokenTime);
if (amount != 0) {
IVotingEscrow(ve).depositFor(_tokenId, amount);
tokenLastBalance -= amount;
}
return amount;
}
/// @notice Claim for multiple tokenIds
function claimMany(uint256[] memory _tokenIds) external returns (bool) {
uint256 _lastTokenTime = lastTokenTime;
if (block.timestamp > _lastTokenTime) {
_checkpointTotalSupply();
}
for (uint256 i = 0; i < _tokenIds.length; i++) {
uint256 amount = _claim(_tokenIds[i], ve, _lastTokenTime);
if (amount != 0) {
IVotingEscrow(ve).depositFor(_tokenIds[i], amount);
tokenLastBalance -= amount;
}
}
return true;
}
}
┌──────────────────────────────────────────────────────────────┐
│ WEEKLY DISTRIBUTION │
├──────────────────────────────────────────────────────────────┤
│ │
│ Total Minted = Emission + Growth + TeamEmission │
│ │
│ Where: │
│ • Emission = weekly (or tailRate × totalSupply) │
│ • Growth = Rebase for veNFT holders │
│ • TeamEmission = teamRate × (Growth + Weekly) / (1 - rate) │
│ │
│ Distribution: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Team ◄──────────── teamEmissions │ │
│ │ │ │
│ │ RewardsDistributor ◄──── growth (rebases) │ │
│ │ │ │
│ │ Voter ◄──────────── emission (LP rewards) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
/// @notice Set team address
function setTeam(address _team) external {
if (msg.sender != team) revert NotTeam();
if (_team == address(0)) revert ZeroAddress();
pendingTeam = _team;
}
function acceptTeam() external {
if (msg.sender != pendingTeam) revert NotPendingTeam();
team = pendingTeam;
delete pendingTeam;
emit AcceptTeam(team);
}
/// @notice Set team emission rate (max 5%)
function setTeamRate(uint256 _rate) external {
if (msg.sender != team) revert NotTeam();
if (_rate > MAXIMUM_TEAM_RATE) revert RateTooHigh();
teamRate = _rate;
}
// Minter events
event Mint(address indexed sender, uint256 weekly, uint256 circulating, bool isTail);
event Nudge(uint256 indexed period, uint256 oldRate, uint256 newRate);
event AcceptTeam(address indexed team);
event DistributeLiquid(address indexed to, uint256 amount);
event DistributeLocked(address indexed to, uint256 amount, uint256 tokenId);
// RewardsDistributor events
event CheckpointToken(uint256 time, uint256 tokens);
event Claimed(uint256 indexed tokenId, uint256 amount, uint256 claimEpoch, uint256 maxEpoch);
contracts/Minter.sol - Emission controllercontracts/RewardsDistributor.sol - Rebase distributioncontracts/EpochGovernor.sol - Tail rate governancecontracts/interfaces/IMinter.sol - Minter interfaceWeekly Installs
–
Repository
First Seen
–
Security Audits
Solana开发技能指南:dApp开发、钱包连接、交易构建、链上程序与安全审计
3,700 周安装