Skip to content

Commit 83e500f

Browse files
ckoopmannpblivin0xPranav Bhardwajsnake-poison
authored
AaveV3LeverageStrategyExtension audit feedback (#142)
* Test showing that settting Emode does not affect getReserveConfigurationData * Start adjusting StrategyExtension test to switcht to wsteth/eth pair * Fixing tests * Fix test * First test case for rebalancing in e-mode * Fix handling of emode ltv / liquidationThreshold * Add test case for wrong repay threshold * Fix tests * Remove subtraction of unutilizedLeveragePercentage when delevering * Add test for targetLeverageRatio verification * Add verification step for targetLeverageRatio >= 1 * Switch div / mul order in _calculateMinRepayUnits * Switch div / mul order in calculateChunkRebalanceNotional * Switch to latestRoundData on chainlink call * Add check for maximum oracle price age * Add tests for outdated price response from oracle * Fix tests * Add method to override noRebalanceInProgress modifier * Fix tests * Use AaveOracle instead of configured chainlink oracles * Get AaveOracle address from AddressProvider instead of deploy argument * Change to get aaveOracle address on the fly from addressProvider * Clean up and additional code comments * fix(dependencies): Add post audit Aave V3 dependencies (#145) update post audit contracts for integration tests Co-authored-by: Pranav Bhardwaj <[email protected]> * Updates to test (#146) * remove test filter on FlashMintLeveraged. * chore: update current block * test: updates to deployment of leverage module. * Fix failing tests for AaveV3LeverageStrategyExtension (#147) * Fix failing tests * Adjust test message for ripcord test --------- Co-authored-by: christn <[email protected]> * remove .only from test * test: fix issues with updating block. Made deadline on uniswap router swaps less brittle. --------- Co-authored-by: pblivin0x <[email protected]> Co-authored-by: Pranav Bhardwaj <[email protected]> Co-authored-by: SnakePoison <[email protected]>
1 parent 8727853 commit 83e500f

18 files changed

+1073
-484
lines changed

contracts/adapters/AaveLeverageStrategyExtension.sol

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ contract AaveLeverageStrategyExtension is BaseExtension {
189189
/**
190190
* Throws if rebalance is currently in TWAP`
191191
*/
192-
modifier noRebalanceInProgress() {
192+
modifier noRebalanceInProgress() virtual {
193193
require(twapLeverageRatio == 0, "Rebalance is currently in progress");
194194
_;
195195
}
@@ -886,7 +886,7 @@ contract AaveLeverageStrategyExtension is BaseExtension {
886886
*
887887
* return ActionInfo Struct containing data used by internal lever and delever functions
888888
*/
889-
function _createActionInfo() internal view returns(ActionInfo memory) {
889+
function _createActionInfo() internal view virtual returns(ActionInfo memory) {
890890
ActionInfo memory rebalanceInfo;
891891

892892
// Calculate prices from chainlink. Chainlink returns prices with 8 decimal places, but we need 36 - underlyingDecimals decimal places.
@@ -915,6 +915,7 @@ contract AaveLeverageStrategyExtension is BaseExtension {
915915
IncentiveSettings memory _incentive
916916
)
917917
internal
918+
virtual
918919
pure
919920
{
920921
require (
@@ -1060,6 +1061,7 @@ contract AaveLeverageStrategyExtension is BaseExtension {
10601061
)
10611062
internal
10621063
view
1064+
virtual
10631065
returns (uint256, uint256)
10641066
{
10651067
// Calculate absolute value of difference between new and current leverage ratio
@@ -1092,7 +1094,7 @@ contract AaveLeverageStrategyExtension is BaseExtension {
10921094
*
10931095
* return uint256 Max borrow notional denominated in collateral asset
10941096
*/
1095-
function _calculateMaxBorrowCollateral(ActionInfo memory _actionInfo, bool _isLever) internal view returns(uint256) {
1097+
function _calculateMaxBorrowCollateral(ActionInfo memory _actionInfo, bool _isLever) internal virtual view returns(uint256) {
10961098

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

12721274
return (enabledExchanges, shouldRebalanceEnums);
12731275
}
1274-
}
1276+
}

contracts/adapters/AaveV3LeverageStrategyExtension.sol

Lines changed: 208 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,15 @@
1919
pragma solidity 0.6.10;
2020
pragma experimental ABIEncoderV2;
2121

22-
import {AaveLeverageStrategyExtension} from "./AaveLeverageStrategyExtension.sol";
22+
import { Math } from "@openzeppelin/contracts/math/Math.sol";
2323

24-
import {IBaseManager} from "../interfaces/IBaseManager.sol";
24+
import { AaveLeverageStrategyExtension } from "./AaveLeverageStrategyExtension.sol";
25+
import { IBaseManager } from "../interfaces/IBaseManager.sol";
26+
import { IAaveOracle } from "../interfaces/IAaveOracle.sol";
27+
import { IPool } from "../interfaces/IPool.sol";
28+
import { IPoolAddressesProvider } from "../interfaces/IPoolAddressesProvider.sol";
29+
import { DataTypes } from "../interfaces/Datatypes.sol";
30+
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
2531

2632
/**
2733
* @title AaveV3LeverageStrategyExtension
@@ -31,14 +37,34 @@ import {IBaseManager} from "../interfaces/IBaseManager.sol";
3137
*
3238
*/
3339
contract AaveV3LeverageStrategyExtension is AaveLeverageStrategyExtension {
40+
41+
uint8 public currentEModeCategoryId; // EMode CategoryId currently set on Aave for the SetToken
42+
IPoolAddressesProvider public lendingPoolAddressesProvider; // Aave's address registry used to get Pool and Oracle addresses
43+
bool public overrideNoRebalanceInProgress; // Manager controlled flag that allows bypassing the noRebalanceInProgress modifier
44+
45+
/* ============ Constructor ============ */
46+
47+
/**
48+
* Instantiate addresses, methodology parameters, execution parameters, and incentive parameters.
49+
*
50+
* @param _manager Address of IBaseManager contract
51+
* @param _strategy Struct of contract addresses
52+
* @param _methodology Struct containing methodology parameters
53+
* @param _execution Struct containing execution parameters
54+
* @param _incentive Struct containing incentive parameters for ripcord
55+
* @param _exchangeNames List of initial exchange names
56+
* @param _exchangeSettings List of structs containing exchange parameters for the initial exchanges
57+
* @param _lendingPoolAddressesProvider Aave's address registry used to get Pool and Oracle addresses
58+
*/
3459
constructor(
3560
IBaseManager _manager,
3661
ContractSettings memory _strategy,
3762
MethodologySettings memory _methodology,
3863
ExecutionSettings memory _execution,
3964
IncentiveSettings memory _incentive,
4065
string[] memory _exchangeNames,
41-
ExchangeSettings[] memory _exchangeSettings
66+
ExchangeSettings[] memory _exchangeSettings,
67+
IPoolAddressesProvider _lendingPoolAddressesProvider
4268
)
4369
public
4470
AaveLeverageStrategyExtension(
@@ -50,20 +76,198 @@ contract AaveV3LeverageStrategyExtension is AaveLeverageStrategyExtension {
5076
_exchangeNames,
5177
_exchangeSettings
5278
)
53-
{}
79+
{
80+
lendingPoolAddressesProvider = _lendingPoolAddressesProvider;
81+
}
82+
83+
/* ============ Modifiers ============ */
84+
85+
/**
86+
* Throws if rebalance is currently in TWAP` can be overriden by the operator
87+
*/
88+
modifier noRebalanceInProgress() override {
89+
if(!overrideNoRebalanceInProgress) {
90+
require(twapLeverageRatio == 0, "Rebalance is currently in progress");
91+
}
92+
_;
93+
}
94+
95+
96+
/* ============ External Functions ============ */
97+
98+
/**
99+
* OPERATOR ONLY: Enable/Disable override of noRebalanceInProgress modifier
100+
*
101+
* @param _overrideNoRebalanceInProgress Boolean indicating wether to enable / disable override
102+
*/
103+
function setOverrideNoRebalanceInProgress(bool _overrideNoRebalanceInProgress) external onlyOperator {
104+
overrideNoRebalanceInProgress = _overrideNoRebalanceInProgress;
105+
}
54106

55107
/**
56108
* OPERATOR ONLY: Set eMode categoryId to new value
57109
*
58110
* @param _categoryId eMode categoryId as defined on aaveV3
59111
*/
60112
function setEModeCategory(uint8 _categoryId) external onlyOperator {
113+
currentEModeCategoryId = _categoryId;
61114
_setEModeCategory(_categoryId);
62115
}
63116

117+
/* ============ Internal Functions ============ */
118+
119+
/**
120+
* Sets EMode category in AaveV3 on behalf of the SetToken
121+
*/
64122
function _setEModeCategory(uint8 _categoryId) internal {
65123
bytes memory setEmodeCallData =
66124
abi.encodeWithSignature("setEModeCategory(address,uint8)", address(strategy.setToken), _categoryId);
67125
invokeManager(address(strategy.leverageModule), setEmodeCallData);
68126
}
127+
128+
/**
129+
* Calculate the max borrow / repay amount allowed in base units for lever / delever. This is due to overcollateralization requirements on
130+
* assets deposited in lending protocols for borrowing.
131+
*
132+
*/
133+
function _calculateMaxBorrowCollateral(ActionInfo memory _actionInfo, bool _isLever) internal override view returns(uint256) {
134+
135+
(uint256 maxLtvRaw, uint256 liquidationThresholdRaw) = _getLtvAndLiquidationThreshold();
136+
137+
// Normalize LTV and liquidation threshold to precise units. LTV is measured in 4 decimals in Aave which is why we must multiply by 1e14
138+
// for example ETH has an LTV value of 8000 which represents 80%
139+
if (_isLever) {
140+
uint256 netBorrowLimit = _actionInfo.collateralValue
141+
.preciseMul(maxLtvRaw.mul(10 ** 14))
142+
.preciseMul(PreciseUnitMath.preciseUnit().sub(execution.unutilizedLeveragePercentage));
143+
144+
return netBorrowLimit
145+
.sub(_actionInfo.borrowValue)
146+
.preciseDiv(_actionInfo.collateralPrice);
147+
} else {
148+
uint256 netRepayLimit = _actionInfo.collateralValue
149+
.preciseMul(liquidationThresholdRaw.mul(10 ** 14));
150+
151+
return _actionInfo.collateralBalance
152+
.preciseMul(netRepayLimit.sub(_actionInfo.borrowValue))
153+
.preciseDiv(netRepayLimit);
154+
}
155+
}
156+
157+
/**
158+
* Calculates LTV and LquidationThreshold either based on ReserveConfiguration or EModeCategory depending on wether EMode is enabled or not
159+
*
160+
*/
161+
function _getLtvAndLiquidationThreshold() internal view returns(uint256, uint256) {
162+
if(currentEModeCategoryId != 0 ) {
163+
// Retrieve collateral factor and liquidation threshold for the collateral asset in precise units (1e16 = 1%)
164+
DataTypes.EModeCategory memory emodeData = IPool(lendingPoolAddressesProvider.getPool()).getEModeCategoryData(currentEModeCategoryId);
165+
return (emodeData.ltv, emodeData.liquidationThreshold);
166+
} else {
167+
( , uint256 maxLtvRaw, uint256 liquidationThresholdRaw, , , , , , ,) = strategy.aaveProtocolDataProvider.getReserveConfigurationData(address(strategy.collateralAsset));
168+
return (maxLtvRaw, liquidationThresholdRaw);
169+
}
170+
171+
}
172+
173+
/**
174+
* Validate non-exchange settings in constructor and setters when updating.
175+
*/
176+
function _validateNonExchangeSettings(
177+
MethodologySettings memory _methodology,
178+
ExecutionSettings memory _execution,
179+
IncentiveSettings memory _incentive
180+
)
181+
internal
182+
override
183+
pure
184+
{
185+
super._validateNonExchangeSettings(_methodology, _execution, _incentive);
186+
require(_methodology.targetLeverageRatio >= 1 ether, "Target leverage ratio must be >= 1e18");
187+
}
188+
189+
/**
190+
* Derive the min repay units from collateral units for delever. Units are calculated as target collateral rebalance units multiplied by slippage tolerance
191+
* and pair price (collateral oracle price / borrow oracle price). Output is measured in borrow unit decimals.
192+
*
193+
* return uint256 Min position units to repay in borrow asset
194+
*/
195+
function _calculateMinRepayUnits(uint256 _collateralRebalanceUnits, uint256 _slippageTolerance, ActionInfo memory _actionInfo)
196+
internal
197+
override
198+
pure
199+
returns(uint256)
200+
{
201+
return _collateralRebalanceUnits
202+
.preciseMul(_actionInfo.collateralPrice)
203+
.preciseMul(PreciseUnitMath.preciseUnit().sub(_slippageTolerance)) // Changed order of mul / div here
204+
.preciseDiv(_actionInfo.borrowPrice);
205+
}
206+
207+
/**
208+
* Calculate total notional rebalance quantity and chunked rebalance quantity in collateral units.
209+
*
210+
* return uint256 Chunked rebalance notional in collateral units
211+
* return uint256 Total rebalance notional in collateral units
212+
*/
213+
function _calculateChunkRebalanceNotional(
214+
LeverageInfo memory _leverageInfo,
215+
uint256 _newLeverageRatio,
216+
bool _isLever
217+
)
218+
internal
219+
view
220+
override
221+
returns (uint256, uint256)
222+
{
223+
// Calculate absolute value of difference between new and current leverage ratio
224+
uint256 leverageRatioDifference = _isLever ?
225+
_newLeverageRatio.sub(_leverageInfo.currentLeverageRatio) :
226+
_leverageInfo.currentLeverageRatio.sub(_newLeverageRatio);
227+
228+
uint256 totalRebalanceNotional = leverageRatioDifference
229+
.preciseMul(_leverageInfo.action.collateralBalance) // Changed order of mul / div here
230+
.preciseDiv(_leverageInfo.currentLeverageRatio);
231+
232+
uint256 maxBorrow = _calculateMaxBorrowCollateral(_leverageInfo.action, _isLever);
233+
234+
uint256 chunkRebalanceNotional = Math.min(Math.min(maxBorrow, totalRebalanceNotional), _leverageInfo.twapMaxTradeSize);
235+
236+
return (chunkRebalanceNotional, totalRebalanceNotional);
237+
}
238+
239+
/**
240+
* Create the action info struct to be used in internal functions
241+
*
242+
* return ActionInfo Struct containing data used by internal lever and delever functions
243+
*/
244+
function _createActionInfo() internal view override returns(ActionInfo memory) {
245+
ActionInfo memory rebalanceInfo;
246+
247+
// Calculate prices from chainlink. AaveOracle returns prices with 8 decimal places, but we need 36 - underlyingDecimals decimal places.
248+
// This is so that when the underlying amount is multiplied by the received price, the collateral valuation is normalized to 36 decimals.
249+
// To perform this adjustment, we multiply by 10^(36 - 8 - underlyingDecimals)
250+
rebalanceInfo.collateralPrice = _getAssetPrice(strategy.collateralAsset, strategy.collateralDecimalAdjustment);
251+
rebalanceInfo.borrowPrice = _getAssetPrice(strategy.borrowAsset, strategy.borrowDecimalAdjustment);
252+
253+
rebalanceInfo.collateralBalance = strategy.targetCollateralAToken.balanceOf(address(strategy.setToken));
254+
rebalanceInfo.borrowBalance = strategy.targetBorrowDebtToken.balanceOf(address(strategy.setToken));
255+
rebalanceInfo.collateralValue = rebalanceInfo.collateralPrice.preciseMul(rebalanceInfo.collateralBalance);
256+
rebalanceInfo.borrowValue = rebalanceInfo.borrowPrice.preciseMul(rebalanceInfo.borrowBalance);
257+
rebalanceInfo.setTotalSupply = strategy.setToken.totalSupply();
258+
259+
return rebalanceInfo;
260+
}
261+
262+
/**
263+
* Gets AssetPrice from AaveOracle and multiplies by decimalAdjustment if necessary
264+
*
265+
* return uint256 Asset price normalized to desired number of decimals
266+
*/
267+
function _getAssetPrice(address _asset, uint256 _decimalAdjustment) internal view returns (uint256) {
268+
IAaveOracle aaveOracle = IAaveOracle(IPoolAddressesProvider(lendingPoolAddressesProvider).getPriceOracle());
269+
uint256 rawPrice = aaveOracle.getAssetPrice(_asset);
270+
return rawPrice.mul(10 ** _decimalAdjustment);
271+
}
272+
69273
}

contracts/interfaces/IAToken.sol

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,67 @@
1-
// SPDX-License-Identifier: agpl-3.0
21
pragma solidity 0.6.10;
32

4-
53
interface IAToken {
6-
/**
7-
* @dev Returns the address of the underlying asset of this aToken (E.g. WETH for aWETH)
8-
**/
9-
function UNDERLYING_ASSET_ADDRESS() external view returns (address);
4+
event Approval(address indexed owner, address indexed spender, uint256 value);
5+
event BalanceTransfer(address indexed from, address indexed to, uint256 value, uint256 index);
6+
event Burn(address indexed from, address indexed target, uint256 value, uint256 balanceIncrease, uint256 index);
7+
event Initialized(
8+
address indexed underlyingAsset,
9+
address indexed pool,
10+
address treasury,
11+
address incentivesController,
12+
uint8 aTokenDecimals,
13+
string aTokenName,
14+
string aTokenSymbol,
15+
bytes params
16+
);
17+
event Mint(
18+
address indexed caller, address indexed onBehalfOf, uint256 value, uint256 balanceIncrease, uint256 index
19+
);
20+
event Transfer(address indexed from, address indexed to, uint256 value);
21+
22+
function ATOKEN_REVISION() external view returns (uint256);
23+
function DOMAIN_SEPARATOR() external view returns (bytes32);
24+
function EIP712_REVISION() external view returns (bytes memory);
25+
function PERMIT_TYPEHASH() external view returns (bytes32);
26+
function POOL() external view returns (address);
27+
function RESERVE_TREASURY_ADDRESS() external view returns (address);
28+
function UNDERLYING_ASSET_ADDRESS() external view returns (address);
29+
function allowance(address owner, address spender) external view returns (uint256);
30+
function approve(address spender, uint256 amount) external returns (bool);
31+
function balanceOf(address user) external view returns (uint256);
32+
function burn(address from, address receiverOfUnderlying, uint256 amount, uint256 index) external;
33+
function decimals() external view returns (uint8);
34+
function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);
35+
function getIncentivesController() external view returns (address);
36+
function getPreviousIndex(address user) external view returns (uint256);
37+
function getScaledUserBalanceAndSupply(address user) external view returns (uint256, uint256);
38+
function handleRepayment(address user, address onBehalfOf, uint256 amount) external;
39+
function increaseAllowance(address spender, uint256 addedValue) external returns (bool);
40+
function initialize(
41+
address initializingPool,
42+
address treasury,
43+
address underlyingAsset,
44+
address incentivesController,
45+
uint8 aTokenDecimals,
46+
string memory aTokenName,
47+
string memory aTokenSymbol,
48+
bytes memory params
49+
) external;
50+
function mint(address caller, address onBehalfOf, uint256 amount, uint256 index) external returns (bool);
51+
function mintToTreasury(uint256 amount, uint256 index) external;
52+
function name() external view returns (string memory);
53+
function nonces(address owner) external view returns (uint256);
54+
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
55+
external;
56+
function rescueTokens(address token, address to, uint256 amount) external;
57+
function scaledBalanceOf(address user) external view returns (uint256);
58+
function scaledTotalSupply() external view returns (uint256);
59+
function setIncentivesController(address controller) external;
60+
function symbol() external view returns (string memory);
61+
function totalSupply() external view returns (uint256);
62+
function transfer(address recipient, uint256 amount) external returns (bool);
63+
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
64+
function transferOnLiquidation(address from, address to, uint256 value) external;
65+
function transferUnderlyingTo(address target, uint256 amount) external;
1066
}
67+

contracts/interfaces/IAaveOracle.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
pragma solidity ^0.8.10;
1+
pragma solidity 0.6.10;
22

33
interface IAaveOracle {
44
event AssetSourceUpdated(address indexed asset, address indexed source);

0 commit comments

Comments
 (0)