diff --git a/contracts/adapters/interfaces/curve/ICurveFeeDistributor.sol b/contracts/adapters/interfaces/curve/ICurveFeeDistributor.sol index a7870a54..aae2fb8f 100644 --- a/contracts/adapters/interfaces/curve/ICurveFeeDistributor.sol +++ b/contracts/adapters/interfaces/curve/ICurveFeeDistributor.sol @@ -3,4 +3,6 @@ pragma solidity 0.6.12; interface ICurveFeeDistributor { function claim() external; + + function token() external view returns (address); } diff --git a/contracts/adapters/interfaces/curve/ICurveGaugeController.sol b/contracts/adapters/interfaces/curve/ICurveGaugeController.sol new file mode 100644 index 00000000..21e9bc2b --- /dev/null +++ b/contracts/adapters/interfaces/curve/ICurveGaugeController.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; + +interface ICurveGaugeController { + function vote_for_gauge_weights(address _gauge_addr, uint256 _user_weight) external; + + function token() external view returns (address); +} diff --git a/contracts/adapters/interfaces/curve/ICurveTreasury.sol b/contracts/adapters/interfaces/curve/ICurveTreasury.sol new file mode 100644 index 00000000..a9ef56aa --- /dev/null +++ b/contracts/adapters/interfaces/curve/ICurveTreasury.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; + +/** + * @title Curve Treasury that holds Curve LP and Gauge tokens + * @notice The treasury holds Curve assets like LP or Gauge tokens and can lock veCRV for boosting Curve yields + * @author Aave + */ +interface ICurveTreasury { + /** + * @dev Deposit Curve LP or Gauge LP tokens into the treasury from a whitelisted entity + * @param token Curve LP or Gauge LP token address + * @param amount Amount of tokens to deposit in base unit + * @param useGauge Flag to determine if the deposit should be staked + */ + function deposit( + address token, + uint256 amount, + bool useGauge + ) external; + + /** + * @dev Withdraw Curve LP or Gauge LP tokens into the treasury from a whitelisted entity + * @param token Curve LP or Gauge LP token address + * @param amount Amount of tokens to withdraw in base unit + * @param useGauge Flag to determine if the withdraw should be unstaked + */ + function withdraw( + address token, + uint256 amount, + bool useGauge + ) external; + + /** + * @dev Register entities to enable or disable deposits and withdraws from the treasury + * @param entities Entities addresses list + * @param tokens Curve LP Token addresses list + * @param gauges Curve Gauge Staking tokens list, use zero address to disable staking + * @param whitelisted Flag to determine if the entity should be enabled or disabled + */ + function setWhitelist( + address[] calldata entities, + address[] calldata tokens, + address[] calldata gauges, + bool[] memory whitelisted + ) external; + + /** + * @dev Claim all available rewards from Curve Gauge Staking contract + * @param gaugeUnderlyingToken The gauge underlying stake token to claim rewards + */ + function claimGaugeRewards(address gaugeUnderlyingToken) external; + + /** + * @dev Claim Curve Distributor fees and send to protocol treasury + */ + function claimCurveDistributorFees() external; + + /** + * @dev Lock the CRV at the veCRV contract up to 4 years to boost rewards from Curve + * @param amount Amount of CRV to lock inside the Lock Contract + * @param unlockTime Time where the CRV will be unlocked, maximum 4 years + */ + function lockCrv(uint256 amount, uint256 unlockTime) external; + + /** + * @dev Withdraw the unlocked CRV at the veCRV contract after the time lock + */ + function unlockCrv() external; + + /** + * @dev Increase the CRV amount inside the veCRV contract + * @param amount The amount of CRV token to add to the current time lock + */ + function increaseLockedCrv(uint256 amount) external; + + /** + * @dev Extend the CRV time lock inside the veCRV ontract + * @param unlockTime Next time where the CRV will be unlocked, maximum 4 years + */ + function increaseUnlockTimeCrv(uint256 unlockTime) external; + + /** Owner methods related with Gauge Controller Voting contract */ + + /** + * @dev Vote to set the gauge weights using veCRV + * @param gauge Gauge address to set the voting weight + * @param weight Percentage of voting with two decimal points, 100% equals 10000 + */ + function voteForGaugeWeights(address gauge, uint256 weight) external; + + /** + * @dev Get the current owner of this contract + */ + function owner() external view returns (address); + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * @param newOwner addess of the new owner + */ + function transferOwnership(address newOwner) external; +} diff --git a/contracts/adapters/rewards/CurveTreasury.sol b/contracts/adapters/rewards/CurveTreasury.sol deleted file mode 100644 index 81428fb3..00000000 --- a/contracts/adapters/rewards/CurveTreasury.sol +++ /dev/null @@ -1,184 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity 0.6.12; - -import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol'; -import {SafeERC20} from '../../dependencies/openzeppelin/contracts/SafeERC20.sol'; -import {ICurveGauge, ICurveGaugeView} from '../interfaces/curve/ICurveGauge.sol'; -import {IVotingEscrow} from '../interfaces/curve/IVotingEscrow.sol'; -import { - VersionedInitializable -} from '../../protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; -import {ICurveFeeDistributor} from '../interfaces/curve/ICurveFeeDistributor.sol'; - -/** - * @title Curve Treasury that holds Curve LP and Gauge tokens - * @notice The treasury holds Curve assets like LP or Gauge tokens and can lock veCRV for boosting Curve yields - * @author Aave - */ -contract CurveTreasury is VersionedInitializable { - using SafeERC20 for IERC20; - - address immutable VOTING_ESCROW; - address immutable CRV_TOKEN; - address immutable FEE_DISTRIBUTOR; - address private _owner; - - uint256 public constant TREASURY_REVISION = 0x1; - - mapping(address => mapping(address => bool)) internal _entityTokenWhitelist; - mapping(address => mapping(address => address)) internal _entityTokenGauge; - - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - - constructor( - address _votingEscrow, - address _crvToken, - address _curveFeeDistributor - ) public { - VOTING_ESCROW = _votingEscrow; - CRV_TOKEN = _crvToken; - FEE_DISTRIBUTOR = _curveFeeDistributor; - } - - /** - * @dev Throws if called by any account other than the owner. - */ - modifier onlyOwner() { - require(_owner == msg.sender, 'Ownable: caller is not the owner'); - _; - } - - modifier onlyWhitelistedEntity(address token) { - require(_entityTokenWhitelist[msg.sender][token] == true, 'ENTITY_NOT_WHITELISTED'); - _; - } - - function initialize( - address[] calldata entities, - address[] calldata tokens, - address[] calldata gauges, - address owner - ) external virtual initializer { - _owner = owner; - bool[] memory whitelisted = new bool[](entities.length); - _setWhitelist(entities, tokens, gauges, whitelisted); - } - - function getRevision() internal pure virtual override returns (uint256) { - return TREASURY_REVISION; - } - - function deposit( - address token, - uint256 amount, - bool useGauge - ) external onlyWhitelistedEntity(token) { - IERC20(token).safeTransferFrom(msg.sender, address(this), amount); - - if (useGauge && _entityTokenGauge[msg.sender][token] != address(0)) { - stakeGauge(_entityTokenGauge[msg.sender][token], amount); - } - } - - function withdraw( - address token, - uint256 amount, - bool useGauge - ) external onlyWhitelistedEntity(token) { - if (useGauge && _entityTokenGauge[msg.sender][token] != address(0)) { - unstakeGauge(_entityTokenGauge[msg.sender][token], amount); - } - IERC20(token).safeTransfer(msg.sender, amount); - } - - function stakeGauge(address gauge, uint256 amount) internal { - ICurveGauge(gauge).deposit(amount); - } - - function unstakeGauge(address gauge, uint256 amount) internal { - ICurveGauge(gauge).withdraw(amount); - } - - /** - * @dev Returns the address of the current owner. - */ - function owner() external view returns (address) { - return _owner; - } - - /** Owner methods */ - function approve( - address token, - address to, - uint256 amount - ) external onlyOwner { - IERC20(token).safeApprove(to, amount); - } - - function transferFrom( - address token, - address from, - address to, - uint256 amount - ) external onlyOwner { - IERC20(token).safeTransferFrom(from, to, amount); - } - - function setWhitelist( - address[] calldata entities, - address[] calldata tokens, - address[] calldata gauges, - bool[] memory whitelisted - ) external onlyOwner { - _setWhitelist(entities, tokens, gauges, whitelisted); - } - - function _setWhitelist( - address[] calldata entities, - address[] calldata tokens, - address[] calldata gauges, - bool[] memory whitelisted - ) internal { - for (uint256 e; e < entities.length; e++) { - _entityTokenWhitelist[entities[e]][tokens[e]] = whitelisted[e]; - IERC20(tokens[e]).safeApprove(entities[e], type(uint256).max); - if (gauges[e] != address(0)) { - IERC20(tokens[e]).safeApprove(gauges[e], type(uint256).max); - _entityTokenGauge[entities[e]][tokens[e]] = gauges[e]; - } - } - } - - function claimCurveFees() external onlyOwner { - ICurveFeeDistributor(FEE_DISTRIBUTOR).claim(); - } - - /** Owner methods related with veCRV to interact with Voting Escrow Curve contract */ - function lockCrv(uint256 amount, uint256 unlockTime) external onlyOwner { - IERC20(CRV_TOKEN).safeApprove(VOTING_ESCROW, amount); - IVotingEscrow(VOTING_ESCROW).create_lock(amount, unlockTime); - } - - function unlockCrv(uint256 amount, uint256 unlockTime) external onlyOwner { - IVotingEscrow(VOTING_ESCROW).withdraw(); - } - - function increaseLockedCrv(uint256 amount) external onlyOwner { - IERC20(CRV_TOKEN).safeApprove(VOTING_ESCROW, amount); - IVotingEscrow(VOTING_ESCROW).increase_amount(amount); - } - - function increaseUnlockTimeCrv(uint256 unlockTime) external onlyOwner { - IVotingEscrow(VOTING_ESCROW).increase_unlock_time(unlockTime); - } - - /** - * @dev Transfers ownership of the contract to a new account (`newOwner`). - * Can only be called by the current owner. - */ - function transferOwnership(address newOwner) external onlyOwner { - require(newOwner != address(0), 'Ownable: new owner is the zero address'); - emit OwnershipTransferred(_owner, newOwner); - _owner = newOwner; - } -} diff --git a/contracts/adapters/rewards/CurveGaugeRewardsAwareAToken.sol b/contracts/adapters/rewards/curve/CurveGaugeRewardsAwareAToken.sol similarity index 75% rename from contracts/adapters/rewards/CurveGaugeRewardsAwareAToken.sol rename to contracts/adapters/rewards/curve/CurveGaugeRewardsAwareAToken.sol index 2d1b1004..c13ecba1 100644 --- a/contracts/adapters/rewards/CurveGaugeRewardsAwareAToken.sol +++ b/contracts/adapters/rewards/curve/CurveGaugeRewardsAwareAToken.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.6.12; -import {ILendingPool} from '../../interfaces/ILendingPool.sol'; -import {RewardsAwareAToken} from '../../protocol/tokenization/RewardsAwareAToken.sol'; -import {SafeERC20} from '../../dependencies/openzeppelin/contracts/SafeERC20.sol'; -import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol'; -import {ICurveMinter} from '../interfaces/curve/ICurveMinter.sol'; -import {ICurveGauge, ICurveGaugeView} from '../interfaces/curve/ICurveGauge.sol'; -import {IAaveIncentivesController} from '../../interfaces/IAaveIncentivesController.sol'; +import {ILendingPool} from '../../../interfaces/ILendingPool.sol'; +import {RewardsAwareAToken} from '../../../protocol/tokenization/RewardsAwareAToken.sol'; +import {SafeERC20} from '../../../dependencies/openzeppelin/contracts/SafeERC20.sol'; +import {IERC20} from '../../../dependencies/openzeppelin/contracts/IERC20.sol'; +import {ICurveMinter} from '../../interfaces/curve/ICurveMinter.sol'; +import {ICurveGauge, ICurveGaugeView} from '../../interfaces/curve/ICurveGauge.sol'; +import {IAaveIncentivesController} from '../../../interfaces/IAaveIncentivesController.sol'; +import {ICurveTreasury} from '../../interfaces/curve/ICurveTreasury.sol'; /** * @title Curve Rewards Aware AToken @@ -17,6 +18,7 @@ import {IAaveIncentivesController} from '../../interfaces/IAaveIncentivesControl contract CurveGaugeRewardsAwareAToken is RewardsAwareAToken { // CRV token address address internal immutable CRV_TOKEN; + address internal immutable CURVE_TREASURY; // Gauge contract address address internal _gaugeController; @@ -29,8 +31,9 @@ contract CurveGaugeRewardsAwareAToken is RewardsAwareAToken { /** * @param crvToken The address of the $CRV token */ - constructor(address crvToken) public { + constructor(address crvToken, address crvTreasury) public { CRV_TOKEN = crvToken; + CURVE_TREASURY = crvTreasury; } /** @@ -79,18 +82,21 @@ contract CurveGaugeRewardsAwareAToken is RewardsAwareAToken { _underlyingAsset = underlyingAsset; _incentivesController = incentivesController; + _gaugeController = _decodeParams(params); + // Initialize Curve Gauge rewards - _gaugeController = underlyingAsset; _rewardTokens[0] = CRV_TOKEN; // Start for loop with index = 1 to not rewrite first element of rewardTokens for (uint256 index = 1; index < MAX_REWARD_TOKENS; index++) { - address reward = ICurveGaugeView(underlyingAsset).reward_tokens(index - 1); + address reward = ICurveGaugeView(_decodeParams(params)).reward_tokens(index - 1); if (reward == address(0)) break; _rewardTokens[index] = reward; } + IERC20(underlyingAsset).safeApprove(CURVE_TREASURY, type(uint256).max); + emit Initialized( underlyingAsset, address(pool), @@ -109,14 +115,14 @@ contract CurveGaugeRewardsAwareAToken is RewardsAwareAToken { * @dev External call to retrieve the lifetime accrued $CRV token rewards from the external Rewards Controller contract */ function _getExternalLifetimeCurve() internal view returns (uint256) { - return ICurveGaugeView(_gaugeController).integrate_fraction(address(this)); + return ICurveGaugeView(_gaugeController).integrate_fraction(CURVE_TREASURY); } /** * @dev External call to retrieve the current claimable extra token rewards from the external Rewards Controller contract */ function _getExternalClaimableCurveExtraRewards(address token) internal view returns (uint256) { - return ICurveGaugeView(_gaugeController).claimable_reward(address(this), token); + return ICurveGaugeView(_gaugeController).claimable_reward(CURVE_TREASURY, token); } /** End of Curve Specific functions */ @@ -154,7 +160,7 @@ contract CurveGaugeRewardsAwareAToken is RewardsAwareAToken { function _computeExternalLifetimeRewards(address token) internal override returns (uint256) { // The Curve Gauge can give exact lifetime rewards and accrued rewards for the CRV token if (token == CRV_TOKEN) { - ICurveGauge(_gaugeController).user_checkpoint(address(this)); + ICurveGauge(_gaugeController).user_checkpoint(CURVE_TREASURY); return _getExternalLifetimeCurve(); } // Other rewards from the Curve Gauge can not get the lifetime rewards externally, only at the moment of claim, due they are external rewards outside the Curve ecosystem. @@ -178,9 +184,6 @@ contract CurveGaugeRewardsAwareAToken is RewardsAwareAToken { * @dev External call to claim the lifetime accrued rewards of the aToken contract to the external Rewards Controller contract */ function _claimRewardsFromController() internal override { - // Mint CRV to aToken - ICurveMinter(ICurveGaugeView(_gaugeController).minter()).mint(_gaugeController); - _updateRewards(); } @@ -197,7 +200,7 @@ contract CurveGaugeRewardsAwareAToken is RewardsAwareAToken { priorTokenBalances[index] = IERC20(rewardToken).balanceOf(address(this)); } // Mint other rewards to aToken - ICurveGauge(_gaugeController).claim_rewards(address(this)); + ICurveTreasury(CURVE_TREASURY).claimGaugeRewards(_gaugeController); for (uint256 index = 1; index < MAX_REWARD_TOKENS; index++) { address rewardToken = _getRewardsTokenAddress(index); @@ -211,6 +214,37 @@ contract CurveGaugeRewardsAwareAToken is RewardsAwareAToken { } } + /** + * @dev Deposit LP tokens at the Curve Treasury and stake into the Gauge Contract from the Treasury + * @param token Address of the LP Curve token + * @param amount Amount of tokens to deposit + */ + function _stake(address token, uint256 amount) internal override returns (uint256) { + ICurveTreasury(CURVE_TREASURY).deposit(token, amount, true); + return amount; + } + + /** + * @dev Withdraw LP tokens at the Curve Treasury and stake into the Gauge Contract from the Treasury + * @param token Address of the LP Curve token + * @param amount Amount of tokens to withdraw + */ + function _unstake(address token, uint256 amount) internal override returns (uint256) { + ICurveTreasury(CURVE_TREASURY).withdraw(token, amount, true); + return amount; + } + + /** + * @dev Param decoder to get Gauge Staking address + * @param params Additional variadic field to include extra params. Expected parameters: + * @return address of gauge + */ + function _decodeParams(bytes memory params) internal pure returns (address) { + address gauge = abi.decode(params, (address)); + + return gauge; + } + /** End of Rewards Aware AToken functions */ /** Start of External getters */ diff --git a/contracts/adapters/rewards/curve/CurveTreasury.sol b/contracts/adapters/rewards/curve/CurveTreasury.sol new file mode 100644 index 00000000..15f6719b --- /dev/null +++ b/contracts/adapters/rewards/curve/CurveTreasury.sol @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; + +import {IERC20} from '../../../dependencies/openzeppelin/contracts/IERC20.sol'; +import {SafeERC20} from '../../../dependencies/openzeppelin/contracts/SafeERC20.sol'; +import {SafeMath} from '../../../dependencies/openzeppelin/contracts/SafeMath.sol'; +import {ICurveGauge, ICurveGaugeView} from '../../interfaces/curve/ICurveGauge.sol'; +import {ICurveMinter} from '../../interfaces/curve/ICurveMinter.sol'; +import {ICurveGaugeController} from '../../interfaces/curve/ICurveGaugeController.sol'; +import {IVotingEscrow} from '../../interfaces/curve/IVotingEscrow.sol'; +import { + VersionedInitializable +} from '../../../protocol/libraries/aave-upgradeability/VersionedInitializable.sol'; +import {ICurveFeeDistributor} from '../../interfaces/curve/ICurveFeeDistributor.sol'; +import {ICurveTreasury} from '../../interfaces/curve/ICurveTreasury.sol'; + +/** + * @title Curve Treasury that holds Curve LP and Gauge tokens + * @notice The treasury holds Curve assets like LP or Gauge tokens and can lock veCRV for boosting Curve yields + * @author Aave + */ +contract CurveTreasury is ICurveTreasury, VersionedInitializable { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + uint256 public constant MAX_REWARD_TOKENS = 10; + + address immutable VOTING_ESCROW; + address immutable CRV_TOKEN; + address immutable FEE_DISTRIBUTOR; + address immutable GAUGE_CONTROLLER; + address immutable AAVE_COLLECTOR; + + address private _owner; + + uint256 public constant TREASURY_REVISION = 0x1; + + mapping(address => mapping(address => bool)) internal _entityTokenWhitelist; + mapping(address => mapping(address => address)) internal _entityTokenGauge; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + constructor( + address _votingEscrow, + address _crvToken, + address _curveFeeDistributor, + address _gaugeController, + address _aaveCollector + ) public { + VOTING_ESCROW = _votingEscrow; + CRV_TOKEN = _crvToken; + FEE_DISTRIBUTOR = _curveFeeDistributor; + GAUGE_CONTROLLER = _gaugeController; + AAVE_COLLECTOR = _aaveCollector; + } + + /** + * @dev Revert if caller is not the owner of this contract + */ + modifier onlyOwner() { + require(_owner == msg.sender, 'Ownable: caller is not the owner'); + _; + } + + /** + * @dev Revert if caller and selected token is not a whitelisted entity + */ + modifier onlyWhitelistedEntity(address token) { + require(_entityTokenWhitelist[msg.sender][token] == true, 'ENTITY_NOT_WHITELISTED'); + _; + } + + /** + * @dev Revert if caller gauge token is not whitelisted + */ + modifier onlyWhitelistedGauge(address token) { + require(_entityTokenGauge[msg.sender][token] != address(0), 'ENTITY_GAUGE_NOT_WHITELISTED'); + _; + } + + /** + * @dev Initializes the contract with an owner and optional entities whitelist + * @param owner Sets the owner of the contract + * @param entities Entities addresses list, optional argument + * @param tokens Curve LP Token addresses list, optional argument + * @param gauges Curve Gauge Staking tokens list, use zero address to disable staking, optional argument + */ + function initialize( + address owner, + address[] calldata entities, + address[] calldata tokens, + address[] calldata gauges + ) external virtual initializer { + _owner = owner; + if (entities.length > 0) { + bool[] memory whitelisted = new bool[](entities.length); + for (uint256 x; x < whitelisted.length; x++) { + whitelisted[x] = true; + } + _setWhitelist(entities, tokens, gauges, whitelisted); + } + } + + /// @inheritdoc ICurveTreasury + function deposit( + address token, + uint256 amount, + bool useGauge + ) external override onlyWhitelistedEntity(token) { + IERC20(token).safeTransferFrom(msg.sender, address(this), amount); + + if (useGauge && _entityTokenGauge[msg.sender][token] != address(0)) { + _stakeGauge(_entityTokenGauge[msg.sender][token], amount); + } + } + + /// @inheritdoc ICurveTreasury + function withdraw( + address token, + uint256 amount, + bool useGauge + ) external override onlyWhitelistedEntity(token) { + if (useGauge && _entityTokenGauge[msg.sender][token] != address(0)) { + _unstakeGauge(_entityTokenGauge[msg.sender][token], amount); + } + IERC20(token).safeTransfer(msg.sender, amount); + } + + /// @inheritdoc ICurveTreasury + function setWhitelist( + address[] calldata entities, + address[] calldata tokens, + address[] calldata gauges, + bool[] memory whitelisted + ) external override onlyOwner { + _setWhitelist(entities, tokens, gauges, whitelisted); + } + + /// @inheritdoc ICurveTreasury + function claimGaugeRewards(address gaugeUnderlyingToken) + external + override + onlyWhitelistedEntity(gaugeUnderlyingToken) + { + address gauge = _entityTokenGauge[msg.sender][gaugeUnderlyingToken]; + + // Claim CRV from Curve Minter + uint256 priorCrvBalance = IERC20(CRV_TOKEN).balanceOf(address(this)); + ICurveMinter(ICurveGaugeView(gauge).minter()).mint(gauge); + uint256 afterCrvBalance = IERC20(CRV_TOKEN).balanceOf(address(this)); + + // Transfer CRV to entity + uint256 crvRewards = afterCrvBalance.sub(priorCrvBalance); + if (crvRewards > 0) { + IERC20(CRV_TOKEN).safeTransfer(msg.sender, crvRewards); + } + + // Claim the extra rewards from Gauge Staking + uint256[] memory priorRewardsBalance = new uint256[](MAX_REWARD_TOKENS); + uint256[] memory afterRewardsBalance = new uint256[](MAX_REWARD_TOKENS); + + // Calculate balances prior claiming rewards + for (uint256 index = 1; index < MAX_REWARD_TOKENS; index++) { + address rewardToken = ICurveGaugeView(gauge).reward_tokens(index - 1); + if (rewardToken == address(0)) break; + priorRewardsBalance[index] = IERC20(rewardToken).balanceOf(address(this)); + } + + // Claim extra rewards + ICurveGauge(gauge).claim_rewards(); + + // Transfer extra rewards to entity + for (uint256 index = 1; index < MAX_REWARD_TOKENS; index++) { + address rewardToken = ICurveGaugeView(gauge).reward_tokens(index - 1); + if (rewardToken == address(0)) break; + afterRewardsBalance[index] = IERC20(rewardToken).balanceOf(address(this)); + uint256 rewardsAmount = afterRewardsBalance[index].sub(priorRewardsBalance[index]); + if (rewardsAmount > 0) { + IERC20(rewardToken).safeTransfer(msg.sender, rewardsAmount); + } + } + } + + /// @inheritdoc ICurveTreasury + function claimCurveDistributorFees() external override onlyOwner { + address rewardsToken = ICurveFeeDistributor(FEE_DISTRIBUTOR).token(); + uint256 priorBalance = IERC20(rewardsToken).balanceOf(address(this)); + ICurveFeeDistributor(FEE_DISTRIBUTOR).claim(); + uint256 afterBalance = IERC20(rewardsToken).balanceOf(address(this)); + uint256 rewards = afterBalance.sub(priorBalance); + if (rewards > 0) { + IERC20(rewardsToken).safeTransfer(AAVE_COLLECTOR, rewards); + } + } + + /** Owner methods related with veCRV to interact with Voting Escrow Curve contract */ + + /// @inheritdoc ICurveTreasury + function lockCrv(uint256 amount, uint256 unlockTime) external override onlyOwner { + IERC20(CRV_TOKEN).safeApprove(VOTING_ESCROW, 0); + IERC20(CRV_TOKEN).safeApprove(VOTING_ESCROW, amount); + IVotingEscrow(VOTING_ESCROW).create_lock(amount, unlockTime); + } + + /// @inheritdoc ICurveTreasury + function unlockCrv() external override onlyOwner { + IVotingEscrow(VOTING_ESCROW).withdraw(); + } + + /// @inheritdoc ICurveTreasury + function increaseLockedCrv(uint256 amount) external override onlyOwner { + IERC20(CRV_TOKEN).safeApprove(VOTING_ESCROW, 0); + IERC20(CRV_TOKEN).safeApprove(VOTING_ESCROW, amount); + IVotingEscrow(VOTING_ESCROW).increase_amount(amount); + } + + /// @inheritdoc ICurveTreasury + function increaseUnlockTimeCrv(uint256 unlockTime) external override onlyOwner { + IVotingEscrow(VOTING_ESCROW).increase_unlock_time(unlockTime); + } + + /** Owner methods related with Gauge Controller Voting contract */ + + /// @inheritdoc ICurveTreasury + function voteForGaugeWeights(address gauge, uint256 weight) external override onlyOwner { + ICurveGaugeController(GAUGE_CONTROLLER).vote_for_gauge_weights(gauge, weight); + } + + /// @inheritdoc ICurveTreasury + function transferOwnership(address newOwner) external override onlyOwner { + require(newOwner != address(0), 'New owner cant be the zero address'); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } + + /// @inheritdoc ICurveTreasury + function owner() external view override returns (address) { + return _owner; + } + + /** + * @dev Get the implementation revision + * @return uin256 - The current version of the implementation + */ + function getRevision() internal pure virtual override returns (uint256) { + return TREASURY_REVISION; + } + + /** + * @dev Register entities to enable or disable deposits and withdraws from the treasury + * @param entities Entities addresses list + * @param tokens Curve LP Token addresses list + * @param gauges Curve Gauge Staking tokens list, use zero address to disable staking + * @param whitelisted Flag to determine if the entity should be enabled or disabled + */ + function _setWhitelist( + address[] calldata entities, + address[] calldata tokens, + address[] calldata gauges, + bool[] memory whitelisted + ) internal { + for (uint256 e; e < entities.length; e++) { + _entityTokenWhitelist[entities[e]][tokens[e]] = whitelisted[e]; + if (whitelisted[e] == true) { + if (gauges[e] != address(0)) { + _entityTokenGauge[entities[e]][tokens[e]] = gauges[e]; + } + _approveEntityTokens(entities[e], tokens[e], gauges[e], type(uint256).max); + } else { + _entityTokenGauge[entities[e]][tokens[e]] = address(0); + _approveEntityTokens(entities[e], tokens[e], gauges[e], 0); + } + } + } + + /** + * @dev ERC20 approval to allow entity and gauge staking contracts to pull whitelisted tokens and rewards from the treasury + * @param entity Entity address + * @param token Curve LP Token contract address + * @param gauge Curve Gauge Staking contract address + * @param amount Amount of tokens + */ + function _approveEntityTokens( + address entity, + address token, + address gauge, + uint256 amount + ) internal { + IERC20(token).safeApprove(entity, 0); + IERC20(token).safeApprove(entity, amount); + if (gauge != address(0)) { + IERC20(token).safeApprove(gauge, 0); + IERC20(token).safeApprove(gauge, amount); + for (uint256 index = 0; index < MAX_REWARD_TOKENS; index++) { + address reward = ICurveGaugeView(gauge).reward_tokens(index - 1); + if (reward == address(0)) break; + IERC20(reward).safeApprove(entity, 0); + IERC20(reward).safeApprove(entity, amount); + } + } + } + + /** + * @dev Stake underliying token inside the Gauge Staking contract + * @param gauge Address of the Gauge Staking contract + * @param amount Amount of the underlying token to stake inside the contract + */ + function _stakeGauge(address gauge, uint256 amount) internal { + ICurveGauge(gauge).deposit(amount); + } + + /** + * @dev Unstake underliying token from the Gauge Staking contract + * @param gauge Address of the Gauge Staking contract + * @param amount Amount of the underlying token to withdraw from the contract + */ + function _unstakeGauge(address gauge, uint256 amount) internal { + ICurveGauge(gauge).withdraw(amount); + } +}