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

Fixed up GaugeReward test #269

Merged
merged 5 commits into from
May 19, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
66 changes: 55 additions & 11 deletions contracts/GaugeController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
pragma solidity 0.8.6;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@pooltogether/owner-manager-contracts/contracts/Ownable.sol";

import "./interfaces/IGaugeController.sol";
import "./interfaces/IGaugeReward.sol";
import "./libraries/TwabLib.sol";
import "./libraries/ExtendedSafeCastLib.sol";

contract GaugeController is IGaugeController {
contract GaugeController is IGaugeController, Ownable {
using ExtendedSafeCastLib for uint256;

struct GaugeInfo {
Expand Down Expand Up @@ -43,11 +44,37 @@ contract GaugeController is IGaugeController {
*/
mapping(address => TwabLib.Account) internal gaugeScaleTwabs;

constructor(IERC20 _token, IGaugeReward _gaugeReward) {
/* ============ Events ============ */

/**
* @notice Event emitted when the GaugeReward contract address is set
* @param gaugeReward Address of the newly set GaugeReward contract
*/
event GaugeRewardSet(IGaugeReward gaugeReward);

/**
* @notice Event emitted when the contract is deployed
* @param token Address of the token being staked in the gauge
*/
event Deployed(IERC20 token);

/* ============ Constructor ============ */

/**
* @notice GaugeController constructor
* @param _token Address of the token being staked in the gauge
* @param _owner Address of the contract owner
*/
constructor(IERC20 _token, address _owner) Ownable(_owner) {
require(_owner != address(0), "GC/owner-not-zero-address");
require(address(_token) != address(0), "GC/token-not-zero-address");
token = _token;
gaugeReward = _gaugeReward;

emit Deployed(_token);
}

/* ============ External Functions ============ */

function deposit(address _to, uint256 _amount) public {
balances[_to] += _amount;
token.transferFrom(msg.sender, address(this), _amount);
Expand Down Expand Up @@ -92,17 +119,11 @@ contract GaugeController is IGaugeController {
}

function addGauge(address _gauge) public {
addGaugeWithScale(_gauge, 1 ether);
_addGaugeWithScale(_gauge, 1 ether);
}

function addGaugeWithScale(address _gauge, uint256 _scale) public {
TwabLib.Account storage gaugeScaleTwab = gaugeScaleTwabs[_gauge];
(TwabLib.AccountDetails memory twabDetails, , ) = TwabLib.increaseBalance(
gaugeScaleTwab,
_scale.toUint208(),
uint32(block.timestamp)
);
gaugeScaleTwab.details = twabDetails;
_addGaugeWithScale(_gauge, _scale);
}

function removeGauge(address _gauge) public {
Expand All @@ -117,6 +138,17 @@ contract GaugeController is IGaugeController {
gaugeScaleTwab.details = twabDetails;
}

/**
* @notice Set GaugeReward contract
* @param _gaugeReward Address of the GaugeReward contract
*/
function setGaugeReward(IGaugeReward _gaugeReward) external onlyOwner {
require(address(_gaugeReward) != address(0), "GC/GaugeReward-not-zero-address");
gaugeReward = _gaugeReward;

emit GaugeRewardSet(_gaugeReward);
}

function setGaugeScale(address _gauge, uint256 _scale) public {
TwabLib.Account storage gaugeScaleTwab = gaugeScaleTwabs[_gauge];
TwabLib.AccountDetails memory twabDetails = gaugeScaleTwab.details;
Expand Down Expand Up @@ -184,6 +216,18 @@ contract GaugeController is IGaugeController {
return userGaugeBalance[_user][_gauge];
}

/* ============ Internal Functions ============ */

function _addGaugeWithScale(address _gauge, uint256 _scale) internal {
TwabLib.Account storage gaugeScaleTwab = gaugeScaleTwabs[_gauge];
(TwabLib.AccountDetails memory twabDetails, , ) = TwabLib.increaseBalance(
gaugeScaleTwab,
_scale.toUint208(),
uint32(block.timestamp)
);
gaugeScaleTwab.details = twabDetails;
}

function _getAverageGaugeBetween(
address _gauge,
uint256 _startTime,
Expand Down
134 changes: 83 additions & 51 deletions contracts/GaugeReward.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,27 @@ pragma solidity 0.8.6;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Multicall.sol";

import "./interfaces/IGaugeReward.sol";
import "./interfaces/IGaugeController.sol";
import "./interfaces/IVault.sol";
import "./interfaces/IPrizePoolLiquidatorListener.sol";

/**
* @title PoolTogether V4 GaugeReward
* @author PoolTogether Inc Team
* @notice The GaugeReward contract handles the rewards for users
who staked in one or several gauges on the GaugeController contract.
*/
contract GaugeReward is IGaugeReward {
contract GaugeReward is IGaugeReward, IPrizePoolLiquidatorListener, Multicall {
using SafeERC20 for IERC20;

/* ============ Variables ============ */

/**
* @notice Tracks rewards tokens per user
* @dev user => token => rewards
* @notice Tracks user token reward balances
*/
mapping(address => mapping(IERC20 => uint256)) public userTokenRewards;
mapping(address => mapping(IERC20 => uint256)) public userTokenRewardBalances;

/**
* @notice Tracks user token gauge exchange rate
Expand Down Expand Up @@ -65,29 +65,38 @@ contract GaugeReward is IGaugeReward {
IGaugeController public gaugeController;

/// @notice Vault contract address
IVault public vault;
address public vault;

/// @notice Address of the liquidator that this contract is listening to
address public liquidator;

/// @notice Percentage of rewards that goes to stakers. Fixed point 9 number this is less than 1.
uint32 public stakerCut;

/* ============ Events ============ */

/**
* @notice Emitted when the contract is initialized
* @notice Emitted when the contract is deployed
* @param gaugeController Address of the GaugeController
* @param vault Address of the Vault
*/
event Deployed(IGaugeController indexed gaugeController, IVault indexed vault);
event Deployed(
IGaugeController indexed gaugeController,
address indexed vault,
address indexed liquidator,
uint32 stakerCut
);

/**
* @notice Emitted when rewards token are added to a gauge
* @param gauge Address of the gauge for which the rewards are added
* @param token Address of the token being added
* @param vault Address of the vault in which rewards are being sent
* @param amount Amount of tokens added to the gauge
* @param exchangeRate New exchange rate for this `token` in this `gauge`
*/
event RewardsAdded(
address indexed gauge,
IERC20 indexed token,
address indexed vault,
uint256 amount,
uint256 exchangeRate
);
Expand Down Expand Up @@ -123,14 +132,28 @@ contract GaugeReward is IGaugeReward {
* @param _gaugeController Address of the GaugeController
* @param _vault Address of the Vault
*/
constructor(IGaugeController _gaugeController, IVault _vault) {
constructor(
IGaugeController _gaugeController,
address _vault,
address _liquidator,
uint32 _stakerCut
) {
require(address(_gaugeController) != address(0), "GReward/GC-not-zero-address");
require(address(_vault) != address(0), "GReward/Vault-not-zero-address");
require(_stakerCut < 1e9, "GReward/staker-cut-lt-1e9");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a require for _liquidator different than address zero.

require(_liquidator != address(0), "GReward/liq-not-zero-address");

gaugeController = _gaugeController;
vault = _vault;

emit Deployed(_gaugeController, _vault);
stakerCut = _stakerCut;
liquidator = _liquidator;

emit Deployed(
_gaugeController,
_vault,
_liquidator,
_stakerCut
);
}

/* ============ External Functions ============ */
Expand All @@ -148,46 +171,40 @@ contract GaugeReward is IGaugeReward {
* @notice Add rewards denominated in `token` for the given `gauge`.
* @dev Called by the liquidation contract anytime tokens are liquidated.
* @dev Will push token to the `gaugeRewardTokens` mapping if different from the current one.
* @param _gauge Address of the gauge to add rewards for
* @param _token Address of the token to add rewards for
* @param _amount Amount of rewards to add
* @param ticket The address of the tickets that were sold
* @param token The address of the token that the tickets were sold for
* @param tokenAmount The amount of tokens that the tickets were sold for
*/
function addRewards(
address _gauge,
IERC20 _token,
uint256 _amount
) external {
address _vaultAddress = address(vault);

if (_token != _currentRewardToken(_gauge).token) {
vault.increaseERC20Allowance(
_token,
address(this),
type(uint256).max - _token.allowance(_vaultAddress, address(this))
);
function afterSwap(IPrizePool, ITicket ticket, uint256, IERC20 token, uint256 tokenAmount) external override {
require(msg.sender == liquidator, "GReward/only-liquidator");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a modifier instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That code isn't used anywhere else- refactoring it into a modifier doesn't make sense


_pushRewardToken(_gauge, _token);
address gauge = address(ticket);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty confusing. I still don't understand what the gauge is referring to in this contract.
Cause here, it means that we add rewards for a ticket and not a gauge?

if (token != _currentRewardToken(gauge).token) {
_pushRewardToken(gauge, token);
}

_token.safeTransferFrom(msg.sender, _vaultAddress, _amount);
uint256 stakerRewards = (tokenAmount * stakerCut) / 1e9;

// Exchange rate = amount / current staked amount on gauge
uint256 _exchangeRate = (_amount * 1e18) / gaugeController.getGaugeBalance(_gauge);
uint256 _exchangeRate = (stakerRewards * 1e18) / gaugeController.getGaugeBalance(gauge);

tokenGaugeExchangeRates[_token][_gauge] += _exchangeRate;
tokenGaugeExchangeRates[token][gauge] += _exchangeRate;

emit RewardsAdded(_gauge, _token, _vaultAddress, _amount, _exchangeRate);
emit RewardsAdded(gauge, token, stakerRewards, _exchangeRate);
}

/// @inheritdoc IGaugeReward
function afterIncreaseGauge(
address _gauge,
address _user,
uint256 _oldStakeBalance
) external override {
) external override onlyGaugeController {
RewardToken memory _rewardToken = _claimPastRewards(_gauge, _user, _oldStakeBalance);

_claim(_gauge, _rewardToken.token, _user, _oldStakeBalance, false);
if (address(_rewardToken.token) != address(0)) {
_claim(_gauge, _rewardToken.token, _user, _oldStakeBalance, false);
}

userLastClaimedTimestamp[_user] = block.timestamp;
}

Expand All @@ -196,10 +213,11 @@ contract GaugeReward is IGaugeReward {
address _gauge,
address _user,
uint256 _oldStakeBalance
) external override {
) external override onlyGaugeController {
RewardToken memory _rewardToken = _claimPastRewards(_gauge, _user, _oldStakeBalance);

_claim(_gauge, _rewardToken.token, _user, _oldStakeBalance, false);
if (_rewardToken.token != IERC20(address(0))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (_rewardToken.token != IERC20(address(0))) {
if (address(_rewardToken.token) != address(0)) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean....same difference right?

_claim(_gauge, _rewardToken.token, _user, _oldStakeBalance, false);
}
userLastClaimedTimestamp[_user] = block.timestamp;
}

Expand All @@ -217,6 +235,7 @@ contract GaugeReward is IGaugeReward {
uint256 _stakeBalance = gaugeController.getUserGaugeBalance(_gauge, _user);

_claimPastRewards(_gauge, _user, _stakeBalance);

_claim(_gauge, _token, _user, _stakeBalance, false);

userLastClaimedTimestamp[_user] = block.timestamp;
Expand Down Expand Up @@ -257,37 +276,42 @@ contract GaugeReward is IGaugeReward {
* @param _token Address of the token to claim rewards for
* @param _user Address of the user to claim rewards for
* @param _stakeBalance User stake balance
* @param _claimPastRewards Whether this function is called in `_claimPastRewards` or not
* @param _eligibleForPastRewards Whether this function is called in `_eligibleForPastRewards` or not
*/
function _claim(
address _gauge,
IERC20 _token,
address _user,
uint256 _stakeBalance,
bool _claimPastRewards
bool _eligibleForPastRewards
) internal returns (uint256) {
uint256 _previousExchangeRate = userTokenGaugeExchangeRates[_user][_token][_gauge];
uint256 _currentExchangeRate = tokenGaugeExchangeRates[_token][_gauge];

if (!_claimPastRewards && _previousExchangeRate == 0) {
if (!_eligibleForPastRewards && _previousExchangeRate == 0) {
_previousExchangeRate = _currentExchangeRate;
}

// Rewards = deltaExchangeRate * stakeBalance
uint256 _rewards = (_currentExchangeRate - _previousExchangeRate) * _stakeBalance;

userTokenRewards[_user][_token] += _rewards;
uint256 _rewards = ((_currentExchangeRate - _previousExchangeRate) * _stakeBalance) / 1e18;

// Record current exchange rate
userTokenGaugeExchangeRates[_user][_token][_gauge] = _currentExchangeRate;

_token.safeTransferFrom(address(vault), _user, _rewards);
userTokenRewardBalances[_user][_token] += _rewards;

emit RewardsClaimed(_gauge, _token, _user, _rewards, _currentExchangeRate);

return _rewards;
}

function redeem(address _user, IERC20 _token) external returns (uint256) {
uint256 rewards = userTokenRewardBalances[_user][_token];
userTokenRewardBalances[_user][_token] = 0;
_token.safeTransferFrom(address(vault), _user, rewards);
return rewards;
}

/**
* @notice Claim user past rewards for a given gauge.
* @param _gauge Address of the gauge to claim rewards for
Expand All @@ -300,20 +324,23 @@ contract GaugeReward is IGaugeReward {
uint256 _stakeBalance
) internal returns (RewardToken memory) {
uint256 _userLastClaimedTimestamp = userLastClaimedTimestamp[_user];
uint256 _gaugeRewardTokenslength = gaugeRewardTokens[_gauge].length;
uint256 _gaugeRewardTokensLength = gaugeRewardTokens[_gauge].length;

RewardToken memory _rewardToken;
RewardToken memory _latestRewardToken;

if (_gaugeRewardTokenslength > 1) {
for (uint256 i = _gaugeRewardTokenslength - 1; i >= 0; i--) {
if (_gaugeRewardTokensLength > 0) {
uint256 i = _gaugeRewardTokensLength;

while (i > 0) {
i = i - 1;
_rewardToken = gaugeRewardTokens[_gauge][i];

if (i == _gaugeRewardTokenslength - 1) {
if (i == _gaugeRewardTokensLength - 1) {
_latestRewardToken = _rewardToken;
}

if (_rewardToken.timestamp > _userLastClaimedTimestamp) {
if (_userLastClaimedTimestamp > 0 && _rewardToken.timestamp > _userLastClaimedTimestamp) {
_claim(_gauge, _rewardToken.token, _user, _stakeBalance, true);
} else {
break;
Expand All @@ -338,4 +365,9 @@ contract GaugeReward is IGaugeReward {

emit RewardTokenPushed(_gauge, _token, _currentTimestamp);
}

modifier onlyGaugeController() {
require(msg.sender == address(gaugeController), "GReward/only-gc");
_;
}
}
Loading