Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AaveV3LeverageStrategyExtension audit feedback #142

Merged
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f3a2d4b
Test showing that settting Emode does not affect getReserveConfigurat…
ckoopmann Jun 15, 2023
77244d3
Start adjusting StrategyExtension test to switcht to wsteth/eth pair
ckoopmann Jun 16, 2023
51c978f
Fixing tests
ckoopmann Jun 19, 2023
c29adfd
Fix test
ckoopmann Jun 21, 2023
bebb8bb
First test case for rebalancing in e-mode
ckoopmann Jun 21, 2023
5526d84
Fix handling of emode ltv / liquidationThreshold
ckoopmann Jun 21, 2023
aa7afc5
Add test case for wrong repay threshold
ckoopmann Jun 22, 2023
dba1428
Fix tests
ckoopmann Jun 22, 2023
718dd42
Remove subtraction of unutilizedLeveragePercentage when delevering
ckoopmann Jun 22, 2023
f2eacaa
Add test for targetLeverageRatio verification
ckoopmann Jun 27, 2023
eeb34fb
Add verification step for targetLeverageRatio >= 1
ckoopmann Jun 27, 2023
9d04e7b
Switch div / mul order in _calculateMinRepayUnits
ckoopmann Jun 27, 2023
0e291b4
Switch div / mul order in calculateChunkRebalanceNotional
ckoopmann Jun 27, 2023
7ab6d28
Switch to latestRoundData on chainlink call
ckoopmann Jun 27, 2023
0e10200
Add check for maximum oracle price age
ckoopmann Jun 27, 2023
7e3f87a
Add tests for outdated price response from oracle
ckoopmann Jun 27, 2023
332caf2
Fix tests
ckoopmann Jun 27, 2023
edbe0b0
Add method to override noRebalanceInProgress modifier
ckoopmann Jul 4, 2023
d9a653f
Fix tests
ckoopmann Jul 4, 2023
bed0e34
Use AaveOracle instead of configured chainlink oracles
ckoopmann Jul 31, 2023
87f4723
Merge branch 'master' into aave-v3-leverage-strategy-extension-audit-…
pblivin0x Aug 1, 2023
5d1cc59
Get AaveOracle address from AddressProvider instead of deploy argument
ckoopmann Aug 2, 2023
140f09b
Merge branch 'aave-v3-leverage-strategy-extension-audit-feedback' of …
ckoopmann Aug 2, 2023
0a69def
Change to get aaveOracle address on the fly from addressProvider
ckoopmann Aug 2, 2023
3b5c204
Clean up and additional code comments
ckoopmann Aug 2, 2023
998fd21
fix(dependencies): Add post audit Aave V3 dependencies (#145)
pblivin0x Aug 11, 2023
8ef6b7b
Updates to test (#146)
snake-poison Aug 14, 2023
125323c
remove .only from test
snake-poison Aug 14, 2023
0bc8b24
test: fix issues with updating block.
snake-poison Aug 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions contracts/adapters/AaveLeverageStrategyExtension.sol
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ contract AaveLeverageStrategyExtension is BaseExtension {
/**
* Throws if rebalance is currently in TWAP`
*/
modifier noRebalanceInProgress() {
modifier noRebalanceInProgress() virtual {
require(twapLeverageRatio == 0, "Rebalance is currently in progress");
_;
}
Expand Down Expand Up @@ -886,7 +886,7 @@ contract AaveLeverageStrategyExtension is BaseExtension {
*
* return ActionInfo Struct containing data used by internal lever and delever functions
*/
function _createActionInfo() internal view returns(ActionInfo memory) {
function _createActionInfo() internal view virtual returns(ActionInfo memory) {
ActionInfo memory rebalanceInfo;

// Calculate prices from chainlink. Chainlink returns prices with 8 decimal places, but we need 36 - underlyingDecimals decimal places.
Expand Down Expand Up @@ -915,6 +915,7 @@ contract AaveLeverageStrategyExtension is BaseExtension {
IncentiveSettings memory _incentive
)
internal
virtual
pure
{
require (
Expand Down Expand Up @@ -1060,6 +1061,7 @@ contract AaveLeverageStrategyExtension is BaseExtension {
)
internal
view
virtual
returns (uint256, uint256)
{
// Calculate absolute value of difference between new and current leverage ratio
Expand Down Expand Up @@ -1092,7 +1094,7 @@ contract AaveLeverageStrategyExtension is BaseExtension {
*
* return uint256 Max borrow notional denominated in collateral asset
*/
function _calculateMaxBorrowCollateral(ActionInfo memory _actionInfo, bool _isLever) internal view returns(uint256) {
function _calculateMaxBorrowCollateral(ActionInfo memory _actionInfo, bool _isLever) internal virtual view returns(uint256) {

// Retrieve collateral factor and liquidation threshold for the collateral asset in precise units (1e16 = 1%)
( , uint256 maxLtvRaw, uint256 liquidationThresholdRaw, , , , , , ,) = strategy.aaveProtocolDataProvider.getReserveConfigurationData(address(strategy.collateralAsset));
Expand Down Expand Up @@ -1144,7 +1146,7 @@ contract AaveLeverageStrategyExtension is BaseExtension {
*
* return uint256 Min position units to repay in borrow asset
*/
function _calculateMinRepayUnits(uint256 _collateralRebalanceUnits, uint256 _slippageTolerance, ActionInfo memory _actionInfo) internal pure returns (uint256) {
function _calculateMinRepayUnits(uint256 _collateralRebalanceUnits, uint256 _slippageTolerance, ActionInfo memory _actionInfo) internal virtual pure returns (uint256) {
return _collateralRebalanceUnits
.preciseMul(_actionInfo.collateralPrice)
.preciseDiv(_actionInfo.borrowPrice)
Expand Down Expand Up @@ -1271,4 +1273,4 @@ contract AaveLeverageStrategyExtension is BaseExtension {

return (enabledExchanges, shouldRebalanceEnums);
}
}
}
158 changes: 155 additions & 3 deletions contracts/adapters/AaveV3LeverageStrategyExtension.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;

import {AaveLeverageStrategyExtension} from "./AaveLeverageStrategyExtension.sol";
import { Math } from "@openzeppelin/contracts/math/Math.sol";

import {AaveLeverageStrategyExtension} from "./AaveLeverageStrategyExtension.sol";
import {IBaseManager} from "../interfaces/IBaseManager.sol";
import { IChainlinkAggregatorV3 } from "../interfaces/IChainlinkAggregatorV3.sol";
import {IPool} from "../interfaces/IPool.sol";
import {IPoolAddressesProvider} from "../interfaces/IPoolAddressesProvider.sol";
import { DataTypes } from "../interfaces/Datatypes.sol";
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting nits

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed with a bunch of other formatting / documentation related changes in this Commit:
3b5c204


/**
* @title AaveV3LeverageStrategyExtension
Expand All @@ -31,14 +37,22 @@ import {IBaseManager} from "../interfaces/IBaseManager.sol";
*
*/
contract AaveV3LeverageStrategyExtension is AaveLeverageStrategyExtension {

uint8 public currentEModeCategoryId;
IPoolAddressesProvider public lendingPoolAddressesProvider;
uint256 public maxOraclePriceAge;
bool public overrideNoRebalanceInProgress;

constructor(
IBaseManager _manager,
ContractSettings memory _strategy,
MethodologySettings memory _methodology,
ExecutionSettings memory _execution,
IncentiveSettings memory _incentive,
string[] memory _exchangeNames,
ExchangeSettings[] memory _exchangeSettings
ExchangeSettings[] memory _exchangeSettings,
IPoolAddressesProvider _lendingPoolAddressesProvider,
uint256 _maxOraclePriceAge
pblivin0x marked this conversation as resolved.
Show resolved Hide resolved
)
public
AaveLeverageStrategyExtension(
Expand All @@ -50,14 +64,40 @@ contract AaveV3LeverageStrategyExtension is AaveLeverageStrategyExtension {
_exchangeNames,
_exchangeSettings
)
{}
{
lendingPoolAddressesProvider = _lendingPoolAddressesProvider;
maxOraclePriceAge = _maxOraclePriceAge;
}

/* ============ Modifiers ============ */

/**
* Throws if rebalance is currently in TWAP` can be overriden by the operator
*/
modifier noRebalanceInProgress() override {
if(!overrideNoRebalanceInProgress) {
require(twapLeverageRatio == 0, "Rebalance is currently in progress");
}
_;
}


/**
* OPERATOR ONLY: Enable/Disable override of noRebalanceInProgress modifier
*
* @param _overrideNoRebalanceInProgress Boolean indicating wether to enable / disable override
*/
function setOverrideNoRebalanceInProgress(bool _overrideNoRebalanceInProgress) external onlyOperator {
overrideNoRebalanceInProgress = _overrideNoRebalanceInProgress;
}

/**
* OPERATOR ONLY: Set eMode categoryId to new value
*
* @param _categoryId eMode categoryId as defined on aaveV3
*/
function setEModeCategory(uint8 _categoryId) external onlyOperator {
currentEModeCategoryId = _categoryId;
_setEModeCategory(_categoryId);
}

Expand All @@ -66,4 +106,116 @@ contract AaveV3LeverageStrategyExtension is AaveLeverageStrategyExtension {
abi.encodeWithSignature("setEModeCategory(address,uint8)", address(strategy.setToken), _categoryId);
invokeManager(address(strategy.leverageModule), setEmodeCallData);
}

function _calculateMaxBorrowCollateral(ActionInfo memory _actionInfo, bool _isLever) internal override view returns(uint256) {

(uint256 maxLtvRaw, uint256 liquidationThresholdRaw) = _getLtvAndLiquidationThreshold();

// Normalize LTV and liquidation threshold to precise units. LTV is measured in 4 decimals in Aave which is why we must multiply by 1e14
// for example ETH has an LTV value of 8000 which represents 80%
if (_isLever) {
uint256 netBorrowLimit = _actionInfo.collateralValue
.preciseMul(maxLtvRaw.mul(10 ** 14))
.preciseMul(PreciseUnitMath.preciseUnit().sub(execution.unutilizedLeveragePercentage));

return netBorrowLimit
.sub(_actionInfo.borrowValue)
.preciseDiv(_actionInfo.collateralPrice);
} else {
uint256 netRepayLimit = _actionInfo.collateralValue
pblivin0x marked this conversation as resolved.
Show resolved Hide resolved
.preciseMul(liquidationThresholdRaw.mul(10 ** 14));

return _actionInfo.collateralBalance
.preciseMul(netRepayLimit.sub(_actionInfo.borrowValue))
.preciseDiv(netRepayLimit);
}
}

function _getLtvAndLiquidationThreshold() internal view returns(uint256, uint256) {
if(currentEModeCategoryId != 0 ) {
// Retrieve collateral factor and liquidation threshold for the collateral asset in precise units (1e16 = 1%)
DataTypes.EModeCategory memory emodeData = IPool(lendingPoolAddressesProvider.getPool()).getEModeCategoryData(currentEModeCategoryId);
pblivin0x marked this conversation as resolved.
Show resolved Hide resolved
return (emodeData.ltv, emodeData.liquidationThreshold);
} else {
( , uint256 maxLtvRaw, uint256 liquidationThresholdRaw, , , , , , ,) = strategy.aaveProtocolDataProvider.getReserveConfigurationData(address(strategy.collateralAsset));
return (maxLtvRaw, liquidationThresholdRaw);
}

}

function _validateNonExchangeSettings(
MethodologySettings memory _methodology,
ExecutionSettings memory _execution,
IncentiveSettings memory _incentive
)
internal
override
pure
{
super._validateNonExchangeSettings(_methodology, _execution, _incentive);
require(_methodology.targetLeverageRatio >= 1 ether, "Target leverage ratio must be >= 1e18");
pblivin0x marked this conversation as resolved.
Show resolved Hide resolved
}

function _calculateMinRepayUnits(uint256 _collateralRebalanceUnits, uint256 _slippageTolerance, ActionInfo memory _actionInfo) internal override pure returns (uint256) {
return _collateralRebalanceUnits
.preciseMul(_actionInfo.collateralPrice)
.preciseMul(PreciseUnitMath.preciseUnit().sub(_slippageTolerance)) // Changed order of mul / div here
.preciseDiv(_actionInfo.borrowPrice);
pblivin0x marked this conversation as resolved.
Show resolved Hide resolved
}

function _calculateChunkRebalanceNotional(
LeverageInfo memory _leverageInfo,
uint256 _newLeverageRatio,
bool _isLever
)
internal
view
override
returns (uint256, uint256)
{
// Calculate absolute value of difference between new and current leverage ratio
uint256 leverageRatioDifference = _isLever ? _newLeverageRatio.sub(_leverageInfo.currentLeverageRatio) : _leverageInfo.currentLeverageRatio.sub(_newLeverageRatio);

uint256 totalRebalanceNotional = leverageRatioDifference
.preciseMul(_leverageInfo.action.collateralBalance) // Changed order of mul / div here
.preciseDiv(_leverageInfo.currentLeverageRatio);

uint256 maxBorrow = _calculateMaxBorrowCollateral(_leverageInfo.action, _isLever);

uint256 chunkRebalanceNotional = Math.min(Math.min(maxBorrow, totalRebalanceNotional), _leverageInfo.twapMaxTradeSize);

return (chunkRebalanceNotional, totalRebalanceNotional);
}

/**
* Create the action info struct to be used in internal functions
*
* return ActionInfo Struct containing data used by internal lever and delever functions
*/
function _createActionInfo() internal view override returns(ActionInfo memory) {
ActionInfo memory rebalanceInfo;

// Calculate prices from chainlink. Chainlink returns prices with 8 decimal places, but we need 36 - underlyingDecimals decimal places.
// This is so that when the underlying amount is multiplied by the received price, the collateral valuation is normalized to 36 decimals.
// To perform this adjustment, we multiply by 10^(36 - 8 - underlyingDecimals)
rebalanceInfo.collateralPrice = _getAssetPrice(strategy.collateralPriceOracle, strategy.collateralDecimalAdjustment);
rebalanceInfo.borrowPrice = _getAssetPrice(strategy.borrowPriceOracle, strategy.borrowDecimalAdjustment);

rebalanceInfo.collateralBalance = strategy.targetCollateralAToken.balanceOf(address(strategy.setToken));
rebalanceInfo.borrowBalance = strategy.targetBorrowDebtToken.balanceOf(address(strategy.setToken));
rebalanceInfo.collateralValue = rebalanceInfo.collateralPrice.preciseMul(rebalanceInfo.collateralBalance);
rebalanceInfo.borrowValue = rebalanceInfo.borrowPrice.preciseMul(rebalanceInfo.borrowBalance);
rebalanceInfo.setTotalSupply = strategy.setToken.totalSupply();

return rebalanceInfo;
}

function _getAssetPrice(IChainlinkAggregatorV3 _priceOracle, uint256 _decimalAdjustment) internal view returns (uint256) {
(,int256 rawPrice,, uint256 updatedAt,) = _priceOracle.latestRoundData();
pblivin0x marked this conversation as resolved.
Show resolved Hide resolved
if(maxOraclePriceAge > 0) {
require(updatedAt > block.timestamp.sub(maxOraclePriceAge), "Price data is stale");
}
return rawPrice.toUint256().mul(10 ** _decimalAdjustment);
}

}
69 changes: 63 additions & 6 deletions contracts/interfaces/IAToken.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,67 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.10;


interface IAToken {
/**
* @dev Returns the address of the underlying asset of this aToken (E.g. WETH for aWETH)
**/
function UNDERLYING_ASSET_ADDRESS() external view returns (address);
event Approval(address indexed owner, address indexed spender, uint256 value);
event BalanceTransfer(address indexed from, address indexed to, uint256 value, uint256 index);
event Burn(address indexed from, address indexed target, uint256 value, uint256 balanceIncrease, uint256 index);
event Initialized(
address indexed underlyingAsset,
address indexed pool,
address treasury,
address incentivesController,
uint8 aTokenDecimals,
string aTokenName,
string aTokenSymbol,
bytes params
);
event Mint(
address indexed caller, address indexed onBehalfOf, uint256 value, uint256 balanceIncrease, uint256 index
);
event Transfer(address indexed from, address indexed to, uint256 value);

function ATOKEN_REVISION() external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function EIP712_REVISION() external view returns (bytes memory);
function PERMIT_TYPEHASH() external view returns (bytes32);
function POOL() external view returns (address);
function RESERVE_TREASURY_ADDRESS() external view returns (address);
function UNDERLYING_ASSET_ADDRESS() external view returns (address);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address user) external view returns (uint256);
function burn(address from, address receiverOfUnderlying, uint256 amount, uint256 index) external;
function decimals() external view returns (uint8);
function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);
function getIncentivesController() external view returns (address);
function getPreviousIndex(address user) external view returns (uint256);
function getScaledUserBalanceAndSupply(address user) external view returns (uint256, uint256);
function handleRepayment(address user, address onBehalfOf, uint256 amount) external;
function increaseAllowance(address spender, uint256 addedValue) external returns (bool);
function initialize(
address initializingPool,
address treasury,
address underlyingAsset,
address incentivesController,
uint8 aTokenDecimals,
string memory aTokenName,
string memory aTokenSymbol,
bytes memory params
) external;
function mint(address caller, address onBehalfOf, uint256 amount, uint256 index) external returns (bool);
function mintToTreasury(uint256 amount, uint256 index) external;
function name() external view returns (string memory);
function nonces(address owner) external view returns (uint256);
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external;
function rescueTokens(address token, address to, uint256 amount) external;
function scaledBalanceOf(address user) external view returns (uint256);
function scaledTotalSupply() external view returns (uint256);
function setIncentivesController(address controller) external;
function symbol() external view returns (string memory);
function totalSupply() external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function transferOnLiquidation(address from, address to, uint256 value) external;
function transferUnderlyingTo(address target, uint256 amount) external;
}

9 changes: 8 additions & 1 deletion contracts/interfaces/IChainlinkAggregatorV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,11 @@ pragma solidity 0.6.10;

interface IChainlinkAggregatorV3 {
function latestAnswer() external view returns (int256);
}
function latestRoundData() external view returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
19 changes: 18 additions & 1 deletion contracts/mocks/ChainlinkAggregatorV3Mock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity 0.6.10;
contract ChainlinkAggregatorV3Mock {

int256 private latestPrice;
uint256 private priceAge;

constructor() public {
latestPrice = 0;
Expand All @@ -17,4 +18,20 @@ contract ChainlinkAggregatorV3Mock {
function latestAnswer() external view returns (int256) {
return latestPrice;
}
}

function setPriceAge(uint256 _priceAge) external {
priceAge = _priceAge;
}

function latestRoundData() external view returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
) {
answer = latestPrice;
updatedAt = block.timestamp - priceAge;
startedAt = updatedAt - 1;
}
}
Loading