Skip to content

Commit 699697d

Browse files
committed
add guage contracts
1 parent b2b2cdc commit 699697d

File tree

22 files changed

+1536
-222
lines changed

22 files changed

+1536
-222
lines changed

contracts/Voter.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ contract Voter is IVoter, ERC2771Context, ReentrancyGuard {
276276

277277
address _bribeVotingReward = IVotingRewardsFactory(votingRewardsFactory).createRewards(forwarder, _pool);
278278

279-
address _gauge = IGaugeFactory(gaugeFactory).createGauge(forwarder, _pool, ve);
279+
address _gauge = IGaugeFactory(gaugeFactory).createGauge(forwarder, _pool);
280280

281281
gaugeToBribe[_gauge] = _bribeVotingReward;
282282
gauges[_pool] = _gauge;

contracts/factories/GaugeFactory.sol

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import {IGaugeFactory} from "../interfaces/factories/IGaugeFactory.sol";
5+
import {Gauge} from "../gauges/Gauge.sol";
6+
7+
contract GaugeFactory is IGaugeFactory {
8+
function createGauge(address _forwarder, address _pool) external returns (address gauge) {
9+
gauge = address(new Gauge(_forwarder, _pool, msg.sender));
10+
}
11+
}

contracts/gauges/Gauge.sol

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
5+
import {IReward} from "../interfaces/IReward.sol";
6+
import {IGauge} from "../interfaces/IGauge.sol";
7+
import {IVoter} from "../interfaces/IVoter.sol";
8+
import {IVotingEscrow} from "../interfaces/IVotingEscrow.sol";
9+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
10+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
11+
import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
12+
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
13+
import {ProtocolTimeLibrary} from "../libraries/ProtocolTimeLibrary.sol";
14+
15+
/// @title Protocol Gauge
16+
/// @author veldorome.finance, @figs999, @pegahcarter
17+
/// @notice Gauge contract for distribution of emissions by address
18+
contract Gauge is IGauge, ERC2771Context, ReentrancyGuard {
19+
using SafeERC20 for IERC20;
20+
/// @inheritdoc IGauge
21+
address public immutable stakingToken;
22+
/// @inheritdoc IGauge
23+
address public immutable voter;
24+
/// @inheritdoc IGauge
25+
address public immutable ve;
26+
27+
uint256 internal constant DURATION = 7 days; // rewards are released over 7 days
28+
uint256 internal constant PRECISION = 10 ** 18;
29+
30+
/// @inheritdoc IGauge
31+
uint256 public periodFinish;
32+
/// @inheritdoc IGauge
33+
uint256 public rewardRate;
34+
/// @inheritdoc IGauge
35+
uint256 public lastUpdateTime;
36+
/// @inheritdoc IGauge
37+
uint256 public rewardPerTokenStored;
38+
/// @inheritdoc IGauge
39+
uint256 public totalSupply;
40+
/// @inheritdoc IGauge
41+
mapping(address => uint256) public balanceOf;
42+
/// @inheritdoc IGauge
43+
mapping(address => uint256) public userRewardPerTokenPaid;
44+
/// @inheritdoc IGauge
45+
mapping(address => uint256) public rewards;
46+
/// @inheritdoc IGauge
47+
mapping(uint256 => uint256) public rewardRateByEpoch;
48+
49+
constructor(address _forwarder, address _stakingToken, address _voter) ERC2771Context(_forwarder) {
50+
stakingToken = _stakingToken;
51+
voter = _voter;
52+
ve = IVoter(voter).ve();
53+
}
54+
55+
/// @inheritdoc IGauge
56+
function rewardPerToken() public view returns (uint256) {
57+
if (totalSupply == 0) {
58+
return rewardPerTokenStored;
59+
}
60+
return
61+
rewardPerTokenStored + ((lastTimeRewardApplicable() - lastUpdateTime) * rewardRate * PRECISION) / totalSupply;
62+
}
63+
64+
/// @inheritdoc IGauge
65+
function lastTimeRewardApplicable() public view returns (uint256) {
66+
return Math.min(block.timestamp, periodFinish);
67+
}
68+
69+
/// @inheritdoc IGauge
70+
function getReward(address _account) external nonReentrant {
71+
address sender = _msgSender();
72+
if (sender != _account && sender != voter) revert NotAuthorized();
73+
74+
_updateRewards(_account);
75+
76+
uint256 reward = rewards[_account];
77+
if (reward > 0) {
78+
rewards[_account] = 0;
79+
payable(_account).transfer(reward);
80+
emit ClaimRewards(_account, reward);
81+
}
82+
}
83+
84+
/// @inheritdoc IGauge
85+
function earned(address _account) public view returns (uint256) {
86+
return
87+
(balanceOf[_account] * (rewardPerToken() - userRewardPerTokenPaid[_account])) / PRECISION + rewards[_account];
88+
}
89+
90+
/// @inheritdoc IGauge
91+
function deposit(uint256 _amount) external {
92+
_depositFor(_amount, _msgSender());
93+
}
94+
95+
/// @inheritdoc IGauge
96+
function deposit(uint256 _amount, address _recipient) external {
97+
_depositFor(_amount, _recipient);
98+
}
99+
100+
function _depositFor(uint256 _amount, address _recipient) internal nonReentrant {
101+
if (_amount == 0) revert ZeroAmount();
102+
if (!IVoter(voter).isAlive(address(this))) revert NotAlive();
103+
104+
address sender = _msgSender();
105+
_updateRewards(_recipient);
106+
107+
IERC20(stakingToken).safeTransferFrom(sender, address(this), _amount);
108+
totalSupply += _amount;
109+
balanceOf[_recipient] += _amount;
110+
111+
emit Deposit(sender, _recipient, _amount);
112+
}
113+
114+
/// @inheritdoc IGauge
115+
function withdraw(uint256 _amount) external nonReentrant {
116+
address sender = _msgSender();
117+
118+
_updateRewards(sender);
119+
120+
totalSupply -= _amount;
121+
balanceOf[sender] -= _amount;
122+
IERC20(stakingToken).safeTransfer(sender, _amount);
123+
124+
emit Withdraw(sender, _amount);
125+
}
126+
127+
function _updateRewards(address _account) internal {
128+
rewardPerTokenStored = rewardPerToken();
129+
lastUpdateTime = lastTimeRewardApplicable();
130+
rewards[_account] = earned(_account);
131+
userRewardPerTokenPaid[_account] = rewardPerTokenStored;
132+
}
133+
134+
/// @inheritdoc IGauge
135+
function left() external view returns (uint256) {
136+
if (block.timestamp >= periodFinish) return 0;
137+
uint256 _remaining = periodFinish - block.timestamp;
138+
return _remaining * rewardRate;
139+
}
140+
141+
/// @inheritdoc IGauge
142+
function notifyRewardAmount() external payable nonReentrant {
143+
address sender = _msgSender();
144+
uint256 _amount = msg.value;
145+
if (sender != voter) revert NotVoter();
146+
if (_amount == 0) revert ZeroAmount();
147+
_notifyRewardAmount(sender, _amount);
148+
}
149+
150+
/// @inheritdoc IGauge
151+
function notifyRewardWithoutClaim() external payable nonReentrant {
152+
address sender = _msgSender();
153+
uint256 _amount = msg.value;
154+
if (sender != IVotingEscrow(ve).team()) revert NotTeam();
155+
if (_amount == 0) revert ZeroAmount();
156+
_notifyRewardAmount(sender, _amount);
157+
}
158+
159+
function _notifyRewardAmount(address sender, uint256 _amount) internal {
160+
rewardPerTokenStored = rewardPerToken();
161+
uint256 timestamp = block.timestamp;
162+
uint256 timeUntilNext = ProtocolTimeLibrary.epochNext(timestamp) - timestamp;
163+
164+
if (timestamp >= periodFinish) {
165+
rewardRate = _amount / timeUntilNext;
166+
} else {
167+
uint256 _remaining = periodFinish - timestamp;
168+
uint256 _leftover = _remaining * rewardRate;
169+
rewardRate = (_amount + _leftover) / timeUntilNext;
170+
}
171+
rewardRateByEpoch[ProtocolTimeLibrary.epochStart(timestamp)] = rewardRate;
172+
if (rewardRate == 0) revert ZeroRewardRate();
173+
174+
// Ensure the provided reward amount is not more than the balance in the contract.
175+
// This keeps the reward rate in the right range, preventing overflows due to
176+
// very high values of rewardRate in the earned and rewardsPerToken functions;
177+
// Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.
178+
uint256 balance = address(this).balance;
179+
if (rewardRate > balance / timeUntilNext) revert RewardRateTooHigh();
180+
181+
lastUpdateTime = timestamp;
182+
periodFinish = timestamp + timeUntilNext;
183+
emit NotifyReward(sender, _amount);
184+
}
185+
}

contracts/interfaces/IGauge.sol

+1-17
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,17 @@ interface IGauge {
1313
event Deposit(address indexed from, address indexed to, uint256 amount);
1414
event Withdraw(address indexed from, uint256 amount);
1515
event NotifyReward(address indexed from, uint256 amount);
16-
event ClaimFees(address indexed from, uint256 claimed0, uint256 claimed1);
1716
event ClaimRewards(address indexed from, uint256 amount);
1817

1918
/// @notice Address of the pool LP token which is deposited (staked) for rewards
2019
function stakingToken() external view returns (address);
2120

22-
/// @notice Address of the token (AERO) rewarded to stakers
23-
function rewardToken() external view returns (address);
24-
25-
/// @notice Address of the FeesVotingReward contract linked to the gauge
26-
function feesVotingReward() external view returns (address);
27-
2821
/// @notice Address of Protocol Voter
2922
function voter() external view returns (address);
3023

3124
/// @notice Address of Protocol Voting Escrow
3225
function ve() external view returns (address);
3326

34-
/// @notice Returns if gauge is linked to a legitimate Protocol pool
35-
function isPool() external view returns (bool);
36-
3727
/// @notice Timestamp end of current rewards period
3828
function periodFinish() external view returns (uint256);
3929

@@ -61,12 +51,6 @@ interface IGauge {
6151
/// @notice View to see the rewardRate given the timestamp of the start of the epoch
6252
function rewardRateByEpoch(uint256) external view returns (uint256);
6353

64-
/// @notice Cached amount of fees generated from the Pool linked to the Gauge of token0
65-
function fees0() external view returns (uint256);
66-
67-
/// @notice Cached amount of fees generated from the Pool linked to the Gauge of token1
68-
function fees1() external view returns (uint256);
69-
7054
/// @notice Get the current reward rate per unit of stakingToken deposited
7155
function rewardPerToken() external view returns (uint256 _rewardPerToken);
7256

@@ -104,5 +88,5 @@ interface IGauge {
10488
/// @dev Notifies gauge of gauge rewards without distributing its fees.
10589
/// Assumes gauge reward tokens is 18 decimals.
10690
/// If not 18 decimals, rewardRate may have rounding issues.
107-
function notifyRewardWithoutClaim(uint256 amount) external;
91+
function notifyRewardWithoutClaim() external payable;
10892
}

contracts/interfaces/factories/IGaugeFactory.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
pragma solidity ^0.8.0;
33

44
interface IGaugeFactory {
5-
function createGauge(address _forwarder, address _pool, address _ve) external returns (address);
5+
function createGauge(address _forwarder, address _pool) external returns (address);
66
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/* Autogenerated file. Do not edit manually. */
2+
/* tslint:disable */
3+
/* eslint-disable */
4+
import type {
5+
BaseContract,
6+
BytesLike,
7+
FunctionFragment,
8+
Result,
9+
Interface,
10+
AddressLike,
11+
ContractRunner,
12+
ContractMethod,
13+
Listener,
14+
} from "ethers";
15+
import type {
16+
TypedContractEvent,
17+
TypedDeferredTopicFilter,
18+
TypedEventLog,
19+
TypedListener,
20+
TypedContractMethod,
21+
} from "../../common";
22+
23+
export interface GaugeFactoryInterface extends Interface {
24+
getFunction(nameOrSignature: "createGauge"): FunctionFragment;
25+
26+
encodeFunctionData(
27+
functionFragment: "createGauge",
28+
values: [AddressLike, AddressLike]
29+
): string;
30+
31+
decodeFunctionResult(
32+
functionFragment: "createGauge",
33+
data: BytesLike
34+
): Result;
35+
}
36+
37+
export interface GaugeFactory extends BaseContract {
38+
connect(runner?: ContractRunner | null): GaugeFactory;
39+
waitForDeployment(): Promise<this>;
40+
41+
interface: GaugeFactoryInterface;
42+
43+
queryFilter<TCEvent extends TypedContractEvent>(
44+
event: TCEvent,
45+
fromBlockOrBlockhash?: string | number | undefined,
46+
toBlock?: string | number | undefined
47+
): Promise<Array<TypedEventLog<TCEvent>>>;
48+
queryFilter<TCEvent extends TypedContractEvent>(
49+
filter: TypedDeferredTopicFilter<TCEvent>,
50+
fromBlockOrBlockhash?: string | number | undefined,
51+
toBlock?: string | number | undefined
52+
): Promise<Array<TypedEventLog<TCEvent>>>;
53+
54+
on<TCEvent extends TypedContractEvent>(
55+
event: TCEvent,
56+
listener: TypedListener<TCEvent>
57+
): Promise<this>;
58+
on<TCEvent extends TypedContractEvent>(
59+
filter: TypedDeferredTopicFilter<TCEvent>,
60+
listener: TypedListener<TCEvent>
61+
): Promise<this>;
62+
63+
once<TCEvent extends TypedContractEvent>(
64+
event: TCEvent,
65+
listener: TypedListener<TCEvent>
66+
): Promise<this>;
67+
once<TCEvent extends TypedContractEvent>(
68+
filter: TypedDeferredTopicFilter<TCEvent>,
69+
listener: TypedListener<TCEvent>
70+
): Promise<this>;
71+
72+
listeners<TCEvent extends TypedContractEvent>(
73+
event: TCEvent
74+
): Promise<Array<TypedListener<TCEvent>>>;
75+
listeners(eventName?: string): Promise<Array<Listener>>;
76+
removeAllListeners<TCEvent extends TypedContractEvent>(
77+
event?: TCEvent
78+
): Promise<this>;
79+
80+
createGauge: TypedContractMethod<
81+
[_forwarder: AddressLike, _pool: AddressLike],
82+
[string],
83+
"nonpayable"
84+
>;
85+
86+
getFunction<T extends ContractMethod = ContractMethod>(
87+
key: string | FunctionFragment
88+
): T;
89+
90+
getFunction(
91+
nameOrSignature: "createGauge"
92+
): TypedContractMethod<
93+
[_forwarder: AddressLike, _pool: AddressLike],
94+
[string],
95+
"nonpayable"
96+
>;
97+
98+
filters: {};
99+
}
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/* Autogenerated file. Do not edit manually. */
2+
/* tslint:disable */
3+
/* eslint-disable */
4+
export type { GaugeFactory } from "./GaugeFactory";

0 commit comments

Comments
 (0)