feat: Added first iteration of CurveTreasury.sol to handle Curve LP token gauges

This commit is contained in:
David Racero 2021-06-09 12:43:16 +02:00
parent ad8d1ea817
commit d178cc1cea
9 changed files with 394 additions and 13 deletions

View File

@ -0,0 +1,179 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.12;
import {SafeMath} from '../../dependencies/openzeppelin/contracts/SafeMath.sol';
import {WadRayMath} from '../../protocol/libraries/math/WadRayMath.sol';
import {PercentageMath} from '../../protocol/libraries/math/PercentageMath.sol';
import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol';
import {ILendingRateOracle} from '../../interfaces/ILendingRateOracle.sol';
import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol';
import {ISushiRewardsAwareAToken} from '../interfaces/sushi/ISushiRewardsAwareAToken.sol';
import {IMasterChef} from '../../adapters/interfaces/sushi/IMasterChef.sol';
import {
DefaultReserveInterestRateStrategy
} from '../../protocol/lendingpool/DefaultReserveInterestRateStrategy.sol';
/**
* @title DefaultReserveInterestRateStrategy contract
* @notice Implements the calculation of the interest rates depending on the reserve state
* @dev The model of interest rate is based on 2 slopes, one before the `OPTIMAL_UTILIZATION_RATE`
* point of utilization and another from that one to 100%
* - An instance of this same contract, can't be used across different Aave markets, due to the caching
* of the LendingPoolAddressesProvider
* @author Aave
**/
contract CurveLPReserveInterestRateStrategy is DefaultReserveInterestRateStrategy {
using WadRayMath for uint256;
using SafeMath for uint256;
using PercentageMath for uint256;
constructor(
ILendingPoolAddressesProvider provider,
uint256 optimalUtilizationRate,
uint256 baseVariableBorrowRate,
uint256 variableRateSlope1,
uint256 variableRateSlope2,
uint256 stableRateSlope1,
uint256 stableRateSlope2
)
public
DefaultReserveInterestRateStrategy(
provider,
optimalUtilizationRate,
baseVariableBorrowRate,
variableRateSlope1,
variableRateSlope2,
stableRateSlope1,
stableRateSlope2
)
{}
/**
* @dev Calculates the interest rates depending on the reserve's state and configurations
* @param reserve The address of the reserve
* @param liquidityAdded The liquidity added during the operation
* @param liquidityTaken The liquidity taken during the operation
* @param totalStableDebt The total borrowed from the reserve a stable rate
* @param totalVariableDebt The total borrowed from the reserve at a variable rate
* @param averageStableBorrowRate The weighted average of all the stable rate loans
* @param reserveFactor The reserve portion of the interest that goes to the treasury of the market
* @return The liquidity rate, the stable borrow rate and the variable borrow rate
**/
function calculateInterestRates(
address reserve,
address aToken,
uint256 liquidityAdded,
uint256 liquidityTaken,
uint256 totalStableDebt,
uint256 totalVariableDebt,
uint256 averageStableBorrowRate,
uint256 reserveFactor
)
external
view
override
returns (
uint256,
uint256,
uint256
)
{
uint256 poolId = ISushiRewardsAwareAToken(aToken).getMasterChefPoolId();
(uint256 stakedBalance, ) =
IMasterChef(ISushiRewardsAwareAToken(aToken).getMasterChef()).userInfo(poolId, aToken);
//avoid stack too deep
uint256 availableLiquidity = stakedBalance.add(liquidityAdded).sub(liquidityTaken);
return
calculateInterestRates(
reserve,
availableLiquidity,
totalStableDebt,
totalVariableDebt,
averageStableBorrowRate,
reserveFactor
);
}
/**
* @dev Calculates the interest rates depending on the reserve's state and configurations.
* NOTE This function is kept for compatibility with the previous DefaultInterestRateStrategy interface.
* New protocol implementation uses the new calculateInterestRates() interface
* @param reserve The address of the reserve
* @param availableLiquidity The liquidity available in the corresponding aToken
* @param totalStableDebt The total borrowed from the reserve a stable rate
* @param totalVariableDebt The total borrowed from the reserve at a variable rate
* @param averageStableBorrowRate The weighted average of all the stable rate loans
* @param reserveFactor The reserve portion of the interest that goes to the treasury of the market
* @return The liquidity rate, the stable borrow rate and the variable borrow rate
**/
function calculateInterestRates(
address reserve,
uint256 availableLiquidity,
uint256 totalStableDebt,
uint256 totalVariableDebt,
uint256 averageStableBorrowRate,
uint256 reserveFactor
)
public
view
virtual
override
returns (
uint256,
uint256,
uint256
)
{
CalcInterestRatesLocalVars memory vars;
vars.totalDebt = totalStableDebt.add(totalVariableDebt);
vars.currentVariableBorrowRate = 0;
vars.currentStableBorrowRate = 0;
vars.currentLiquidityRate = 0;
vars.utilizationRate = vars.totalDebt == 0
? 0
: vars.totalDebt.rayDiv(availableLiquidity.add(vars.totalDebt));
vars.currentStableBorrowRate = ILendingRateOracle(addressesProvider.getLendingRateOracle())
.getMarketBorrowRate(reserve);
if (vars.utilizationRate > OPTIMAL_UTILIZATION_RATE) {
uint256 excessUtilizationRateRatio =
vars.utilizationRate.sub(OPTIMAL_UTILIZATION_RATE).rayDiv(EXCESS_UTILIZATION_RATE);
vars.currentStableBorrowRate = vars.currentStableBorrowRate.add(_stableRateSlope1).add(
_stableRateSlope2.rayMul(excessUtilizationRateRatio)
);
vars.currentVariableBorrowRate = _baseVariableBorrowRate.add(_variableRateSlope1).add(
_variableRateSlope2.rayMul(excessUtilizationRateRatio)
);
} else {
vars.currentStableBorrowRate = vars.currentStableBorrowRate.add(
_stableRateSlope1.rayMul(vars.utilizationRate.rayDiv(OPTIMAL_UTILIZATION_RATE))
);
vars.currentVariableBorrowRate = _baseVariableBorrowRate.add(
vars.utilizationRate.rayMul(_variableRateSlope1).rayDiv(OPTIMAL_UTILIZATION_RATE)
);
}
vars.currentLiquidityRate = _getOverallBorrowRate(
totalStableDebt,
totalVariableDebt,
vars
.currentVariableBorrowRate,
averageStableBorrowRate
)
.rayMul(vars.utilizationRate)
.percentMul(PercentageMath.PERCENTAGE_FACTOR.sub(reserveFactor));
return (
vars.currentLiquidityRate,
vars.currentStableBorrowRate,
vars.currentVariableBorrowRate
);
}
}

