mirror of
				https://github.com/Instadapp/aave-protocol-v2.git
				synced 2024-07-29 21:47:30 +00:00 
			
		
		
		
	feat: Adapt reward tokens to Curve Treasury.
This commit is contained in:
		
							parent
							
								
									d178cc1cea
								
							
						
					
					
						commit
						8d52a520b0
					
				|  | @ -3,4 +3,6 @@ pragma solidity 0.6.12; | |||
| 
 | ||||
| interface ICurveFeeDistributor { | ||||
|   function claim() external; | ||||
| 
 | ||||
|   function token() external view returns (address); | ||||
| } | ||||
|  |  | |||
|  | @ -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); | ||||
| } | ||||
							
								
								
									
										102
									
								
								contracts/adapters/interfaces/curve/ICurveTreasury.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								contracts/adapters/interfaces/curve/ICurveTreasury.sol
									
									
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||
| } | ||||
|  | @ -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; | ||||
|   } | ||||
| } | ||||
|  | @ -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 */ | ||||
							
								
								
									
										320
									
								
								contracts/adapters/rewards/curve/CurveTreasury.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										320
									
								
								contracts/adapters/rewards/curve/CurveTreasury.sol
									
									
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||
|   } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 David Racero
						David Racero