npx skills add https://github.com/cyotee/aerodrome-skill --skill 'Aerodrome Gauge'流动性计量器向质押其 LP 代币的流动性提供者分发 AERO 排放奖励。流动性提供者放弃其交易手续费收益,以换取按比例分配的 AERO 排放奖励。
┌──────────────────────────────────────────────────────────────┐
│ 计量器流程 │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ LP │ 质押 LP 代币 │ │ │
│ │ 提供者 │─────────────────────►│ 计量器 │ │
│ └─────────────┘ └──────┬──────┘ │
│ ▲ │ │
│ │ ▼ │
│ │ 领取 AERO ┌─────────────────┐ │
│ └────────────────────────────│ AERO 奖励 │ │
│ └─────────────────┘ │
│ ▲ │
│ │ │
│ ┌─────────────────┐ │
│ │ 投票者 │ │
│ │ (排放分配) │ │
│ └─────────────────┘ │
│ │
│ 交易手续费 ──────────────────────► 手续费投票奖励 │
│ (给 veNFT 投票者) │
│ │
└──────────────────────────────────────────────────────────────┘
contract Gauge is IGauge, ERC2771Context, ReentrancyGuard {
address public immutable stakingToken; // LP 代币
address public immutable rewardToken; // AERO
address public immutable feesVotingReward;// 手续费奖励合约
address public immutable voter;
address public immutable ve;
bool public immutable isPool; // 是否为资金池?
uint256 internal constant DURATION = 7 days;
uint256 internal constant PRECISION = 10 ** 18;
uint256 public periodFinish;
uint256 public rewardRate;
uint256 public lastUpdateTime;
uint256 public rewardPerTokenStored;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;
mapping(uint256 => uint256) public rewardRateByEpoch;
uint256 public fees0; // 累计的 token0 手续费
uint256 public fees1; // 累计的 token1 手续费
}
/// @notice 质押 LP 代币
function deposit(uint256 _amount) external {
_depositFor(_amount, _msgSender());
}
/// @notice 为另一个地址质押 LP 代币
function deposit(uint256 _amount, address _recipient) external {
_depositFor(_amount, _recipient);
}
function _depositFor(uint256 _amount, address _recipient) internal nonReentrant {
if (_amount == 0) revert ZeroAmount();
if (!IVoter(voter).isAlive(address(this))) revert NotAlive();
address sender = _msgSender();
_updateRewards(_recipient);
IERC20(stakingToken).safeTransferFrom(sender, address(this), _amount);
totalSupply += _amount;
balanceOf[_recipient] += _amount;
emit Deposit(sender, _recipient, _amount);
}
/// @notice 提取已质押的 LP 代币
function withdraw(uint256 _amount) external nonReentrant {
address sender = _msgSender();
_updateRewards(sender);
totalSupply -= _amount;
balanceOf[sender] -= _amount;
IERC20(stakingToken).safeTransfer(sender, _amount);
emit Withdraw(sender, _amount);
}
/// @notice 获取存储的每代币奖励
function rewardPerToken() public view returns (uint256) {
if (totalSupply == 0) {
return rewardPerTokenStored;
}
return rewardPerTokenStored +
((lastTimeRewardApplicable() - lastUpdateTime) * rewardRate * PRECISION) / totalSupply;
}
/// @notice 获取奖励适用的最后时间
function lastTimeRewardApplicable() public view returns (uint256) {
return Math.min(block.timestamp, periodFinish);
}
/// @notice 计算已获得但未领取的奖励
function earned(address _account) public view returns (uint256) {
return (balanceOf[_account] * (rewardPerToken() - userRewardPerTokenPaid[_account])) / PRECISION
+ rewards[_account];
}
/// @notice 领取 AERO 奖励
function getReward(address _account) external nonReentrant {
address sender = _msgSender();
if (sender != _account && sender != voter) revert NotAuthorized();
_updateRewards(_account);
uint256 reward = rewards[_account];
if (reward > 0) {
rewards[_account] = 0;
IERC20(rewardToken).safeTransfer(_account, reward);
emit ClaimRewards(_account, reward);
}
}
function _updateRewards(address _account) internal {
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
rewards[_account] = earned(_account);
userRewardPerTokenPaid[_account] = rewardPerTokenStored;
}
/// @notice 接收来自投票者的排放奖励
function notifyRewardAmount(uint256 _amount) external nonReentrant {
address sender = _msgSender();
if (sender != voter) revert NotVoter();
if (_amount == 0) revert ZeroAmount();
_claimFees(); // 首先领取交易手续费
_notifyRewardAmount(sender, _amount);
}
/// @notice 接收奖励但不领取手续费(仅限团队)
function notifyRewardWithoutClaim(uint256 _amount) external nonReentrant {
address sender = _msgSender();
if (sender != IVotingEscrow(ve).team()) revert NotTeam();
if (_amount == 0) revert ZeroAmount();
_notifyRewardAmount(sender, _amount);
}
function _notifyRewardAmount(address sender, uint256 _amount) internal {
rewardPerTokenStored = rewardPerToken();
uint256 timestamp = block.timestamp;
uint256 timeUntilNext = ProtocolTimeLibrary.epochNext(timestamp) - timestamp;
if (timestamp >= periodFinish) {
// 新周期
IERC20(rewardToken).safeTransferFrom(sender, address(this), _amount);
rewardRate = _amount / timeUntilNext;
} else {
// 添加到现有周期
uint256 _remaining = periodFinish - timestamp;
uint256 _leftover = _remaining * rewardRate;
IERC20(rewardToken).safeTransferFrom(sender, address(this), _amount);
rewardRate = (_amount + _leftover) / timeUntilNext;
}
rewardRateByEpoch[ProtocolTimeLibrary.epochStart(timestamp)] = rewardRate;
if (rewardRate == 0) revert ZeroRewardRate();
// 完整性检查
uint256 balance = IERC20(rewardToken).balanceOf(address(this));
if (rewardRate > balance / timeUntilNext) revert RewardRateTooHigh();
lastUpdateTime = timestamp;
periodFinish = timestamp + timeUntilNext;
emit NotifyReward(sender, _amount);
}
当 LP 代币被质押到计量器中时,交易手续费将分配给 veNFT 投票者,而不是流动性提供者:
function _claimFees() internal returns (uint256 claimed0, uint256 claimed1) {
if (!isPool) {
return (0, 0);
}
// 从资金池领取手续费
(claimed0, claimed1) = IPool(stakingToken).claimFees();
if (claimed0 > 0 || claimed1 > 0) {
uint256 _fees0 = fees0 + claimed0;
uint256 _fees1 = fees1 + claimed1;
(address _token0, address _token1) = IPool(stakingToken).tokens();
// 如果超过阈值,则分配给手续费投票奖励合约
if (_fees0 > DURATION) {
fees0 = 0;
IERC20(_token0).safeApprove(feesVotingReward, _fees0);
IReward(feesVotingReward).notifyRewardAmount(_token0, _fees0);
} else {
fees0 = _fees0;
}
if (_fees1 > DURATION) {
fees1 = 0;
IERC20(_token1).safeApprove(feesVotingReward, _fees1);
IReward(feesVotingReward).notifyRewardAmount(_token1, _fees1);
} else {
fees1 = _fees1;
}
emit ClaimFees(_msgSender(), claimed0, claimed1);
}
}
/// @notice 获取当前周期剩余奖励
function left() external view returns (uint256) {
if (block.timestamp >= periodFinish) return 0;
uint256 _remaining = periodFinish - block.timestamp;
return _remaining * rewardRate;
}
┌──────────────────────────────────────────────────────────────┐
│ 排放分配流程 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 1. Minter.updatePeriod() │
│ └──► 铸造每周的 AERO │
│ └──► 发送到 Voter.notifyRewardAmount() │
│ │
│ 2. Voter 更新索引 │
│ └──► index += (amount * 1e18) / totalWeight │
│ │
│ 3. Voter.distribute(gauges) │
│ └──► 对于每个计量器: │
│ └──► _updateFor(gauge) │
│ └──► claimable[gauge] += 基于权重的份额 │
│ └──► gauge.notifyRewardAmount(claimable) │
│ │
│ 4. Gauge 在周期内分配 │
│ └──► rewardRate = amount / timeUntilNextEpoch │
│ └──► 流动性提供者根据其质押比例获得奖励 │
│ │
│ 5. 流动性提供者通过 getReward() 领取 │
│ └──► reward = balanceOf * (rewardPerToken - paid) / 1e18 │
│ │
└──────────────────────────────────────────────────────────────┘
event Deposit(address indexed from, address indexed to, uint256 amount);
event Withdraw(address indexed from, uint256 amount);
event NotifyReward(address indexed from, uint256 amount);
event ClaimFees(address indexed from, uint256 claimed0, uint256 claimed1);
event ClaimRewards(address indexed from, uint256 amount);
// 1. 从资金池获取 LP 代币
router.addLiquidity(tokenA, tokenB, stable, amountA, amountB, minA, minB, user, deadline);
// 2. 授权给计量器
IERC20(pool).approve(gauge, lpAmount);
// 3. 在计量器中质押
IGauge(gauge).deposit(lpAmount);
// 4. 等待排放奖励...
// 5. 检查已获奖励
uint256 pending = IGauge(gauge).earned(user);
// 6. 领取奖励
IGauge(gauge).getReward(user);
// 7. 提取 LP 代币
IGauge(gauge).withdraw(lpAmount);
contracts/gauges/Gauge.sol - 计量器实现contracts/interfaces/IGauge.sol - 计量器接口contracts/Voter.sol - 排放分配每周安装次数
–
代码仓库
首次出现
–
安全审计
Gauges distribute AERO emissions to liquidity providers who stake their LP tokens. LPs forgo their trading fee claims in exchange for proportional AERO emissions.
┌──────────────────────────────────────────────────────────────┐
│ GAUGE FLOW │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ LP │ Stake LP Tokens │ │ │
│ │ Provider │─────────────────────►│ Gauge │ │
│ └─────────────┘ └──────┬──────┘ │
│ ▲ │ │
│ │ ▼ │
│ │ Claim AERO ┌─────────────────┐ │
│ └────────────────────────────│ AERO Rewards │ │
│ └─────────────────┘ │
│ ▲ │
│ │ │
│ ┌─────────────────┐ │
│ │ Voter │ │
│ │ (Emissions) │ │
│ └─────────────────┘ │
│ │
│ Trading Fees ──────────────────────► FeesVotingReward │
│ (For veNFT voters) │
│ │
└──────────────────────────────────────────────────────────────┘
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
contract Gauge is IGauge, ERC2771Context, ReentrancyGuard {
address public immutable stakingToken; // LP token
address public immutable rewardToken; // AERO
address public immutable feesVotingReward;// Fee reward contract
address public immutable voter;
address public immutable ve;
bool public immutable isPool; // Is this for a pool?
uint256 internal constant DURATION = 7 days;
uint256 internal constant PRECISION = 10 ** 18;
uint256 public periodFinish;
uint256 public rewardRate;
uint256 public lastUpdateTime;
uint256 public rewardPerTokenStored;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;
mapping(uint256 => uint256) public rewardRateByEpoch;
uint256 public fees0; // Accumulated token0 fees
uint256 public fees1; // Accumulated token1 fees
}
/// @notice Stake LP tokens
function deposit(uint256 _amount) external {
_depositFor(_amount, _msgSender());
}
/// @notice Stake LP tokens for another address
function deposit(uint256 _amount, address _recipient) external {
_depositFor(_amount, _recipient);
}
function _depositFor(uint256 _amount, address _recipient) internal nonReentrant {
if (_amount == 0) revert ZeroAmount();
if (!IVoter(voter).isAlive(address(this))) revert NotAlive();
address sender = _msgSender();
_updateRewards(_recipient);
IERC20(stakingToken).safeTransferFrom(sender, address(this), _amount);
totalSupply += _amount;
balanceOf[_recipient] += _amount;
emit Deposit(sender, _recipient, _amount);
}
/// @notice Withdraw staked LP tokens
function withdraw(uint256 _amount) external nonReentrant {
address sender = _msgSender();
_updateRewards(sender);
totalSupply -= _amount;
balanceOf[sender] -= _amount;
IERC20(stakingToken).safeTransfer(sender, _amount);
emit Withdraw(sender, _amount);
}
/// @notice Get reward per token stored
function rewardPerToken() public view returns (uint256) {
if (totalSupply == 0) {
return rewardPerTokenStored;
}
return rewardPerTokenStored +
((lastTimeRewardApplicable() - lastUpdateTime) * rewardRate * PRECISION) / totalSupply;
}
/// @notice Get last time rewards are applicable
function lastTimeRewardApplicable() public view returns (uint256) {
return Math.min(block.timestamp, periodFinish);
}
/// @notice Calculate earned but unclaimed rewards
function earned(address _account) public view returns (uint256) {
return (balanceOf[_account] * (rewardPerToken() - userRewardPerTokenPaid[_account])) / PRECISION
+ rewards[_account];
}
/// @notice Claim AERO rewards
function getReward(address _account) external nonReentrant {
address sender = _msgSender();
if (sender != _account && sender != voter) revert NotAuthorized();
_updateRewards(_account);
uint256 reward = rewards[_account];
if (reward > 0) {
rewards[_account] = 0;
IERC20(rewardToken).safeTransfer(_account, reward);
emit ClaimRewards(_account, reward);
}
}
function _updateRewards(address _account) internal {
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
rewards[_account] = earned(_account);
userRewardPerTokenPaid[_account] = rewardPerTokenStored;
}
/// @notice Receive emissions from Voter
function notifyRewardAmount(uint256 _amount) external nonReentrant {
address sender = _msgSender();
if (sender != voter) revert NotVoter();
if (_amount == 0) revert ZeroAmount();
_claimFees(); // Claim trading fees first
_notifyRewardAmount(sender, _amount);
}
/// @notice Receive rewards without claiming fees (team only)
function notifyRewardWithoutClaim(uint256 _amount) external nonReentrant {
address sender = _msgSender();
if (sender != IVotingEscrow(ve).team()) revert NotTeam();
if (_amount == 0) revert ZeroAmount();
_notifyRewardAmount(sender, _amount);
}
function _notifyRewardAmount(address sender, uint256 _amount) internal {
rewardPerTokenStored = rewardPerToken();
uint256 timestamp = block.timestamp;
uint256 timeUntilNext = ProtocolTimeLibrary.epochNext(timestamp) - timestamp;
if (timestamp >= periodFinish) {
// New period
IERC20(rewardToken).safeTransferFrom(sender, address(this), _amount);
rewardRate = _amount / timeUntilNext;
} else {
// Add to existing period
uint256 _remaining = periodFinish - timestamp;
uint256 _leftover = _remaining * rewardRate;
IERC20(rewardToken).safeTransferFrom(sender, address(this), _amount);
rewardRate = (_amount + _leftover) / timeUntilNext;
}
rewardRateByEpoch[ProtocolTimeLibrary.epochStart(timestamp)] = rewardRate;
if (rewardRate == 0) revert ZeroRewardRate();
// Sanity check
uint256 balance = IERC20(rewardToken).balanceOf(address(this));
if (rewardRate > balance / timeUntilNext) revert RewardRateTooHigh();
lastUpdateTime = timestamp;
periodFinish = timestamp + timeUntilNext;
emit NotifyReward(sender, _amount);
}
When LP tokens are staked in a gauge, trading fees go to veNFT voters instead of LPs:
function _claimFees() internal returns (uint256 claimed0, uint256 claimed1) {
if (!isPool) {
return (0, 0);
}
// Claim fees from the pool
(claimed0, claimed1) = IPool(stakingToken).claimFees();
if (claimed0 > 0 || claimed1 > 0) {
uint256 _fees0 = fees0 + claimed0;
uint256 _fees1 = fees1 + claimed1;
(address _token0, address _token1) = IPool(stakingToken).tokens();
// Distribute to FeesVotingReward if above threshold
if (_fees0 > DURATION) {
fees0 = 0;
IERC20(_token0).safeApprove(feesVotingReward, _fees0);
IReward(feesVotingReward).notifyRewardAmount(_token0, _fees0);
} else {
fees0 = _fees0;
}
if (_fees1 > DURATION) {
fees1 = 0;
IERC20(_token1).safeApprove(feesVotingReward, _fees1);
IReward(feesVotingReward).notifyRewardAmount(_token1, _fees1);
} else {
fees1 = _fees1;
}
emit ClaimFees(_msgSender(), claimed0, claimed1);
}
}
/// @notice Get remaining rewards in current period
function left() external view returns (uint256) {
if (block.timestamp >= periodFinish) return 0;
uint256 _remaining = periodFinish - block.timestamp;
return _remaining * rewardRate;
}
┌──────────────────────────────────────────────────────────────┐
│ EMISSION DISTRIBUTION │
├──────────────────────────────────────────────────────────────┤
│ │
│ 1. Minter.updatePeriod() │
│ └──► Mints weekly AERO │
│ └──► Sends to Voter.notifyRewardAmount() │
│ │
│ 2. Voter updates index │
│ └──► index += (amount * 1e18) / totalWeight │
│ │
│ 3. Voter.distribute(gauges) │
│ └──► For each gauge: │
│ └──► _updateFor(gauge) │
│ └──► claimable[gauge] += share based on weight │
│ └──► gauge.notifyRewardAmount(claimable) │
│ │
│ 4. Gauge distributes over epoch │
│ └──► rewardRate = amount / timeUntilNextEpoch │
│ └──► LPs earn proportionally to their stake │
│ │
│ 5. LPs claim via getReward() │
│ └──► reward = balanceOf * (rewardPerToken - paid) / 1e18 │
│ │
└──────────────────────────────────────────────────────────────┘
event Deposit(address indexed from, address indexed to, uint256 amount);
event Withdraw(address indexed from, uint256 amount);
event NotifyReward(address indexed from, uint256 amount);
event ClaimFees(address indexed from, uint256 claimed0, uint256 claimed1);
event ClaimRewards(address indexed from, uint256 amount);
// 1. Get LP tokens from pool
router.addLiquidity(tokenA, tokenB, stable, amountA, amountB, minA, minB, user, deadline);
// 2. Approve gauge
IERC20(pool).approve(gauge, lpAmount);
// 3. Stake in gauge
IGauge(gauge).deposit(lpAmount);
// 4. Wait for emissions...
// 5. Check earned rewards
uint256 pending = IGauge(gauge).earned(user);
// 6. Claim rewards
IGauge(gauge).getReward(user);
// 7. Withdraw LP tokens
IGauge(gauge).withdraw(lpAmount);
contracts/gauges/Gauge.sol - Gauge implementationcontracts/interfaces/IGauge.sol - Gauge interfacecontracts/Voter.sol - Emission distributionWeekly Installs
–
Repository
First Seen
–
Security Audits
Solana开发技能指南:dApp开发、钱包连接、交易构建、链上程序与安全审计
3,700 周安装