View File

@ -0,0 +1,6 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.12;
interface ICurveFeeDistributor {
function claim() external;
}

View File

@ -0,0 +1,12 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.12;
interface IVotingEscrow {
function create_lock(uint256 value, uint256 time) external;
function increase_amount(uint256 value) external;
function increase_unlock_time(uint256 time) external;
function withdraw() external;
}

View File

@ -14,7 +14,7 @@ import {IAaveIncentivesController} from '../../interfaces/IAaveIncentivesControl
* @notice AToken aware to claim and distribute rewards from an external Curve Gauge controller.
* @author Aave
*/
contract CurveRewardsAwareAToken is RewardsAwareAToken {
contract CurveGaugeRewardsAwareAToken is RewardsAwareAToken {
// CRV token address
address internal immutable CRV_TOKEN;

View File

@ -0,0 +1,184 @@
// 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;
}
}

View File

@ -51,7 +51,7 @@ import {
FlashLiquidationAdapterFactory,
RewardsTokenFactory,
RewardsATokenMockFactory,
CurveRewardsAwareATokenFactory,
CurveGaugeRewardsAwareATokenFactory,
} from '../types';
import {
withSaveAndVerify,
@ -664,8 +664,8 @@ export const chooseATokenDeployment = (id: eContractid) => {
return deployDelegationAwareATokenImpl;
case eContractid.RewardsATokenMock:
return deployRewardATokenMock;
case eContractid.CurveRewardsAwareAToken:
return deployCurveRewardsAwareATokenByNetwork;
case eContractid.CurveGaugeRewardsAwareAToken:
return deployCurveGaugeRewardsAwareATokenByNetwork;
default:
throw Error(`Missing aToken implementation deployment script for: ${id}`);
}
@ -719,20 +719,20 @@ export const deployATokenImplementations = async (
}
};
export const deployCurveRewardsAwareAToken = async (
export const deployCurveGaugeRewardsAwareAToken = async (
crvToken: tEthereumAddress,
verify?: boolean
) => {
const args: [tEthereumAddress] = [crvToken];
return withSaveAndVerify(
await new CurveRewardsAwareATokenFactory(await getFirstSigner()).deploy(...args),
eContractid.CurveRewardsAwareAToken,
await new CurveGaugeRewardsAwareATokenFactory(await getFirstSigner()).deploy(...args),
eContractid.CurveGaugeRewardsAwareAToken,
args,
verify
);
};
export const deployCurveRewardsAwareATokenByNetwork = async (verify?: boolean) => {
export const deployCurveGaugeRewardsAwareATokenByNetwork = async (verify?: boolean) => {
const network = DRE.network.name as eEthereumNetwork;
return deployCurveRewardsAwareAToken(CRV_TOKEN[network], verify);
return deployCurveGaugeRewardsAwareAToken(CRV_TOKEN[network], verify);
};

View File

@ -89,7 +89,7 @@ export enum eContractid {
FlashLiquidationAdapter = 'FlashLiquidationAdapter',
RewardsATokenMock = 'RewardsATokenMock',
RewardsToken = 'RewardsToken',
CurveRewardsAwareAToken = 'CurveRewardsAwareAToken',
CurveGaugeRewardsAwareAToken = 'CurveGaugeRewardsAwareAToken',
}
/*

2
package-lock.json generated
View File

@ -14654,7 +14654,7 @@
}
},
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#1a27c59c15ab1e95ee8e5c4ed6ad814c49cc439e",
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#ee3994657fa7a427238e6ba92a84d0b529bbcde0",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"dev": true,
"requires": {

View File

@ -17,7 +17,7 @@ import {
import { deployDefaultReserveInterestRateStrategy } from '../../../helpers/contracts-deployments';
import { IERC20Factory } from '../../../types/IERC20Factory';
import BigNumberJs from 'bignumber.js';
import { CurveRewardsAwareATokenFactory } from '../../../types';
import { CurveGaugeRewardsAwareATokenFactory } from '../../../types';
import { eContractid, eEthereumNetwork, tEthereumAddress } from '../../../helpers/types';
import { strategyWBTC } from '../../../markets/aave/reservesConfigs';
import { checkRewards } from '../helpers/rewards-distribution/verify';
@ -87,7 +87,7 @@ const listGauge = async (gauge: GaugeInfo) => {
eEthereumNetwork.main
);
const aTokenImpl = (
await new CurveRewardsAwareATokenFactory(await getFirstSigner()).deploy(CRV_TOKEN)
await new CurveGaugeRewardsAwareATokenFactory(await getFirstSigner()).deploy(CRV_TOKEN)
).address;
const stableDebtTokenImpl = await getContractAddressWithJsonFallback(
eContractid.StableDebtToken,