mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
feat: Added first iteration of CurveTreasury.sol to handle Curve LP token gauges
This commit is contained in:
parent
ad8d1ea817
commit
d178cc1cea
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
// SPDX-License-Identifier: agpl-3.0
|
||||
pragma solidity 0.6.12;
|
||||
|
||||
interface ICurveFeeDistributor {
|
||||
function claim() external;
|
||||
}
|
12
contracts/adapters/interfaces/curve/IVotingEscrow.sol
Normal file
12
contracts/adapters/interfaces/curve/IVotingEscrow.sol
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
|
184
contracts/adapters/rewards/CurveTreasury.sol
Normal file
184
contracts/adapters/rewards/CurveTreasury.sol
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -89,7 +89,7 @@ export enum eContractid {
|
|||
FlashLiquidationAdapter = 'FlashLiquidationAdapter',
|
||||
RewardsATokenMock = 'RewardsATokenMock',
|
||||
RewardsToken = 'RewardsToken',
|
||||
CurveRewardsAwareAToken = 'CurveRewardsAwareAToken',
|
||||
CurveGaugeRewardsAwareAToken = 'CurveGaugeRewardsAwareAToken',
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -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": {
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue
Block a user