Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More ERC20 token Incentive in gauge #10

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions contracts/Voter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,26 @@
pragma solidity ^0.8.0;

import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IVotingRewardsFactory} from "./interfaces/factories/IVotingRewardsFactory.sol";
import {IIncentivesFactory} from "./interfaces/factories/IIncentivesFactory.sol";
import {IGauge} from "./interfaces/IGauge.sol";
import {IGaugeFactory} from "./interfaces/factories/IGaugeFactory.sol";
import {IVault} from "./interfaces/IVault.sol";
import {IVoter} from "./interfaces/IVoter.sol";
import {IReward} from "./interfaces/IReward.sol";
import {IFactoryRegistry} from "./interfaces/factories/IFactoryRegistry.sol";
import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {ProtocolTimeLibrary} from "./libraries/ProtocolTimeLibrary.sol";
import {IStrategyManager} from "./interfaces/IStrategyManager.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/// @title Protocol Voter
/// @author velodrome.finance, @figs999, @pegahcarter
/// @notice Manage votes, emission distribution, and gauge creation within the Protocol's ecosystem.
/// Also provides support for depositing and withdrawing from managed veNFTs.
contract Voter is IVoter, ERC2771Context, ReentrancyGuard {
using SafeERC20 for IERC20;
/// @inheritdoc IVoter
address public immutable forwarder;
/// @inheritdoc IVoter
Expand Down Expand Up @@ -47,6 +51,8 @@ contract Voter is IVoter, ERC2771Context, ReentrancyGuard {
/// @inheritdoc IVoter
mapping(address => address) public gauges;
/// @inheritdoc IVoter
mapping(address => address) public gaugeToIncentives;
/// @inheritdoc IVoter
mapping(address => address) public poolForGauge;
/// @inheritdoc IVoter
mapping(address => uint256) public weights;
Expand Down Expand Up @@ -90,6 +96,13 @@ contract Voter is IVoter, ERC2771Context, ReentrancyGuard {
_;
}

modifier hasValidGauge(address _pool) {
address _gauge = gauges[_pool];
if (_gauge == address(0)) revert GaugeDoesNotExist(_pool);
if (!isAlive[_gauge]) revert GaugeNotAlive(_gauge);
_;
}

function epochStart(uint256 _timestamp) external pure returns (uint256) {
return ProtocolTimeLibrary.epochStart(_timestamp);
}
Expand Down Expand Up @@ -252,15 +265,21 @@ contract Voter is IVoter, ERC2771Context, ReentrancyGuard {

/// @inheritdoc IVoter
function createGauge(address _poolFactory, address _pool) external nonReentrant returns (address) {
address sender = _msgSender();
if (gauges[_pool] != address(0)) revert GaugeExists();
(address incentiveFactory, address gaugeFactory) = IFactoryRegistry(factoryRegistry).factoriesToPoolFactory(
_poolFactory
);

address sender = _msgSender();
if (sender != governor) {
if (!isWhitelistedToken[_pool]) revert NotWhitelistedToken();
}

address gaugeFactory = IFactoryRegistry(factoryRegistry).factoriesToPoolFactory(_poolFactory);
address _gauge = IGaugeFactory(gaugeFactory).createGauge(forwarder, _pool);
address _incentiveReward = IIncentivesFactory(incentiveFactory).createRewards(forwarder, _pool);

gaugeToIncentives[_gauge] = _incentiveReward;
gauges[_pool] = _gauge;
poolForGauge[_gauge] = _pool;
isGauge[_gauge] = true;
Expand Down Expand Up @@ -360,6 +379,15 @@ contract Voter is IVoter, ERC2771Context, ReentrancyGuard {
}
}

/// @inheritdoc IVoter
function claimIncentive(address[] memory _incentives, address[][] memory _tokens) external {
address _sender = msg.sender;
uint256 _length = _incentives.length;
for (uint256 i = 0; i < _length; i++) {
IReward(_incentives[i]).getReward(_sender, _tokens[i]);
}
}
ludete marked this conversation as resolved.
Show resolved Hide resolved

function _distribute(address _gauge) internal {
_updateFor(_gauge); // should set claimable to 0 if killed
uint256 _claimable = claimable[_gauge];
Expand All @@ -386,4 +414,23 @@ contract Voter is IVoter, ERC2771Context, ReentrancyGuard {
_distribute(_gauges[x]);
}
}

function depositLP(address _pool, uint256 _amount) external hasValidGauge(_pool) nonReentrant {
if (_amount == 0) revert ZeroAmount();
address _sender = msg.sender;
address _gauge = gauges[_pool];
IERC20(_pool).safeTransferFrom(_sender, address(this), _amount);
IERC20(_pool).approve(_gauge, _amount);
IGauge(_gauge).deposit(_amount, _sender);
ludete marked this conversation as resolved.
Show resolved Hide resolved
IReward(gaugeToIncentives[_gauge])._deposit(_amount, _sender);
}

function withdrawLP(address _pool, uint256 _amount) external nonReentrant {
if (_amount == 0) revert ZeroAmount();
address _gauge = gauges[_pool];
if (_gauge == address(0)) revert GaugeDoesNotExist(_pool);
address _sender = msg.sender;
IGauge(_gauge).withdraw(_sender, _amount);
IReward(gaugeToIncentives[_gauge])._withdraw(_amount, _sender);
}
}
27 changes: 16 additions & 11 deletions contracts/factories/FactoryRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,51 +22,56 @@ contract FactoryRegistry is IFactoryRegistry, Ownable {
EnumerableSet.AddressSet private _poolFactories;

struct FactoriesToPoolFactory {
address incentiveFactory;
address gaugeFactory;
}
/// @dev the factories linked to the poolFactory
mapping(address => FactoriesToPoolFactory) private _factoriesToPoolsFactory;

constructor(address _fallbackPoolFactory, address _fallbackGaugeFactory) {
constructor(address _fallbackPoolFactory, address _fallbackIncentiveFactory, address _fallbackGaugeFactory) {
fallbackPoolFactory = _fallbackPoolFactory;

approve(_fallbackPoolFactory, _fallbackGaugeFactory);
approve(_fallbackPoolFactory, _fallbackIncentiveFactory, _fallbackGaugeFactory);
}

/// @inheritdoc IFactoryRegistry
function approve(address poolFactory, address gaugeFactory) public onlyOwner {
if (poolFactory == address(0) || gaugeFactory == address(0)) revert ZeroAddress();
function approve(address poolFactory, address incentiveFactory, address gaugeFactory) public onlyOwner {
if (poolFactory == address(0) || incentiveFactory == address(0) || gaugeFactory == address(0)) revert ZeroAddress();
if (_poolFactories.contains(poolFactory)) revert PathAlreadyApproved();

FactoriesToPoolFactory memory usedFactories = _factoriesToPoolsFactory[poolFactory];

// If the poolFactory *has not* been approved before, can approve any gauge/votingRewards factory
// Only one check is sufficient
if (usedFactories.gaugeFactory == address(0)) {
_factoriesToPoolsFactory[poolFactory] = FactoriesToPoolFactory(gaugeFactory);
if (usedFactories.incentiveFactory == address(0)) {
_factoriesToPoolsFactory[poolFactory] = FactoriesToPoolFactory(incentiveFactory, gaugeFactory);
} else {
// If the poolFactory *has* been approved before, can only approve the same used gauge/votingRewards factory to
// to maintain state within Voter
if (gaugeFactory != usedFactories.gaugeFactory) revert InvalidFactoriesToPoolFactory();
if (incentiveFactory != usedFactories.incentiveFactory || gaugeFactory != usedFactories.gaugeFactory)
revert InvalidFactoriesToPoolFactory();
}

_poolFactories.add(poolFactory);
emit Approve(poolFactory, gaugeFactory);
emit Approve(poolFactory, incentiveFactory, gaugeFactory);
}

/// @inheritdoc IFactoryRegistry
function unapprove(address poolFactory) external onlyOwner {
if (poolFactory == fallbackPoolFactory) revert FallbackFactory();
if (!_poolFactories.contains(poolFactory)) revert PathNotApproved();
_poolFactories.remove(poolFactory);
address gaugeFactory = factoriesToPoolFactory(poolFactory);
emit Unapprove(poolFactory, gaugeFactory);
(address incentiveFactory, address gaugeFactory) = factoriesToPoolFactory(poolFactory);
emit Unapprove(poolFactory, incentiveFactory, gaugeFactory);
}

/// @inheritdoc IFactoryRegistry
function factoriesToPoolFactory(address poolFactory) public view returns (address gaugeFactory) {
function factoriesToPoolFactory(
address poolFactory
) public view returns (address incentiveFactory, address gaugeFactory) {
FactoriesToPoolFactory memory f = _factoriesToPoolsFactory[poolFactory];
gaugeFactory = f.gaugeFactory;
incentiveFactory = f.incentiveFactory;
}

/// @inheritdoc IFactoryRegistry
Expand Down
14 changes: 14 additions & 0 deletions contracts/factories/IncentivesFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IIncentivesFactory} from "../interfaces/factories/IIncentivesFactory.sol";
import {Incentive} from "../rewards/Incentive.sol";

contract IncentivesFactory is IIncentivesFactory {
/// @inheritdoc IIncentivesFactory
function createRewards(address _forwarder, address _pool) external returns (address incentiveReward) {
address[] memory rewards = new address[](1);
rewards[0] = _pool;
incentiveReward = address(new Incentive(_forwarder, msg.sender, rewards));
}
}
17 changes: 13 additions & 4 deletions contracts/gauges/Gauge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,23 @@ contract Gauge is IGauge, ERC2771Context, ReentrancyGuard {
/// @inheritdoc IGauge
function withdraw(uint256 _amount) external nonReentrant {
address sender = _msgSender();
_withdraw(sender, _amount);
}

function withdraw(address receipt, uint256 _amount) external nonReentrant {
address _sender = msg.sender;
if (_sender != voter) revert NotVoter();
_withdraw(receipt, _amount);
}

_updateRewards(sender);
function _withdraw(address receipt, uint256 _amount) internal {
_updateRewards(receipt);

totalSupply -= _amount;
balanceOf[sender] -= _amount;
IERC20(stakingToken).safeTransfer(sender, _amount);
balanceOf[receipt] -= _amount;
IERC20(stakingToken).safeTransfer(receipt, _amount);

emit Withdraw(sender, _amount);
emit Withdraw(receipt, _amount);
}

function _updateRewards(address _account) internal {
Expand Down
5 changes: 5 additions & 0 deletions contracts/interfaces/IGauge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ interface IGauge {
/// @param _amount .
function withdraw(uint256 _amount) external;

/// @notice Withdraw LP tokens for user by voter
/// @param _amount .
/// @param receipt will receive amount of LP
function withdraw(address receipt, uint256 _amount) external;

/// @dev Notifies gauge of gauge rewards. Assumes gauge reward tokens is 18 decimals.
/// If not 18 decimals, rewardRate may have rounding issues.
function notifyRewardAmount() external payable;
Expand Down
114 changes: 114 additions & 0 deletions contracts/interfaces/IReward.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IReward {
error InvalidReward();
error NotAuthorized();
error NotGauge();
error NotEscrowToken();
error NotSingleToken();
error NotVotingEscrow();
error NotWhitelisted();
error ZeroAmount();

event Deposit(address indexed from, address indexed receiver, uint256 amount);
event Withdraw(address indexed from, address indexed receiver, uint256 amount);
event NotifyReward(address indexed from, address indexed reward, uint256 indexed epoch, uint256 amount);
event ClaimRewards(address indexed from, address indexed reward, uint256 amount);

/// @notice A checkpoint for marking balance
struct Checkpoint {
uint256 timestamp;
uint256 balanceOf;
}

/// @notice A checkpoint for marking supply
struct SupplyCheckpoint {
uint256 timestamp;
uint256 supply;
}

/// @notice Epoch duration constant (7 days)
function DURATION() external view returns (uint256);

/// @notice Address of Voter.sol
function voter() external view returns (address);

/// @dev Address which has permission to externally call _deposit() & _withdraw()
function authorized() external view returns (address);

/// @notice Total amount currently deposited via _deposit()
function totalSupply() external view returns (uint256);

/// @notice Current amount deposited by tokenId
function balanceOf(address _user) external view returns (uint256);

/// @notice Amount of tokens to reward depositors for a given epoch
/// @param token Address of token to reward
/// @param epochStart Startime of rewards epoch
/// @return Amount of token
function tokenRewardsPerEpoch(address token, uint256 epochStart) external view returns (uint256);

/// @notice Most recent timestamp a veNFT has claimed their rewards
/// @param token Address of token rewarded
/// @param _user will receive incentive
/// @return Timestamp
function lastEarn(address token, address _user) external view returns (uint256);

/// @notice True if a token is or has been an active reward token, else false
function isReward(address token) external view returns (bool);

/// @notice The number of checkpoints for each user deposited
function numCheckpoints(address user) external view returns (uint256);

/// @notice The total number of checkpoints
function supplyNumCheckpoints() external view returns (uint256);

/// @notice Deposit an amount into the rewards contract to earn future rewards associated to a veNFT
ludete marked this conversation as resolved.
Show resolved Hide resolved
/// @dev Internal notation used as only callable internally by `authorized`.
/// @param amount Amount deposited for the veNFT
/// @param user Which deposit LP
function _deposit(uint256 amount, address user) external;
ludete marked this conversation as resolved.
Show resolved Hide resolved

/// @notice Withdraw an amount from the rewards contract associated to a veNFT
/// @dev Internal notation used as only callable internally by `authorized`.
/// @param amount Amount deposited for the veNFT
/// @param receiver Will withdraw LP from gauge
function _withdraw(uint256 amount, address receiver) external;

/// @notice Claim the rewards earned by Lp
/// @param tokens Array of tokens to claim rewards of
function getReward(address[] memory tokens) external;
ludete marked this conversation as resolved.
Show resolved Hide resolved

/// @notice Claim the rewards earned by Lp
/// @param user which get rewards from incentive
/// @param tokens Array of tokens to claim rewards of
function getReward(address user, address[] memory tokens) external;

/// @notice Add rewards for stakers to earn
/// @param token Address of token to reward
/// @param amount Amount of token to transfer to rewards
function notifyRewardAmount(address token, uint256 amount) external;

/// @notice Determine the prior balance for an account as of a block number
/// @dev Block number must be a finalized block or else this function will revert to prevent misinformation.
/// @param receiver the account will calculate reward.
/// @param timestamp The timestamp to get the balance at
/// @return The balance the account had as of the given block
function getPriorBalanceIndex(address receiver, uint256 timestamp) external view returns (uint256);

/// @notice Determine the prior index of supply staked by of a timestamp
/// @dev Timestamp must be <= current timestamp
/// @param timestamp The timestamp to get the index at
/// @return Index of supply checkpoint
function getPriorSupplyIndex(uint256 timestamp) external view returns (uint256);

/// @notice Get number of rewards tokens
function rewardsListLength() external view returns (uint256);
ludete marked this conversation as resolved.
Show resolved Hide resolved

/// @notice Calculate how much in rewards are earned for a specific token and veNFT
/// @param token Address of token to fetch rewards of
/// @param _user will receive reward
/// @return Amount of token earned in rewards
function earned(address token, address _user) external view returns (uint256);
}
19 changes: 19 additions & 0 deletions contracts/interfaces/IVoter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface IVoter {
error ZeroBalance();
error ZeroAddress();
error EpochVoteEnd();
error ZeroAmount();

event GaugeCreated(
address indexed poolFactory,
Expand Down Expand Up @@ -94,6 +95,9 @@ interface IVoter {
/// @dev Token => Whitelisted status
function isWhitelistedToken(address token) external view returns (bool);

/// @dev gauge ==> incentive
function gaugeToIncentives(address gauge) external view returns (address);

/// @dev Gauge => Liveness status
function isAlive(address gauge) external view returns (bool);

Expand All @@ -117,6 +121,16 @@ interface IVoter {
/// @param _gauges Array of gauges to distribute to.
function distribute(address[] memory _gauges) external;

/// @dev Deposit gauge of pool by users.
/// @param _pool lp of the deposit
/// @param _amount of the lp
function depositLP(address _pool, uint256 _amount) external;

/// @dev Deposit gauge of pool by users.
/// @param _pool lp of the deposit
/// @param _amount of the lp
function withdrawLP(address _pool, uint256 _amount) external;

/// @notice Called by users to update voting balances in voting rewards contracts.
function poke() external;

Expand All @@ -141,6 +155,11 @@ interface IVoter {
/// @param _gauges Array of gauges to collect emissions from.
function claimRewards(address[] memory _gauges) external;

/// @notice Claim incentives with another reward for LP in gauge
/// @param _incentives which store reward
/// @param _tokens Array of reward token for LP in gauge
function claimIncentive(address[] memory _incentives, address[][] memory _tokens) external;

/// @notice Set new governor.
/// @dev Throws if not called by governor.
/// @param _governor .
Expand Down
Loading
Loading