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